diff --git a/internal/overlay/executor_test.go b/internal/overlay/executor_test.go index 31d8e695..b0beb771 100644 --- a/internal/overlay/executor_test.go +++ b/internal/overlay/executor_test.go @@ -151,6 +151,19 @@ func TestApplyPatchFileRejectsMissingPatch(t *testing.T) { } } +func TestApplyPatchFileRejectsInvalidPatch(t *testing.T) { + outputDir := t.TempDir() + patchPath := filepath.Join(t.TempDir(), "invalid.patch") + if err := os.WriteFile(patchPath, []byte("not a patch\n"), 0o644); err != nil { + t.Fatalf("WriteFile() error = %v", err) + } + + err := applyPatchFile(context.Background(), outputDir, patchPath) + if err == nil { + t.Fatal("applyPatchFile() error = nil, want invalid patch failure") + } +} + func TestWriteMetadataIncludesSourceDirAndOverlays(t *testing.T) { metadataPath := filepath.Join(t.TempDir(), metadataFileName) overlays := []pack.HostOverlay{{OverlayID: "sample", PatchPath: "overlays/sample.patch"}} @@ -208,3 +221,31 @@ func TestCopyTreeSkipsGitAndPreservesSymlink(t *testing.T) { t.Fatalf("symlink target = %q, want backend/hello.txt", target) } } + +func TestApplyRejectsExistingOutputDir(t *testing.T) { + sourceDir := t.TempDir() + if err := os.MkdirAll(filepath.Join(sourceDir, "backend"), 0o755); err != nil { + t.Fatalf("MkdirAll() error = %v", err) + } + outputDir := filepath.Join(t.TempDir(), "existing-output") + if err := os.MkdirAll(outputDir, 0o755); err != nil { + t.Fatalf("MkdirAll(outputDir) error = %v", err) + } + + _, err := Apply(context.Background(), ApplyRequest{ + PackDir: t.TempDir(), + SourceDir: sourceDir, + OutputDir: outputDir, + Overlays: []pack.HostOverlay{{OverlayID: "sample", PatchPath: "overlays/sample.patch"}}, + }) + if err == nil || !strings.Contains(err.Error(), "already exists") { + t.Fatalf("Apply() error = %v, want existing output rejection", err) + } +} + +func TestCopyFileRejectsMissingSource(t *testing.T) { + err := copyFile(filepath.Join(t.TempDir(), "missing.txt"), filepath.Join(t.TempDir(), "target.txt"), 0o644) + if err == nil { + t.Fatal("copyFile() error = nil, want missing source failure") + } +} diff --git a/internal/routing/logwriter_test.go b/internal/routing/logwriter_test.go index ccd6334b..45becab1 100644 --- a/internal/routing/logwriter_test.go +++ b/internal/routing/logwriter_test.go @@ -162,6 +162,22 @@ func TestAsyncLogWriterFlushesQueuedEvents(t *testing.T) { } } +func TestAsyncLogWriterFlushAfterCloseReturnsNil(t *testing.T) { + t.Parallel() + + writer := NewAsyncLogWriter(&recordingRouteLogSink{}, AsyncLogWriterOptions{ + QueueSize: 1, + FlushInterval: time.Hour, + MaxBatchSize: 1, + }) + if err := writer.Close(); err != nil { + t.Fatalf("Close() error = %v", err) + } + if err := writer.Flush(context.Background()); err != nil { + t.Fatalf("Flush() after close error = %v, want nil", err) + } +} + type failingRouteLogSink struct { appendCalls int } diff --git a/internal/routing/sticky_test.go b/internal/routing/sticky_test.go index 28fb8c78..081c2b33 100644 --- a/internal/routing/sticky_test.go +++ b/internal/routing/sticky_test.go @@ -181,6 +181,18 @@ func TestRedisStickyStoreRoundTripWithFakeServer(t *testing.T) { if state, ok, err := store.GetCooldown(ctx, "asxs"); err != nil || !ok || state.Reason != "degraded" { t.Fatalf("GetCooldown() = (%+v, %v, %v), want reason degraded", state, ok, err) } + if err := store.ClearRouteFailure(ctx, "asxs"); err != nil { + t.Fatalf("ClearRouteFailure() error = %v", err) + } + if _, ok, err := store.GetRouteFailure(ctx, "asxs"); err != nil || ok { + t.Fatalf("GetRouteFailure() after clear = (ok=%v, err=%v), want false nil", ok, err) + } + if err := store.ClearCooldown(ctx, "asxs"); err != nil { + t.Fatalf("ClearCooldown() error = %v", err) + } + if _, ok, err := store.GetCooldown(ctx, "asxs"); err != nil || ok { + t.Fatalf("GetCooldown() after clear = (ok=%v, err=%v), want false nil", ok, err) + } if err := store.Delete(ctx, key); err != nil { t.Fatalf("Delete() error = %v", err) } diff --git a/internal/store/sqlite/provider_accounts_repo_test.go b/internal/store/sqlite/provider_accounts_repo_test.go index 7d277e56..0a0b481c 100644 --- a/internal/store/sqlite/provider_accounts_repo_test.go +++ b/internal/store/sqlite/provider_accounts_repo_test.go @@ -561,3 +561,86 @@ func TestSyncProviderAccountsFromImportBatchPromotesSingleReadyGatewayAccount(t t.Fatalf("LastProbeStatus = %q, want gateway_ready", account.LastProbeStatus) } } + +func TestSyncProviderAccountsFromLatestImportBatchesSyncsEachLatestReconcilableBatch(t *testing.T) { + t.Parallel() + + store := openTestDBWithFK(t) + ctx := context.Background() + hostID := createTestHost(t, store) + packID := createTestPack(t, store) + providerAID, err := store.Providers().Create(ctx, Provider{ + PackID: packID, + ProviderID: "provider-a", + DisplayName: "Provider A", + BaseURL: "https://api.provider-a.example/v1", + Platform: "openai", + }) + if err != nil { + t.Fatalf("Providers().Create(provider-a) error = %v", err) + } + providerBID, err := store.Providers().Create(ctx, Provider{ + PackID: packID, + ProviderID: "provider-b", + DisplayName: "Provider B", + BaseURL: "https://api.provider-b.example/v1", + Platform: "openai", + }) + if err != nil { + t.Fatalf("Providers().Create(provider-b) error = %v", err) + } + + createBatchWithAccount := func(providerID int64, accountID, keyFingerprint string) { + t.Helper() + batchID, err := store.ImportBatches().Create(ctx, ImportBatch{ + HostID: hostID, + PackID: packID, + ProviderID: providerID, + Mode: "strict", + BatchStatus: "succeeded", + AccessStatus: "subscription_ready", + }) + if err != nil { + t.Fatalf("ImportBatches().Create(%s) error = %v", accountID, err) + } + if _, err := store.ImportBatchItems().Create(ctx, ImportBatchItem{ + BatchID: batchID, + KeyFingerprint: keyFingerprint, + AccountStatus: "passed", + ProbeSummaryJSON: `{"account_id":"` + accountID + `","probe_status":"passed"}`, + }); err != nil { + t.Fatalf("ImportBatchItems().Create(%s) error = %v", accountID, err) + } + for _, resource := range []ManagedResource{ + {BatchID: batchID, HostID: hostID, ResourceType: "group", HostResourceID: "group-" + accountID, ResourceName: "Group " + accountID}, + {BatchID: batchID, HostID: hostID, ResourceType: "account", HostResourceID: accountID, ResourceName: "Account " + accountID}, + } { + if _, err := store.ManagedResources().Create(ctx, resource); err != nil { + t.Fatalf("ManagedResources().Create(%s/%s) error = %v", accountID, resource.ResourceType, err) + } + } + } + + createBatchWithAccount(providerAID, "account-a1", "sha256:a1") + createBatchWithAccount(providerBID, "account-b1", "sha256:b1") + + if err := SyncProviderAccountsFromLatestImportBatches(ctx, store); err != nil { + t.Fatalf("SyncProviderAccountsFromLatestImportBatches() error = %v", err) + } + + accountA, err := store.ProviderAccounts().GetByHostIDAndAccountID(ctx, hostID, "account-a1") + if err != nil { + t.Fatalf("ProviderAccounts().GetByHostIDAndAccountID(account-a1) error = %v", err) + } + if accountA.AccountStatus != ProviderAccountStatusActive || accountA.KeyFingerprint != "sha256:a1" { + t.Fatalf("account-a1 = %+v, want active sha256:a1", accountA) + } + + accountB, err := store.ProviderAccounts().GetByHostIDAndAccountID(ctx, hostID, "account-b1") + if err != nil { + t.Fatalf("ProviderAccounts().GetByHostIDAndAccountID(account-b1) error = %v", err) + } + if accountB.AccountStatus != ProviderAccountStatusActive || accountB.KeyFingerprint != "sha256:b1" { + t.Fatalf("account-b1 = %+v, want active sha256:b1", accountB) + } +}