Skip to content

Commit 9620ade

Browse files
test(ci): 100% patch coverage for with_resources seed error arms
CI diff-cover flagged 80% patch coverage: the seed-failure branches were uncovered (internal_e2e_account.go:297-300 seed_failed 503 arm; 374-379 CreateResource/MarkResourceActive error returns). - Add e2eSeedFastResources package-var seam (mirrors the e2eSignSessionJWT seam) so a test can force CreateAccount's seed_failed 503 arm without making the real resources table reject an insert mid-request. - TestE2EAccount_Create_WithResources_SeedFailure_Returns503: seam → 503 seed_failed. - internal_e2e_account_seed_whitebox_test.go (sqlmock): the two seedFastResources error arms — CreateResource error + MarkResourceActive error after a successful insert. seedFastResources + CreateAccount now report 100.0% func coverage. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 651fbef commit 9620ade

4 files changed

Lines changed: 116 additions & 1 deletion

File tree

internal/handlers/internal_e2e_account.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,7 @@ func (h *E2EAccountHandler) CreateAccount(c *fiber.Ctx) error {
292292
// loudly. The rows are reaped with the team.
293293
var seededTokens []string
294294
if req.WithResources {
295-
toks, serr := h.seedFastResources(ctx, team.ID, tier, env)
295+
toks, serr := e2eSeedFastResources(h, ctx, team.ID, tier, env)
296296
if serr != nil {
297297
metrics.E2EAccountTotal.WithLabelValues(e2eMetricOpCreate, e2eResultError).Inc()
298298
slog.Error("internal.e2e.create.seed_failed", "error", serr, "team_id", team.ID.String())
@@ -362,6 +362,12 @@ func (h *E2EAccountHandler) CreateAccount(c *fiber.Ctx) error {
362362
// a cache row needs no dedicated infra to satisfy a list/detail journey), so
363363
// the seed is fast and synchronous. Any error aborts (returns it) — the caller
364364
// turns it into a 503 so CI never receives a half-populated account.
365+
//
366+
// A package-var seam (not a direct method call) so a test can force the
367+
// caller's seed_failed (503) arm without needing to make the real resources
368+
// table reject an insert mid-request.
369+
var e2eSeedFastResources = (*E2EAccountHandler).seedFastResources
370+
365371
func (h *E2EAccountHandler) seedFastResources(ctx context.Context, teamID uuid.UUID, tier, env string) ([]string, error) {
366372
tokens := make([]string, 0, len(e2eSeedResourceTypes))
367373
for _, rt := range e2eSeedResourceTypes {

internal/handlers/internal_e2e_account_export_test.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ package handlers
44
// internal_e2e_account_*_test.go coverage suite (package handlers_test).
55

66
import (
7+
"context"
78
"time"
89

910
"github.com/google/uuid"
@@ -40,6 +41,18 @@ func E2ESeedResourceTypesForTest() []string {
4041
return out
4142
}
4243

44+
// SetE2ESeedFastResourcesForTest overrides the e2eSeedFastResources seam so a
45+
// test can force CreateAccount's seed_failed (503) arm deterministically,
46+
// without making the real resources table reject an insert mid-request.
47+
// Returns a restore func.
48+
func SetE2ESeedFastResourcesForTest(err error) (restore func()) {
49+
prev := e2eSeedFastResources
50+
e2eSeedFastResources = func(_ *E2EAccountHandler, _ context.Context, _ uuid.UUID, _, _ string) ([]string, error) {
51+
return nil, err
52+
}
53+
return func() { e2eSeedFastResources = prev }
54+
}
55+
4356
// SetE2ESignSessionJWTForTest overrides the e2eSignSessionJWT seam so a test
4457
// can force the token_issue_failed (503) arm of CreateAccount. Returns a
4558
// restore func. HS256-over-[]byte never errors in practice, so this seam is
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package handlers
2+
3+
// internal_e2e_account_seed_whitebox_test.go — whitebox coverage for the
4+
// with_resources seed error arms (seedFastResources). The happy seed path is
5+
// covered end-to-end by the external suite against a real test DB; these tests
6+
// drive the two failure branches deterministically with sqlmock so the 100%-
7+
// patch gate is satisfied without a flaky "make the real DB fail" dance:
8+
//
9+
// - CreateResource error → seedFastResources returns "seed <type>: ..."
10+
// - MarkResourceActive err → seedFastResources returns "activate seeded ...: ..."
11+
12+
import (
13+
"context"
14+
"errors"
15+
"testing"
16+
"time"
17+
18+
"github.com/DATA-DOG/go-sqlmock"
19+
"github.com/google/uuid"
20+
"github.com/stretchr/testify/require"
21+
22+
"instant.dev/internal/config"
23+
)
24+
25+
// resourceReturningRow builds a single fully-populated resources row in the
26+
// exact column order scanResource expects, so a mocked CreateResource INSERT …
27+
// RETURNING parses cleanly and the test can advance to the MarkResourceActive
28+
// step. Values are placeholders — only the shape matters.
29+
func resourceReturningRow() *sqlmock.Rows {
30+
cols := []string{
31+
"id", "team_id", "token", "resource_type", "name", "connection_url", "key_prefix", "tier",
32+
"env", "fingerprint", "cloud_vendor", "country_code", "status", "migration_status",
33+
"expires_at", "storage_bytes", "provider_resource_id", "created_request_id", "parent_resource_id", "paused_at",
34+
"last_seen_at", "degraded", "degraded_reason", "last_reconciled_at", "auth_mode", "created_at",
35+
}
36+
return sqlmock.NewRows(cols).AddRow(
37+
uuid.New(), uuid.New(), uuid.New(), "cache", nil, nil, nil, "pro",
38+
"development", nil, nil, nil, "pending", "none",
39+
nil, int64(0), nil, nil, nil, nil,
40+
nil, false, nil, nil, "legacy_open", time.Now(),
41+
)
42+
}
43+
44+
func TestSeedFastResources_CreateResourceError(t *testing.T) {
45+
db, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherRegexp))
46+
require.NoError(t, err)
47+
defer db.Close()
48+
49+
// First seed type's INSERT fails outright.
50+
mock.ExpectQuery(`INSERT INTO resources`).WillReturnError(errors.New("insert boom"))
51+
52+
h := &E2EAccountHandler{db: db, cfg: &config.Config{}}
53+
toks, serr := h.seedFastResources(context.Background(), uuid.New(), "pro", "")
54+
require.Error(t, serr)
55+
require.Contains(t, serr.Error(), "seed")
56+
require.Contains(t, serr.Error(), "insert boom")
57+
require.Nil(t, toks)
58+
require.NoError(t, mock.ExpectationsWereMet())
59+
}
60+
61+
func TestSeedFastResources_MarkResourceActiveError(t *testing.T) {
62+
db, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherRegexp))
63+
require.NoError(t, err)
64+
defer db.Close()
65+
66+
// INSERT succeeds (returns a pending row); the activate UPDATE then fails.
67+
mock.ExpectQuery(`INSERT INTO resources`).WillReturnRows(resourceReturningRow())
68+
mock.ExpectExec(`UPDATE resources SET status = 'active'`).WillReturnError(errors.New("update boom"))
69+
70+
h := &E2EAccountHandler{db: db, cfg: &config.Config{}}
71+
toks, serr := h.seedFastResources(context.Background(), uuid.New(), "pro", "")
72+
require.Error(t, serr)
73+
require.Contains(t, serr.Error(), "activate seeded")
74+
require.Contains(t, serr.Error(), "update boom")
75+
require.Nil(t, toks)
76+
require.NoError(t, mock.ExpectationsWereMet())
77+
}

internal/handlers/internal_e2e_account_test.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,25 @@ func TestE2EAccount_Create_WithoutResources_SeedsNothing(t *testing.T) {
401401
require.Equal(t, 0, n, "no resources must be seeded when with_resources is omitted")
402402
}
403403

404+
// TestE2EAccount_Create_WithResources_SeedFailure_Returns503 forces the seed
405+
// step to fail (via the e2eSeedFastResources seam) and asserts CreateAccount
406+
// surfaces a 503 seed_failed — CI must never receive a half-populated account.
407+
func TestE2EAccount_Create_WithResources_SeedFailure_Returns503(t *testing.T) {
408+
skipUnlessE2EDB(t)
409+
db, cleanup := testhelpers.SetupTestDB(t)
410+
defer cleanup()
411+
app := newE2ETestApp(t, db, nil, testE2EToken)
412+
413+
restore := handlers.SetE2ESeedFastResourcesForTest(errors.New("seed exploded"))
414+
defer restore()
415+
416+
resp := postE2ECreate(t, app, testE2EToken, `{"tier":"pro","with_resources":true}`)
417+
require.Equal(t, http.StatusServiceUnavailable, resp.StatusCode,
418+
"a seed failure must surface as 503, never a half-populated 200")
419+
out := decodeE2ECreate(t, resp)
420+
require.Equal(t, "seed_failed", out.Error)
421+
}
422+
404423
// --- reap --------------------------------------------------------------------
405424

406425
func TestE2EAccount_Reap_TestCohortTeam_Purged(t *testing.T) {

0 commit comments

Comments
 (0)