diff --git a/internal/store/sqlite/packs_repo_test.go b/internal/store/sqlite/packs_repo_test.go index 6bcf17a3..d4a76081 100644 --- a/internal/store/sqlite/packs_repo_test.go +++ b/internal/store/sqlite/packs_repo_test.go @@ -4,6 +4,7 @@ import ( "context" "database/sql" "errors" + "strings" "testing" ) @@ -159,6 +160,87 @@ func TestPacksRepoUpsertTrimsAndDefaultsManifest(t *testing.T) { } } +func TestPacksRepoUpsertFallsBackWhenLastInsertIDUnavailable(t *testing.T) { + store := openTestDB(t) + ctx := context.Background() + db := store.SQLDB() + repo := newPacksRepo(execQuerierStub{ + execFn: func(ctx context.Context, query string, args ...any) (sql.Result, error) { + if _, err := db.ExecContext(ctx, query, args...); err != nil { + return nil, err + } + return resultStub{lastInsertErr: errors.New("last insert unavailable")}, nil + }, + queryFn: db.QueryContext, + queryRowFn: db.QueryRowContext, + }) + + id, err := repo.Upsert(ctx, Pack{ + PackID: "fallback-pack", + Version: "1.0.0", + Checksum: "fallback-checksum", + }) + if err != nil { + t.Fatalf("Upsert() error = %v", err) + } + if id <= 0 { + t.Fatalf("Upsert() id = %d, want positive", id) + } + got, err := store.Packs().GetByPackID(ctx, "fallback-pack") + if err != nil { + t.Fatalf("GetByPackID() error = %v", err) + } + if got.ID != id { + t.Fatalf("fallback id = %d, want persisted id %d", id, got.ID) + } +} + +func TestPacksRepoUpsertReturnsExecError(t *testing.T) { + ctx := context.Background() + repo := newPacksRepo(execQuerierStub{ + execFn: func(context.Context, string, ...any) (sql.Result, error) { + return nil, errors.New("exec boom") + }, + queryFn: func(context.Context, string, ...any) (*sql.Rows, error) { + return nil, errors.New("unexpected query") + }, + queryRowFn: func(context.Context, string, ...any) *sql.Row { + panic("unexpected QueryRowContext") + }, + }) + + _, err := repo.Upsert(ctx, Pack{ + PackID: "pack-exec-error", + Version: "1.0.0", + Checksum: "checksum", + }) + if err == nil || !strings.Contains(err.Error(), "exec boom") { + t.Fatalf("Upsert() error = %v, want exec boom", err) + } +} + +func TestPacksRepoUpsertReturnsFallbackReadError(t *testing.T) { + store := openTestDB(t) + ctx := context.Background() + db := store.SQLDB() + repo := newPacksRepo(execQuerierStub{ + execFn: func(context.Context, string, ...any) (sql.Result, error) { + return resultStub{lastInsertErr: errors.New("last insert unavailable")}, nil + }, + queryFn: db.QueryContext, + queryRowFn: db.QueryRowContext, + }) + + _, err := repo.Upsert(ctx, Pack{ + PackID: "pack-missing-after-upsert", + Version: "1.0.0", + Checksum: "checksum", + }) + if !errors.Is(err, sql.ErrNoRows) { + t.Fatalf("Upsert() error = %v, want sql.ErrNoRows fallback read error", err) + } +} + func TestPacksRepoValidationErrors(t *testing.T) { store := openTestDB(t) diff --git a/internal/store/sqlite/providers_repo_test.go b/internal/store/sqlite/providers_repo_test.go index 229309b5..c2603abf 100644 --- a/internal/store/sqlite/providers_repo_test.go +++ b/internal/store/sqlite/providers_repo_test.go @@ -4,9 +4,44 @@ import ( "context" "database/sql" "errors" + "strings" "testing" ) +type resultStub struct { + lastInsertID int64 + lastInsertErr error +} + +func (r resultStub) LastInsertId() (int64, error) { + if r.lastInsertErr != nil { + return 0, r.lastInsertErr + } + return r.lastInsertID, nil +} + +func (r resultStub) RowsAffected() (int64, error) { + return 1, nil +} + +type execQuerierStub struct { + execFn func(context.Context, string, ...any) (sql.Result, error) + queryFn func(context.Context, string, ...any) (*sql.Rows, error) + queryRowFn func(context.Context, string, ...any) *sql.Row +} + +func (s execQuerierStub) ExecContext(ctx context.Context, query string, args ...any) (sql.Result, error) { + return s.execFn(ctx, query, args...) +} + +func (s execQuerierStub) QueryContext(ctx context.Context, query string, args ...any) (*sql.Rows, error) { + return s.queryFn(ctx, query, args...) +} + +func (s execQuerierStub) QueryRowContext(ctx context.Context, query string, args ...any) *sql.Row { + return s.queryRowFn(ctx, query, args...) +} + func TestProvidersRepoCreateAndGet(t *testing.T) { store := openTestDB(t) @@ -229,6 +264,95 @@ func TestProvidersRepoUpsertTrimsAndDefaultsOptionalJSON(t *testing.T) { } } +func TestProvidersRepoUpsertFallsBackWhenLastInsertIDUnavailable(t *testing.T) { + store := openTestDB(t) + packID := createTestPack(t, store) + ctx := context.Background() + + db := store.SQLDB() + repo := newProvidersRepo(execQuerierStub{ + execFn: func(ctx context.Context, query string, args ...any) (sql.Result, error) { + if _, err := db.ExecContext(ctx, query, args...); err != nil { + return nil, err + } + return resultStub{lastInsertErr: errors.New("last insert unavailable")}, nil + }, + queryFn: db.QueryContext, + queryRowFn: db.QueryRowContext, + }) + + id, err := repo.Upsert(ctx, Provider{ + PackID: packID, + ProviderID: "fallback-provider", + DisplayName: "Fallback Provider", + BaseURL: "https://fallback.example.com/v1", + Platform: "openai", + }) + if err != nil { + t.Fatalf("Upsert() error = %v", err) + } + if id <= 0 { + t.Fatalf("Upsert() id = %d, want positive", id) + } + got, err := store.Providers().GetByPackIDAndProviderID(ctx, packID, "fallback-provider") + if err != nil { + t.Fatalf("GetByPackIDAndProviderID() error = %v", err) + } + if got.ID != id { + t.Fatalf("fallback id = %d, want persisted id %d", id, got.ID) + } +} + +func TestProvidersRepoUpsertReturnsExecError(t *testing.T) { + ctx := context.Background() + repo := newProvidersRepo(execQuerierStub{ + execFn: func(context.Context, string, ...any) (sql.Result, error) { + return nil, errors.New("exec boom") + }, + queryFn: func(context.Context, string, ...any) (*sql.Rows, error) { + return nil, errors.New("unexpected query") + }, + queryRowFn: func(context.Context, string, ...any) *sql.Row { + panic("unexpected QueryRowContext") + }, + }) + + _, err := repo.Upsert(ctx, Provider{ + PackID: 1, + ProviderID: "provider-exec-error", + DisplayName: "Provider Exec Error", + BaseURL: "https://exec-error.example.com/v1", + Platform: "openai", + }) + if err == nil || !strings.Contains(err.Error(), "exec boom") { + t.Fatalf("Upsert() error = %v, want exec boom", err) + } +} + +func TestProvidersRepoUpsertReturnsFallbackReadError(t *testing.T) { + store := openTestDB(t) + ctx := context.Background() + db := store.SQLDB() + repo := newProvidersRepo(execQuerierStub{ + execFn: func(context.Context, string, ...any) (sql.Result, error) { + return resultStub{lastInsertErr: errors.New("last insert unavailable")}, nil + }, + queryFn: db.QueryContext, + queryRowFn: db.QueryRowContext, + }) + + _, err := repo.Upsert(ctx, Provider{ + PackID: createTestPack(t, store), + ProviderID: "provider-missing-after-upsert", + DisplayName: "Provider Missing After Upsert", + BaseURL: "https://missing.example.com/v1", + Platform: "openai", + }) + if !errors.Is(err, sql.ErrNoRows) { + t.Fatalf("Upsert() error = %v, want sql.ErrNoRows fallback read error", err) + } +} + func TestProvidersRepoValidationErrors(t *testing.T) { store := openTestDB(t) packID := createTestPack(t, store)