diff --git a/internal/routing/sticky_test.go b/internal/routing/sticky_test.go index abaefb53..a79c9553 100644 --- a/internal/routing/sticky_test.go +++ b/internal/routing/sticky_test.go @@ -457,6 +457,44 @@ func TestRedisStickyStoreOpenRejectsAuthError(t *testing.T) { } } +func TestRedisStickyStoreOpenRejectsAuthUnexpectedResponse(t *testing.T) { + t.Parallel() + + server := newScriptedRedisServer(t, func(conn net.Conn, reader *bufio.Reader) { + command, err := readRESPArray(reader) + if err != nil { + return + } + if len(command) > 0 && strings.ToUpper(command[0]) == "AUTH" { + _, _ = io.WriteString(conn, ":1\r\n") + } + }) + defer server.Close() + + store := &RedisStickyStore{cfg: RedisConfig{Addr: server.Addr(), Password: "secret"}} + _, _, err := store.open(context.Background()) + if err == nil || !strings.Contains(err.Error(), "redis AUTH unexpected response") { + t.Fatalf("open() error = %v, want auth unexpected response", err) + } +} + +func TestRedisStickyStoreOpenRejectsAuthReadFailure(t *testing.T) { + t.Parallel() + + server := newScriptedRedisServer(t, func(conn net.Conn, reader *bufio.Reader) { + if _, err := readRESPArray(reader); err != nil { + return + } + }) + defer server.Close() + + store := &RedisStickyStore{cfg: RedisConfig{Addr: server.Addr(), Password: "secret"}} + _, _, err := store.open(context.Background()) + if err == nil || !strings.Contains(err.Error(), "redis AUTH read") { + t.Fatalf("open() error = %v, want auth read failure", err) + } +} + func TestRedisStickyStoreOpenRejectsSelectUnexpectedResponse(t *testing.T) { t.Parallel() @@ -485,6 +523,30 @@ func TestRedisStickyStoreOpenRejectsSelectUnexpectedResponse(t *testing.T) { } } +func TestRedisStickyStoreOpenRejectsSelectReadFailure(t *testing.T) { + t.Parallel() + + server := newScriptedRedisServer(t, func(conn net.Conn, reader *bufio.Reader) { + command, err := readRESPArray(reader) + if err != nil { + return + } + if len(command) > 0 && strings.ToUpper(command[0]) == "AUTH" { + _, _ = io.WriteString(conn, "+OK\r\n") + } + if _, err := readRESPArray(reader); err != nil { + return + } + }) + defer server.Close() + + store := &RedisStickyStore{cfg: RedisConfig{Addr: server.Addr(), Password: "secret", DB: 2}} + _, _, err := store.open(context.Background()) + if err == nil || !strings.Contains(err.Error(), "redis SELECT read") { + t.Fatalf("open() error = %v, want select read failure", err) + } +} + func TestRedisStickyStoreGetJSONRejectsUnexpectedResponse(t *testing.T) { t.Parallel() diff --git a/internal/store/sqlite/import_runs_repo_test.go b/internal/store/sqlite/import_runs_repo_test.go index 43125282..654bf811 100644 --- a/internal/store/sqlite/import_runs_repo_test.go +++ b/internal/store/sqlite/import_runs_repo_test.go @@ -363,6 +363,41 @@ func TestImportRunItemsRepoUpsertDefaultsOptionalJSONAndNullableFields(t *testin } } +func TestImportRunItemsRepoUpsertValidationErrors(t *testing.T) { + t.Parallel() + + ctx := context.Background() + store := openTestDB(t) + run := ImportRun{RunID: "run-upsert-validation", HostID: "host-upsert-validation", Mode: "strict", AccessMode: "subscription", State: "running"} + if err := store.ImportRuns().Create(ctx, run); err != nil { + t.Fatalf("ImportRuns().Create() error = %v", err) + } + + tests := []struct { + name string + item ImportRunItem + }{ + {name: "missing item_id", item: ImportRunItem{RunID: "run-upsert-validation", BaseURL: "https://api.example.com/v1", ProviderID: "provider", APIKeyFingerprint: "fp", CurrentStage: "confirm", ConfirmationStatus: "pending", AccessStatus: "unknown", MatchedAccountState: "active", AccountResolution: "created"}}, + {name: "missing run_id", item: ImportRunItem{ItemID: "item", BaseURL: "https://api.example.com/v1", ProviderID: "provider", APIKeyFingerprint: "fp", CurrentStage: "confirm", ConfirmationStatus: "pending", AccessStatus: "unknown", MatchedAccountState: "active", AccountResolution: "created"}}, + {name: "missing base_url", item: ImportRunItem{ItemID: "item", RunID: "run-upsert-validation", ProviderID: "provider", APIKeyFingerprint: "fp", CurrentStage: "confirm", ConfirmationStatus: "pending", AccessStatus: "unknown", MatchedAccountState: "active", AccountResolution: "created"}}, + {name: "missing provider_id", item: ImportRunItem{ItemID: "item", RunID: "run-upsert-validation", BaseURL: "https://api.example.com/v1", APIKeyFingerprint: "fp", CurrentStage: "confirm", ConfirmationStatus: "pending", AccessStatus: "unknown", MatchedAccountState: "active", AccountResolution: "created"}}, + {name: "missing fingerprint", item: ImportRunItem{ItemID: "item", RunID: "run-upsert-validation", BaseURL: "https://api.example.com/v1", ProviderID: "provider", CurrentStage: "confirm", ConfirmationStatus: "pending", AccessStatus: "unknown", MatchedAccountState: "active", AccountResolution: "created"}}, + {name: "missing current_stage", item: ImportRunItem{ItemID: "item", RunID: "run-upsert-validation", BaseURL: "https://api.example.com/v1", ProviderID: "provider", APIKeyFingerprint: "fp", ConfirmationStatus: "pending", AccessStatus: "unknown", MatchedAccountState: "active", AccountResolution: "created"}}, + {name: "missing confirmation_status", item: ImportRunItem{ItemID: "item", RunID: "run-upsert-validation", BaseURL: "https://api.example.com/v1", ProviderID: "provider", APIKeyFingerprint: "fp", CurrentStage: "confirm", AccessStatus: "unknown", MatchedAccountState: "active", AccountResolution: "created"}}, + {name: "missing access_status", item: ImportRunItem{ItemID: "item", RunID: "run-upsert-validation", BaseURL: "https://api.example.com/v1", ProviderID: "provider", APIKeyFingerprint: "fp", CurrentStage: "confirm", ConfirmationStatus: "pending", MatchedAccountState: "active", AccountResolution: "created"}}, + {name: "missing matched_account_state", item: ImportRunItem{ItemID: "item", RunID: "run-upsert-validation", BaseURL: "https://api.example.com/v1", ProviderID: "provider", APIKeyFingerprint: "fp", CurrentStage: "confirm", ConfirmationStatus: "pending", AccessStatus: "unknown", AccountResolution: "created"}}, + {name: "missing account_resolution", item: ImportRunItem{ItemID: "item", RunID: "run-upsert-validation", BaseURL: "https://api.example.com/v1", ProviderID: "provider", APIKeyFingerprint: "fp", CurrentStage: "confirm", ConfirmationStatus: "pending", AccessStatus: "unknown", MatchedAccountState: "active"}}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := store.ImportRunItems().Upsert(ctx, tt.item); err == nil { + t.Fatal("Upsert() error = nil, want validation error") + } + }) + } +} + func TestImportRunEventsRepoCreateAndHelpers(t *testing.T) { t.Parallel() diff --git a/internal/store/sqlite/packs_repo_test.go b/internal/store/sqlite/packs_repo_test.go index 386d79e2..6bcf17a3 100644 --- a/internal/store/sqlite/packs_repo_test.go +++ b/internal/store/sqlite/packs_repo_test.go @@ -180,6 +180,27 @@ func TestPacksRepoValidationErrors(t *testing.T) { } } +func TestPacksRepoUpsertValidationErrors(t *testing.T) { + store := openTestDB(t) + + tests := []struct { + name string + pack Pack + }{ + {"empty pack_id", Pack{Version: "v", Checksum: "c"}}, + {"empty version", Pack{PackID: "p", Checksum: "c"}}, + {"empty checksum", Pack{PackID: "p", Version: "v"}}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + _, err := store.Packs().Upsert(context.Background(), tt.pack) + if err == nil { + t.Fatal("Upsert() error = nil, want validation error") + } + }) + } +} + func TestPacksRepoGetByIDNotFound(t *testing.T) { store := openTestDB(t) _, err := store.Packs().GetByID(context.Background(), 999) diff --git a/internal/store/sqlite/providers_repo_test.go b/internal/store/sqlite/providers_repo_test.go index 2c5223d3..229309b5 100644 --- a/internal/store/sqlite/providers_repo_test.go +++ b/internal/store/sqlite/providers_repo_test.go @@ -253,6 +253,30 @@ func TestProvidersRepoValidationErrors(t *testing.T) { } } +func TestProvidersRepoUpsertValidationErrors(t *testing.T) { + store := openTestDB(t) + packID := createTestPack(t, store) + + tests := []struct { + name string + provider Provider + }{ + {"pack_id zero", Provider{ProviderID: "p", DisplayName: "d", BaseURL: "b", Platform: "openai"}}, + {"empty provider_id", Provider{PackID: packID, DisplayName: "d", BaseURL: "b", Platform: "openai"}}, + {"empty display_name", Provider{PackID: packID, ProviderID: "p", BaseURL: "b", Platform: "openai"}}, + {"empty base_url", Provider{PackID: packID, ProviderID: "p", DisplayName: "d", Platform: "openai"}}, + {"empty platform", Provider{PackID: packID, ProviderID: "p", DisplayName: "d", BaseURL: "b"}}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + _, err := store.Providers().Upsert(context.Background(), tt.provider) + if err == nil { + t.Fatal("Upsert() error = nil, want validation error") + } + }) + } +} + func TestProvidersRepoGetByPackIDAndProviderIDNotFound(t *testing.T) { store := openTestDB(t) _, err := store.Providers().GetByPackIDAndProviderID(context.Background(), 999, "p")