454 lines
12 KiB
Go
454 lines
12 KiB
Go
package store
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func newTestStore(t *testing.T) *Store {
|
|
t.Helper()
|
|
s, err := New(":memory:")
|
|
if err != nil {
|
|
t.Fatalf("failed to open in-memory store: %v", err)
|
|
}
|
|
t.Cleanup(func() { s.Close() })
|
|
return s
|
|
}
|
|
|
|
// ── Users ─────────────────────────────────────────────────────────────────────
|
|
|
|
func TestUpsertAndGetUserHash(t *testing.T) {
|
|
s := newTestStore(t)
|
|
|
|
if err := s.UpsertUser("alice", "hash123"); err != nil {
|
|
t.Fatalf("UpsertUser: %v", err)
|
|
}
|
|
|
|
h, err := s.GetUserHash("alice")
|
|
if err != nil {
|
|
t.Fatalf("GetUserHash: %v", err)
|
|
}
|
|
if h != "hash123" {
|
|
t.Errorf("expected hash123, got %q", h)
|
|
}
|
|
}
|
|
|
|
func TestGetUserHash_NotFound(t *testing.T) {
|
|
s := newTestStore(t)
|
|
|
|
_, err := s.GetUserHash("nobody")
|
|
if err == nil {
|
|
t.Fatal("expected error for missing user, got nil")
|
|
}
|
|
}
|
|
|
|
func TestUpsertUser_Update(t *testing.T) {
|
|
s := newTestStore(t)
|
|
|
|
if err := s.UpsertUser("alice", "first"); err != nil {
|
|
t.Fatalf("UpsertUser: %v", err)
|
|
}
|
|
if err := s.UpsertUser("alice", "second"); err != nil {
|
|
t.Fatalf("UpsertUser update: %v", err)
|
|
}
|
|
|
|
h, err := s.GetUserHash("alice")
|
|
if err != nil {
|
|
t.Fatalf("GetUserHash: %v", err)
|
|
}
|
|
if h != "second" {
|
|
t.Errorf("expected second, got %q", h)
|
|
}
|
|
}
|
|
|
|
func TestUserExists(t *testing.T) {
|
|
s := newTestStore(t)
|
|
|
|
ok, err := s.UserExists("alice")
|
|
if err != nil {
|
|
t.Fatalf("UserExists: %v", err)
|
|
}
|
|
if ok {
|
|
t.Error("expected false for non-existent user")
|
|
}
|
|
|
|
_ = s.UpsertUser("alice", "hash")
|
|
|
|
ok, err = s.UserExists("alice")
|
|
if err != nil {
|
|
t.Fatalf("UserExists: %v", err)
|
|
}
|
|
if !ok {
|
|
t.Error("expected true after insert")
|
|
}
|
|
}
|
|
|
|
// ── Agents ────────────────────────────────────────────────────────────────────
|
|
|
|
func TestCreateAgentToken(t *testing.T) {
|
|
s := newTestStore(t)
|
|
|
|
if err := s.CreateAgentToken("id1", "tok1", "host1"); err != nil {
|
|
t.Fatalf("CreateAgentToken: %v", err)
|
|
}
|
|
|
|
a, err := s.AgentByToken("tok1")
|
|
if err != nil {
|
|
t.Fatalf("AgentByToken: %v", err)
|
|
}
|
|
if a.ID != "id1" || a.Hostname != "host1" {
|
|
t.Errorf("unexpected agent: %+v", a)
|
|
}
|
|
}
|
|
|
|
func TestAgentByToken_NotFound(t *testing.T) {
|
|
s := newTestStore(t)
|
|
|
|
_, err := s.AgentByToken("doesnotexist")
|
|
if err == nil {
|
|
t.Fatal("expected error for unknown token")
|
|
}
|
|
}
|
|
|
|
func TestUpsertAgent(t *testing.T) {
|
|
s := newTestStore(t)
|
|
|
|
a := &Agent{
|
|
ID: "agent1",
|
|
Token: "tok1",
|
|
Hostname: "myhost",
|
|
Alias: "myalias",
|
|
IPAddress: "10.0.0.1",
|
|
Arch: "amd64",
|
|
OS: "linux",
|
|
Online: true,
|
|
}
|
|
if err := s.UpsertAgent(a); err != nil {
|
|
t.Fatalf("UpsertAgent: %v", err)
|
|
}
|
|
|
|
got, err := s.GetAgent("agent1")
|
|
if err != nil {
|
|
t.Fatalf("GetAgent: %v", err)
|
|
}
|
|
if got.Hostname != "myhost" || got.Alias != "myalias" {
|
|
t.Errorf("unexpected agent: %+v", got)
|
|
}
|
|
}
|
|
|
|
func TestListAgents(t *testing.T) {
|
|
s := newTestStore(t)
|
|
|
|
_ = s.CreateAgentToken("a1", "t1", "host-b")
|
|
_ = s.CreateAgentToken("a2", "t2", "host-a")
|
|
|
|
agents, err := s.ListAgents()
|
|
if err != nil {
|
|
t.Fatalf("ListAgents: %v", err)
|
|
}
|
|
if len(agents) != 2 {
|
|
t.Fatalf("expected 2 agents, got %d", len(agents))
|
|
}
|
|
// ORDER BY hostname: host-a < host-b
|
|
if agents[0].Hostname != "host-a" || agents[1].Hostname != "host-b" {
|
|
t.Errorf("unexpected order: %v %v", agents[0].Hostname, agents[1].Hostname)
|
|
}
|
|
}
|
|
|
|
func TestSetAgentOffline(t *testing.T) {
|
|
s := newTestStore(t)
|
|
|
|
_ = s.UpsertAgent(&Agent{
|
|
ID: "a1",
|
|
Token: "t1",
|
|
Hostname: "h1",
|
|
Online: true,
|
|
})
|
|
|
|
if err := s.SetAgentOffline("a1"); err != nil {
|
|
t.Fatalf("SetAgentOffline: %v", err)
|
|
}
|
|
|
|
a, err := s.GetAgent("a1")
|
|
if err != nil {
|
|
t.Fatalf("GetAgent: %v", err)
|
|
}
|
|
if a.Online {
|
|
t.Error("expected Online=false after SetAgentOffline")
|
|
}
|
|
}
|
|
|
|
func TestUpdateAgentAlias(t *testing.T) {
|
|
s := newTestStore(t)
|
|
|
|
_ = s.CreateAgentToken("a1", "t1", "host1")
|
|
|
|
if err := s.UpdateAgentAlias("a1", "newalias"); err != nil {
|
|
t.Fatalf("UpdateAgentAlias: %v", err)
|
|
}
|
|
|
|
a, err := s.GetAgent("a1")
|
|
if err != nil {
|
|
t.Fatalf("GetAgent: %v", err)
|
|
}
|
|
if a.Alias != "newalias" {
|
|
t.Errorf("expected alias 'newalias', got %q", a.Alias)
|
|
}
|
|
}
|
|
|
|
func TestDeleteAgent(t *testing.T) {
|
|
s := newTestStore(t)
|
|
|
|
if err := s.CreateAgentToken("a1", "t1", "host1"); err != nil {
|
|
t.Fatalf("CreateAgentToken: %v", err)
|
|
}
|
|
|
|
if err := s.DeleteAgent("a1"); err != nil {
|
|
t.Fatalf("DeleteAgent: %v", err)
|
|
}
|
|
|
|
_, err := s.GetAgent("a1")
|
|
if err == nil {
|
|
t.Error("expected error after deletion, got nil")
|
|
}
|
|
}
|
|
|
|
func TestDeleteAgent_NotFound(t *testing.T) {
|
|
s := newTestStore(t)
|
|
|
|
// Deleting a non-existent agent should not error (DELETE is idempotent at SQL level)
|
|
if err := s.DeleteAgent("nonexistent"); err != nil {
|
|
t.Fatalf("DeleteAgent on missing id: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestDeleteAgent_RemovesFromList(t *testing.T) {
|
|
s := newTestStore(t)
|
|
|
|
_ = s.CreateAgentToken("a1", "t1", "host1")
|
|
_ = s.CreateAgentToken("a2", "t2", "host2")
|
|
|
|
if err := s.DeleteAgent("a1"); err != nil {
|
|
t.Fatalf("DeleteAgent: %v", err)
|
|
}
|
|
|
|
agents, err := s.ListAgents()
|
|
if err != nil {
|
|
t.Fatalf("ListAgents: %v", err)
|
|
}
|
|
if len(agents) != 1 {
|
|
t.Fatalf("expected 1 agent after deletion, got %d", len(agents))
|
|
}
|
|
if agents[0].ID != "a2" {
|
|
t.Errorf("expected remaining agent to be a2, got %q", agents[0].ID)
|
|
}
|
|
}
|
|
|
|
func TestCreateAgentToken_IdempotentIgnore(t *testing.T) {
|
|
s := newTestStore(t)
|
|
|
|
// INSERT OR IGNORE — second call should not error
|
|
if err := s.CreateAgentToken("id1", "tok1", "h1"); err != nil {
|
|
t.Fatalf("first call: %v", err)
|
|
}
|
|
if err := s.CreateAgentToken("id1", "tok1", "h1"); err != nil {
|
|
t.Fatalf("second call (should be idempotent): %v", err)
|
|
}
|
|
}
|
|
|
|
// ── AutoUpdatePolicies ────────────────────────────────────────────────────────
|
|
|
|
// helper: create an agent prerequisite for FK constraints.
|
|
func createAgent(t *testing.T, s *Store, id, token, hostname string) {
|
|
t.Helper()
|
|
if err := s.CreateAgentToken(id, token, hostname); err != nil {
|
|
t.Fatalf("createAgent: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestUpsertAndGetAutoUpdatePolicy(t *testing.T) {
|
|
s := newTestStore(t)
|
|
createAgent(t, s, "ag1", "tok1", "host1")
|
|
|
|
p := &AutoUpdatePolicy{
|
|
AgentID: "ag1",
|
|
ContainerID: "ctr1",
|
|
Enabled: true,
|
|
IntervalMinutes: 60,
|
|
}
|
|
if err := s.UpsertAutoUpdatePolicy(p); err != nil {
|
|
t.Fatalf("UpsertAutoUpdatePolicy: %v", err)
|
|
}
|
|
|
|
got, err := s.GetAutoUpdatePolicy("ag1", "ctr1")
|
|
if err != nil {
|
|
t.Fatalf("GetAutoUpdatePolicy: %v", err)
|
|
}
|
|
if got == nil {
|
|
t.Fatal("expected policy, got nil")
|
|
}
|
|
if !got.Enabled || got.IntervalMinutes != 60 {
|
|
t.Errorf("unexpected policy: %+v", got)
|
|
}
|
|
if got.LastCheckedAt != nil || got.LastUpdatedAt != nil {
|
|
t.Error("expected nil timestamps on fresh policy")
|
|
}
|
|
}
|
|
|
|
func TestGetAutoUpdatePolicy_NotFound(t *testing.T) {
|
|
s := newTestStore(t)
|
|
|
|
p, err := s.GetAutoUpdatePolicy("nobody", "ctr")
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if p != nil {
|
|
t.Errorf("expected nil, got %+v", p)
|
|
}
|
|
}
|
|
|
|
func TestUpsertAutoUpdatePolicy_Update(t *testing.T) {
|
|
s := newTestStore(t)
|
|
createAgent(t, s, "ag1", "tok1", "host1")
|
|
|
|
_ = s.UpsertAutoUpdatePolicy(&AutoUpdatePolicy{AgentID: "ag1", ContainerID: "ctr1", Enabled: true, IntervalMinutes: 60})
|
|
_ = s.UpsertAutoUpdatePolicy(&AutoUpdatePolicy{AgentID: "ag1", ContainerID: "ctr1", Enabled: false, IntervalMinutes: 1440})
|
|
|
|
got, err := s.GetAutoUpdatePolicy("ag1", "ctr1")
|
|
if err != nil {
|
|
t.Fatalf("GetAutoUpdatePolicy: %v", err)
|
|
}
|
|
if got.Enabled || got.IntervalMinutes != 1440 {
|
|
t.Errorf("expected updated policy, got %+v", got)
|
|
}
|
|
}
|
|
|
|
func TestUpdateAutoUpdateChecked(t *testing.T) {
|
|
s := newTestStore(t)
|
|
createAgent(t, s, "ag1", "tok1", "host1")
|
|
_ = s.UpsertAutoUpdatePolicy(&AutoUpdatePolicy{AgentID: "ag1", ContainerID: "ctr1", Enabled: true, IntervalMinutes: 60})
|
|
|
|
now := time.Now().Truncate(time.Second)
|
|
if err := s.UpdateAutoUpdateChecked("ag1", "ctr1", now); err != nil {
|
|
t.Fatalf("UpdateAutoUpdateChecked: %v", err)
|
|
}
|
|
|
|
got, _ := s.GetAutoUpdatePolicy("ag1", "ctr1")
|
|
if got.LastCheckedAt == nil {
|
|
t.Fatal("expected LastCheckedAt to be set")
|
|
}
|
|
if got.LastCheckedAt.UTC().Truncate(time.Second) != now.UTC() {
|
|
t.Errorf("expected %v, got %v", now.UTC(), got.LastCheckedAt.UTC().Truncate(time.Second))
|
|
}
|
|
}
|
|
|
|
func TestUpdateAutoUpdateDone(t *testing.T) {
|
|
s := newTestStore(t)
|
|
createAgent(t, s, "ag1", "tok1", "host1")
|
|
_ = s.UpsertAutoUpdatePolicy(&AutoUpdatePolicy{AgentID: "ag1", ContainerID: "ctr1", Enabled: true, IntervalMinutes: 60})
|
|
|
|
now := time.Now().Truncate(time.Second)
|
|
if err := s.UpdateAutoUpdateDone("ag1", "ctr1", now); err != nil {
|
|
t.Fatalf("UpdateAutoUpdateDone: %v", err)
|
|
}
|
|
|
|
got, _ := s.GetAutoUpdatePolicy("ag1", "ctr1")
|
|
if got.LastUpdatedAt == nil {
|
|
t.Fatal("expected LastUpdatedAt to be set")
|
|
}
|
|
}
|
|
|
|
func TestListDueAutoUpdatePolicies_NullLastChecked(t *testing.T) {
|
|
s := newTestStore(t)
|
|
createAgent(t, s, "ag1", "tok1", "host1")
|
|
_ = s.UpsertAutoUpdatePolicy(&AutoUpdatePolicy{AgentID: "ag1", ContainerID: "ctr1", Enabled: true, IntervalMinutes: 60})
|
|
|
|
// last_checked_at IS NULL → should be due immediately.
|
|
due, err := s.ListDueAutoUpdatePolicies(time.Now())
|
|
if err != nil {
|
|
t.Fatalf("ListDueAutoUpdatePolicies: %v", err)
|
|
}
|
|
if len(due) != 1 {
|
|
t.Fatalf("expected 1 due policy, got %d", len(due))
|
|
}
|
|
if due[0].ContainerID != "ctr1" {
|
|
t.Errorf("unexpected container: %q", due[0].ContainerID)
|
|
}
|
|
}
|
|
|
|
func TestListDueAutoUpdatePolicies_NotDueYet(t *testing.T) {
|
|
s := newTestStore(t)
|
|
createAgent(t, s, "ag1", "tok1", "host1")
|
|
_ = s.UpsertAutoUpdatePolicy(&AutoUpdatePolicy{AgentID: "ag1", ContainerID: "ctr1", Enabled: true, IntervalMinutes: 1440})
|
|
|
|
// Mark as just checked — not due yet.
|
|
_ = s.UpdateAutoUpdateChecked("ag1", "ctr1", time.Now())
|
|
|
|
due, err := s.ListDueAutoUpdatePolicies(time.Now())
|
|
if err != nil {
|
|
t.Fatalf("ListDueAutoUpdatePolicies: %v", err)
|
|
}
|
|
if len(due) != 0 {
|
|
t.Fatalf("expected 0 due policies (just checked), got %d", len(due))
|
|
}
|
|
}
|
|
|
|
func TestListDueAutoUpdatePolicies_Due(t *testing.T) {
|
|
s := newTestStore(t)
|
|
createAgent(t, s, "ag1", "tok1", "host1")
|
|
_ = s.UpsertAutoUpdatePolicy(&AutoUpdatePolicy{AgentID: "ag1", ContainerID: "ctr1", Enabled: true, IntervalMinutes: 60})
|
|
|
|
// Simulate last check 2 hours ago → should be due.
|
|
past := time.Now().Add(-2 * time.Hour)
|
|
_ = s.UpdateAutoUpdateChecked("ag1", "ctr1", past)
|
|
|
|
due, err := s.ListDueAutoUpdatePolicies(time.Now())
|
|
if err != nil {
|
|
t.Fatalf("ListDueAutoUpdatePolicies: %v", err)
|
|
}
|
|
if len(due) != 1 {
|
|
t.Fatalf("expected 1 due policy (overdue), got %d", len(due))
|
|
}
|
|
}
|
|
|
|
func TestListDueAutoUpdatePolicies_DisabledExcluded(t *testing.T) {
|
|
s := newTestStore(t)
|
|
createAgent(t, s, "ag1", "tok1", "host1")
|
|
_ = s.UpsertAutoUpdatePolicy(&AutoUpdatePolicy{AgentID: "ag1", ContainerID: "ctr1", Enabled: false, IntervalMinutes: 60})
|
|
|
|
due, err := s.ListDueAutoUpdatePolicies(time.Now())
|
|
if err != nil {
|
|
t.Fatalf("ListDueAutoUpdatePolicies: %v", err)
|
|
}
|
|
if len(due) != 0 {
|
|
t.Fatalf("expected 0 due policies (disabled), got %d", len(due))
|
|
}
|
|
}
|
|
|
|
func TestDeleteAutoUpdatePolicy(t *testing.T) {
|
|
s := newTestStore(t)
|
|
createAgent(t, s, "ag1", "tok1", "host1")
|
|
_ = s.UpsertAutoUpdatePolicy(&AutoUpdatePolicy{AgentID: "ag1", ContainerID: "ctr1", Enabled: true, IntervalMinutes: 60})
|
|
|
|
if err := s.DeleteAutoUpdatePolicy("ag1", "ctr1"); err != nil {
|
|
t.Fatalf("DeleteAutoUpdatePolicy: %v", err)
|
|
}
|
|
|
|
got, err := s.GetAutoUpdatePolicy("ag1", "ctr1")
|
|
if err != nil {
|
|
t.Fatalf("GetAutoUpdatePolicy: %v", err)
|
|
}
|
|
if got != nil {
|
|
t.Error("expected nil after deletion")
|
|
}
|
|
}
|
|
|
|
func TestDeleteAutoUpdatePolicy_Idempotent(t *testing.T) {
|
|
s := newTestStore(t)
|
|
|
|
// Deleting a non-existent policy should not error.
|
|
if err := s.DeleteAutoUpdatePolicy("nobody", "ctr"); err != nil {
|
|
t.Fatalf("DeleteAutoUpdatePolicy on missing: %v", err)
|
|
}
|
|
}
|