package sqlite import ( "context" "testing" ) func TestImportBatchesRepoCreateAndGet(t *testing.T) { store := openTestDB(t) hostID := createTestHost(t, store) packID := createTestPack(t, store) providerID := createTestProviderWithPack(t, store, packID) id, err := store.ImportBatches().Create(context.Background(), ImportBatch{ HostID: hostID, PackID: packID, ProviderID: providerID, Mode: "partial", BatchStatus: "running", AccessStatus: "pending", }) if err != nil { t.Fatalf("Create() error = %v", err) } if id <= 0 { t.Fatalf("Create() id = %d, want positive", id) } got, _ := store.ImportBatches().GetByID(context.Background(), id) if got.Mode != "partial" || got.BatchStatus != "running" { t.Fatalf("GetByID() = %+v, want running batch", got) } } func TestImportBatchesRepoUpdateStatus(t *testing.T) { store := openTestDB(t) hostID := createTestHost(t, store) packID := createTestPack(t, store) providerID := createTestProviderWithPack(t, store, packID) id, _ := store.ImportBatches().Create(context.Background(), ImportBatch{ HostID: hostID, PackID: packID, ProviderID: providerID, Mode: "partial", BatchStatus: "running", AccessStatus: "pending", }) err := store.ImportBatches().UpdateStatus(context.Background(), id, "succeeded", "subscription_ready") if err != nil { t.Fatalf("UpdateStatus() error = %v", err) } got, _ := store.ImportBatches().GetByID(context.Background(), id) if got.BatchStatus != "succeeded" || got.AccessStatus != "subscription_ready" { t.Fatalf("status = %+v, want succeeded/subscription_ready", got) } } func TestImportBatchesRepoGetLatestByProviderID(t *testing.T) { store := openTestDB(t) hostID := createTestHost(t, store) packID := createTestPack(t, store) providerID := createTestProviderWithPack(t, store, packID) store.ImportBatches().Create(context.Background(), ImportBatch{ HostID: hostID, PackID: packID, ProviderID: providerID, Mode: "partial", BatchStatus: "running", AccessStatus: "pending", }) id2, _ := store.ImportBatches().Create(context.Background(), ImportBatch{ HostID: hostID, PackID: packID, ProviderID: providerID, Mode: "strict", BatchStatus: "succeeded", AccessStatus: "subscription_ready", }) got, _ := store.ImportBatches().GetLatestByProviderID(context.Background(), providerID) if got.ID != id2 { t.Fatalf("latest id = %d, want %d", got.ID, id2) } } func TestImportBatchesRepoListByProviderID(t *testing.T) { store := openTestDB(t) hostID := createTestHost(t, store) packID := createTestPack(t, store) providerID := createTestProviderWithPack(t, store, packID) olderID, err := store.ImportBatches().Create(context.Background(), ImportBatch{ HostID: hostID, PackID: packID, ProviderID: providerID, Mode: "partial", BatchStatus: "running", AccessStatus: "pending", }) if err != nil { t.Fatalf("Create older batch error = %v", err) } newerID, err := store.ImportBatches().Create(context.Background(), ImportBatch{ HostID: hostID, PackID: packID, ProviderID: providerID, Mode: "strict", BatchStatus: "succeeded", AccessStatus: "subscription_ready", }) if err != nil { t.Fatalf("Create newer batch error = %v", err) } batches, err := store.ImportBatches().ListByProviderID(context.Background(), providerID) if err != nil { t.Fatalf("ListByProviderID() error = %v", err) } if len(batches) != 2 { t.Fatalf("ListByProviderID() len = %d, want 2", len(batches)) } if batches[0].ID != newerID || batches[1].ID != olderID { t.Fatalf("ListByProviderID() ids = [%d %d], want [%d %d]", batches[0].ID, batches[1].ID, newerID, olderID) } } func TestImportBatchesRepoGetByIDNotFound(t *testing.T) { store := openTestDB(t) _, err := store.ImportBatches().GetByID(context.Background(), 999) if err == nil { t.Fatal("GetByID(999) error = nil, want error") } } func TestImportBatchesRepoCreateValidationErrors(t *testing.T) { store := openTestDB(t) for _, tt := range []struct { name string batch ImportBatch }{ {"host_id zero", ImportBatch{HostID: 0, PackID: 1, ProviderID: 1, Mode: "m", BatchStatus: "s", AccessStatus: "s"}}, {"pack_id zero", ImportBatch{HostID: 1, PackID: 0, ProviderID: 1, Mode: "m", BatchStatus: "s", AccessStatus: "s"}}, {"provider_id zero", ImportBatch{HostID: 1, PackID: 1, ProviderID: 0, Mode: "m", BatchStatus: "s", AccessStatus: "s"}}, {"empty mode", ImportBatch{HostID: 1, PackID: 1, ProviderID: 1, Mode: "", BatchStatus: "s", AccessStatus: "s"}}, {"empty batch_status", ImportBatch{HostID: 1, PackID: 1, ProviderID: 1, Mode: "m", BatchStatus: "", AccessStatus: "s"}}, {"empty access_status", ImportBatch{HostID: 1, PackID: 1, ProviderID: 1, Mode: "m", BatchStatus: "s", AccessStatus: ""}}, } { t.Run(tt.name, func(t *testing.T) { _, err := store.ImportBatches().Create(context.Background(), tt.batch) if err == nil { t.Fatal("Create() error = nil") } }) } } func TestImportBatchesRepoUpdateStatusValidation(t *testing.T) { store := openTestDB(t) if err := store.ImportBatches().UpdateStatus(context.Background(), 0, "s", "s"); err == nil { t.Fatal("UpdateStatus id=0 error = nil") } if err := store.ImportBatches().UpdateStatus(context.Background(), 1, "", "s"); err == nil { t.Fatal("UpdateStatus empty batch_status error = nil") } } // --- ImportBatchItems Repo Tests --- func TestImportBatchItemsRepoCreateAndGet(t *testing.T) { store := openTestDB(t) batchID := createTestBatch(t, store) id, err := store.ImportBatchItems().Create(context.Background(), ImportBatchItem{ BatchID: batchID, KeyFingerprint: "sha256:abc", AccountStatus: "passed", ProbeSummaryJSON: `{"ok":true}`, }) if err != nil { t.Fatalf("Create() error = %v", err) } if id <= 0 { t.Fatalf("Create() id = %d, want positive", id) } items, _ := store.ImportBatchItems().GetByBatchID(context.Background(), batchID) if len(items) != 1 || items[0].AccountStatus != "passed" { t.Fatalf("items = %+v, want 1 with passed status", items) } } func TestImportBatchItemsRepoMultipleItems(t *testing.T) { store := openTestDB(t) batchID := createTestBatch(t, store) for _, status := range []string{"passed", "failed"} { store.ImportBatchItems().Create(context.Background(), ImportBatchItem{ BatchID: batchID, KeyFingerprint: "sha256:" + status, AccountStatus: status, }) } items, _ := store.ImportBatchItems().GetByBatchID(context.Background(), batchID) if len(items) != 2 { t.Fatalf("count = %d, want 2", len(items)) } } func TestImportBatchItemsRepoUpdateResult(t *testing.T) { store := openTestDB(t) batchID := createTestBatch(t, store) itemID, _ := store.ImportBatchItems().Create(context.Background(), ImportBatchItem{ BatchID: batchID, KeyFingerprint: "sha256:x", AccountStatus: "pending", }) store.ImportBatchItems().UpdateResult(context.Background(), itemID, "passed", `{"ok":true}`) items, _ := store.ImportBatchItems().GetByBatchID(context.Background(), batchID) if items[0].AccountStatus != "passed" { t.Fatalf("AccountStatus = %q, want passed", items[0].AccountStatus) } } func TestImportBatchItemsRepoGetByBatchIDEmpty(t *testing.T) { store := openTestDB(t) items, err := store.ImportBatchItems().GetByBatchID(context.Background(), 999) if err != nil { t.Fatalf("GetByBatchID() error = %v, want empty result", err) } if len(items) != 0 { t.Fatalf("count = %d, want 0", len(items)) } } func TestImportBatchItemsRepoValidation(t *testing.T) { store := openTestDB(t) _, err := store.ImportBatchItems().Create(context.Background(), ImportBatchItem{ BatchID: 0, KeyFingerprint: "k", AccountStatus: "s", }) if err == nil { t.Fatal("Create batch_id=0 error = nil") } } // --- Managed Resources Repo Tests --- func TestManagedResourcesRepoCreateAndGet(t *testing.T) { store := openTestDB(t) batchID := createTestBatch(t, store) batch, _ := store.ImportBatches().GetByID(context.Background(), batchID) id, err := store.ManagedResources().Create(context.Background(), ManagedResource{ BatchID: batchID, HostID: batch.HostID, ResourceType: "group", HostResourceID: "g_01", ResourceName: "test-group", }) if err != nil { t.Fatalf("Create() error = %v", err) } resources, _ := store.ManagedResources().GetByBatchID(context.Background(), batchID) if len(resources) != 1 || resources[0].HostResourceID != "g_01" { t.Fatalf("resources = %+v, want 1 with g_01", resources) } _ = id } func TestManagedResourcesRepoMultipleResources(t *testing.T) { store := openTestDB(t) batchID := createTestBatch(t, store) batch, _ := store.ImportBatches().GetByID(context.Background(), batchID) for _, r := range []ManagedResource{ {BatchID: batchID, HostID: batch.HostID, ResourceType: "group", HostResourceID: "g_01", ResourceName: "group-1"}, {BatchID: batchID, HostID: batch.HostID, ResourceType: "channel", HostResourceID: "c_01", ResourceName: "channel-1"}, {BatchID: batchID, HostID: batch.HostID, ResourceType: "account", HostResourceID: "a_01", ResourceName: "account-1"}, } { store.ManagedResources().Create(context.Background(), r) } resources, _ := store.ManagedResources().GetByBatchID(context.Background(), batchID) if len(resources) != 3 { t.Fatalf("count = %d, want 3", len(resources)) } } func TestManagedResourcesRepoGetByBatchIDEmpty(t *testing.T) { store := openTestDB(t) resources, _ := store.ManagedResources().GetByBatchID(context.Background(), 999) if len(resources) != 0 { t.Fatalf("count = %d, want 0", len(resources)) } } func TestManagedResourcesRepoValidationErrors(t *testing.T) { store := openTestDB(t) for _, tt := range []struct { name string r ManagedResource }{ {"batch_id zero", ManagedResource{HostID: 1, ResourceType: "g", HostResourceID: "h", ResourceName: "n"}}, {"host_id zero", ManagedResource{BatchID: 1, ResourceType: "g", HostResourceID: "h", ResourceName: "n"}}, {"empty resource_type", ManagedResource{BatchID: 1, HostID: 1, HostResourceID: "h", ResourceName: "n"}}, {"empty host_resource_id", ManagedResource{BatchID: 1, HostID: 1, ResourceType: "g", ResourceName: "n"}}, {"empty resource_name", ManagedResource{BatchID: 1, HostID: 1, ResourceType: "g", HostResourceID: "h"}}, } { t.Run(tt.name, func(t *testing.T) { _, err := store.ManagedResources().Create(context.Background(), tt.r) if err == nil { t.Fatal("Create() error = nil") } }) } } // --- Probe Results Repo Tests --- func TestProbeResultsRepoCreateAndGet(t *testing.T) { store := openTestDB(t) batchID := createTestBatch(t, store) itemID := createTestBatchItem(t, store, batchID) id, err := store.ProbeResults().Create(context.Background(), ProbeResult{ BatchItemID: itemID, ProbeType: "account_smoke", Status: "passed", SummaryJSON: `{"ok":true}`, }) if err != nil { t.Fatalf("Create() error = %v", err) } results, _ := store.ProbeResults().GetByBatchItemID(context.Background(), itemID) if len(results) != 1 || results[0].ProbeType != "account_smoke" { t.Fatalf("results = %+v, want 1 with account_smoke", results) } _ = id } func TestProbeResultsRepoMultipleResults(t *testing.T) { store := openTestDB(t) batchID := createTestBatch(t, store) itemID := createTestBatchItem(t, store, batchID) for _, p := range []ProbeResult{ {BatchItemID: itemID, ProbeType: "account_smoke", Status: "passed", SummaryJSON: `{"ok":true}`}, {BatchItemID: itemID, ProbeType: "model_list", Status: "passed", SummaryJSON: `{"models":["m1"]}`}, } { store.ProbeResults().Create(context.Background(), p) } results, _ := store.ProbeResults().GetByBatchItemID(context.Background(), itemID) if len(results) != 2 { t.Fatalf("count = %d, want 2", len(results)) } } func TestProbeResultsRepoGetByBatchItemIDEmpty(t *testing.T) { store := openTestDB(t) results, _ := store.ProbeResults().GetByBatchItemID(context.Background(), 999) if len(results) != 0 { t.Fatalf("count = %d, want 0", len(results)) } } func TestProbeResultsRepoValidationErrors(t *testing.T) { store := openTestDB(t) for _, tt := range []struct { name string probe ProbeResult }{ {"batch_item_id zero", ProbeResult{ProbeType: "t", Status: "s"}}, {"empty probe_type", ProbeResult{BatchItemID: 1, Status: "s"}}, {"empty status", ProbeResult{BatchItemID: 1, ProbeType: "t"}}, } { t.Run(tt.name, func(t *testing.T) { _, err := store.ProbeResults().Create(context.Background(), tt.probe) if err == nil { t.Fatal("Create() error = nil") } }) } } // --- Access Closures Repo Tests --- func TestAccessClosureRecordsRepoCreateAndGet(t *testing.T) { store := openTestDB(t) batchID := createTestBatch(t, store) id, err := store.AccessClosures().Create(context.Background(), AccessClosureRecord{ BatchID: batchID, ClosureType: "subscription", Status: "subscription_ready", DetailsJSON: `{"status_code":200}`, }) if err != nil { t.Fatalf("Create() error = %v", err) } records, _ := store.AccessClosures().GetByBatchID(context.Background(), batchID) if len(records) != 1 || records[0].ClosureType != "subscription" { t.Fatalf("records = %+v, want 1 subscription", records) } _ = id } func TestAccessClosureRecordsRepoMultiple(t *testing.T) { store := openTestDB(t) batchID := createTestBatch(t, store) store.AccessClosures().Create(context.Background(), AccessClosureRecord{BatchID: batchID, ClosureType: "subscription", Status: "subscription_ready", DetailsJSON: "{}"}) store.AccessClosures().Create(context.Background(), AccessClosureRecord{BatchID: batchID, ClosureType: "self_service", Status: "self_service_ready", DetailsJSON: "{}"}) records, _ := store.AccessClosures().GetByBatchID(context.Background(), batchID) if len(records) != 2 { t.Fatalf("count = %d, want 2", len(records)) } } func TestAccessClosureRecordsRepoGetByBatchIDEmpty(t *testing.T) { store := openTestDB(t) records, _ := store.AccessClosures().GetByBatchID(context.Background(), 999) if len(records) != 0 { t.Fatalf("count = %d, want 0", len(records)) } } func TestAccessClosureRecordsRepoValidation(t *testing.T) { store := openTestDB(t) _, err := store.AccessClosures().Create(context.Background(), AccessClosureRecord{BatchID: 0, ClosureType: "t", Status: "s"}) if err == nil { t.Fatal("Create batch_id=0 error = nil") } } // --- Reconcile Runs Repo Tests --- func createTestProviderWithPack(t *testing.T, store *DB, packID int64) int64 { t.Helper() id, err := store.Providers().Create(context.Background(), Provider{ PackID: packID, ProviderID: "test-provider-" + sanitizeTestName(t.Name()), DisplayName: "TP", BaseURL: "https://tp.com", Platform: "openai", }) if err != nil { t.Fatalf("createTestProviderWithPack error = %v", err) } return id } func createTestProvider(t *testing.T, store *DB) int64 { t.Helper() packID := createTestPack(t, store) return createTestProviderWithPack(t, store, packID) } func TestReconcileRunsRepoCreateAndGet(t *testing.T) { store := openTestDB(t) batchID := createTestBatch(t, store) batch, err := store.ImportBatches().GetByID(context.Background(), batchID) if err != nil { t.Fatalf("ImportBatches().GetByID() error = %v", err) } id, err := store.ReconcileRuns().Create(context.Background(), ReconcileRun{ BatchID: batchID, HostID: batch.HostID, ProviderID: batch.ProviderID, Status: "active", SummaryJSON: `{"drifted":false}`, }) if err != nil { t.Fatalf("Create() error = %v", err) } runs, _ := store.ReconcileRuns().GetByBatchID(context.Background(), batchID) if len(runs) != 1 || runs[0].Status != "active" { t.Fatalf("runs = %+v, want 1 active", runs) } _ = id } func TestReconcileRunsRepoMultipleRunsOrderedDesc(t *testing.T) { store := openTestDB(t) batchID := createTestBatch(t, store) batch, err := store.ImportBatches().GetByID(context.Background(), batchID) if err != nil { t.Fatalf("ImportBatches().GetByID() error = %v", err) } id1, _ := store.ReconcileRuns().Create(context.Background(), ReconcileRun{BatchID: batchID, HostID: batch.HostID, ProviderID: batch.ProviderID, Status: "first", SummaryJSON: "{}"}) id2, _ := store.ReconcileRuns().Create(context.Background(), ReconcileRun{BatchID: batchID, HostID: batch.HostID, ProviderID: batch.ProviderID, Status: "second", SummaryJSON: "{}"}) runs, _ := store.ReconcileRuns().GetByBatchID(context.Background(), batchID) if len(runs) != 2 || runs[0].ID != id2 || runs[1].ID != id1 { t.Fatalf("order: got %d, %d; want %d, %d (DESC)", runs[0].ID, runs[1].ID, id2, id1) } } func TestReconcileRunsRepoSeparatesHosts(t *testing.T) { store := openTestDB(t) hostA := createTestHost(t, store) hostB := createTestHostWithBaseURL(t, store, "host-b", "https://host-b.example.com") packID := createTestPack(t, store) providerID := createTestProviderWithPack(t, store, packID) batchA, err := store.ImportBatches().Create(context.Background(), ImportBatch{HostID: hostA, PackID: packID, ProviderID: providerID, Mode: "partial", BatchStatus: "running", AccessStatus: "pending"}) if err != nil { t.Fatalf("ImportBatches().Create(hostA) error = %v", err) } batchB, err := store.ImportBatches().Create(context.Background(), ImportBatch{HostID: hostB, PackID: packID, ProviderID: providerID, Mode: "partial", BatchStatus: "running", AccessStatus: "pending"}) if err != nil { t.Fatalf("ImportBatches().Create(hostB) error = %v", err) } if _, err := store.ReconcileRuns().Create(context.Background(), ReconcileRun{BatchID: batchA, HostID: hostA, ProviderID: providerID, Status: "drifted", SummaryJSON: `{"host":"a"}`}); err != nil { t.Fatalf("Create(hostA) error = %v", err) } if _, err := store.ReconcileRuns().Create(context.Background(), ReconcileRun{BatchID: batchB, HostID: hostB, ProviderID: providerID, Status: "active", SummaryJSON: `{"host":"b"}`}); err != nil { t.Fatalf("Create(hostB) error = %v", err) } runs, _ := store.ReconcileRuns().GetByProviderIDAndHostID(context.Background(), providerID, hostA) if len(runs) != 1 { t.Fatalf("len(runs) = %d, want 1", len(runs)) } if runs[0].Status != "drifted" { t.Fatalf("runs[0].Status = %q, want drifted", runs[0].Status) } } func TestReconcileRunsRepoGetByProviderIDAndHostIDEmpty(t *testing.T) { store := openTestDB(t) runs, _ := store.ReconcileRuns().GetByProviderIDAndHostID(context.Background(), 999, 1) if len(runs) != 0 { t.Fatalf("count = %d, want 0", len(runs)) } } func TestReconcileRunsRepoGetByBatchIDEmpty(t *testing.T) { store := openTestDB(t) runs, _ := store.ReconcileRuns().GetByBatchID(context.Background(), 999) if len(runs) != 0 { t.Fatalf("count = %d, want 0", len(runs)) } } func TestReconcileRunsRepoValidation(t *testing.T) { store := openTestDB(t) hostID := createTestHost(t, store) _, err := store.ReconcileRuns().Create(context.Background(), ReconcileRun{BatchID: 1, HostID: hostID, ProviderID: 0, Status: "s"}) if err == nil { t.Fatal("Create provider_id=0 error = nil") } }