Skip to content

Commit e160b72

Browse files
test(e2e-account): register new endpoint in cross-cutting registries + cover model reap/purge branches
Fixes the CI failures from #260's guarded /internal/e2e/account endpoint (endpoint + handler tests were correct; only the cross-cutting registries and a few uncovered model error-branches were missing): 1. codeToAgentAction (helpers.go): add operator-facing agent_action for the 6 new error codes — not_test_cohort, rand_failed, team_create_failed, tier_not_allowed, tier_set_failed, user_create_failed. Fixes TestErrorCode_HasAgentAction. 2. auditConsumerSpec (reliability_contract_test.go): register e2e.account.created + e2e.account.reaped as IntentionallyNoConsumer (operator-internal, never customer-facing). Fixes TestReliability_AuditKinds_EveryConstantHasConsumerSpec. 3. openapi_test.go intentionallyHidden: exempt POST /internal/e2e/account + DELETE /internal/e2e/account/{team_id} (CI-only, guarded; mirrors the other /internal worker routes). Fixes TestOpenAPI_CoversAllRegisteredRoutes. 4. route_donebar_guard routeTestMap: map the 2 routes to their existing covering handler-integration tests (create→MintsTestCohortAndAuthenticatingJWT, delete→Reap_TestCohortTeam_Purged). Fixes TestDoneBar_EveryRouteCovered; keeps TestDoneBar_TestMapPointsAtRealTests green. 5. e2e_account_errbranches_test.go: white-box sqlmock coverage for the DB-error branches of CreateTestCohortTeam, DeleteTeamHard, and MarkTeamResourcesForReaper (resource.go 587-592, team.go 414-442) — 100% of the changed lines, satisfying the patch-coverage gate. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent ac8b561 commit e160b72

5 files changed

Lines changed: 144 additions & 2 deletions

File tree

e2e/reliability_contract_test.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,16 @@ var auditConsumerSpec = map[string]auditConsumerExpectation{
214214
// signature-passed-but-team-unknown signal. No customer email: the
215215
// affected "customer" either does not exist or was deleted.
216216
"razorpay.webhook.team_not_found": {IntentionallyNoConsumer: true},
217+
218+
// CI-only ephemeral-test-account surface (guarded; inert by default).
219+
// Both fire from the internal POST/DELETE /internal/e2e/account routes
220+
// and are operator-internal observability signals — a spike in created
221+
// (vs reaped) means CI is leaking test accounts. NEVER customer-facing:
222+
// the team is always is_test_cohort and the synthetic email is not PII.
223+
// Counterparts to the other operator-only kinds above — audit rows are
224+
// dashboard signals, not customer notifications.
225+
"e2e.account.created": {IntentionallyNoConsumer: true},
226+
"e2e.account.reaped": {IntentionallyNoConsumer: true},
217227
}
218228

219229
// ─── Test 1: every constant has a spec entry ──────────────────────────────────

internal/handlers/helpers.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1198,6 +1198,31 @@ var codeToAgentAction = map[string]errorCodeMeta{
11981198
"reauth_required": {
11991199
AgentAction: "Tell the user this action requires a fresh session (admin-scope PAT mints need re-auth). Sign in again at https://instanode.dev/login — see https://instanode.dev/docs/auth.",
12001200
},
1201+
1202+
// ── CI-only ephemeral-test-account surface (POST/DELETE /internal/e2e/account) ──
1203+
// These codes only ever surface to the CI harness driving the guarded
1204+
// E2E_ACCOUNT_TOKEN endpoint — never to a real customer agent (the route
1205+
// is inert/404 unless the token is configured). The agent_action is
1206+
// therefore operator-facing: it points at the token + request shape rather
1207+
// than a customer recovery URL.
1208+
"not_test_cohort": {
1209+
AgentAction: "This is the guarded CI test-account endpoint: the target team is not in the test cohort, so it can never be reaped here. Operators: only is_test_cohort teams minted via POST /internal/e2e/account are reapable — check the team_id in the request.",
1210+
},
1211+
"rand_failed": {
1212+
AgentAction: "This is the guarded CI test-account endpoint: secure random generation failed while minting the account id. Operators: retry; if it persists the host's entropy source is unavailable.",
1213+
},
1214+
"team_create_failed": {
1215+
AgentAction: "This is the guarded CI test-account endpoint: creating the test team failed. Operators: check platform_db connectivity and the E2E_ACCOUNT_TOKEN / request before retrying.",
1216+
},
1217+
"tier_not_allowed": {
1218+
AgentAction: "This is the guarded CI test-account endpoint: the requested tier is not mintable here. Operators: pass an allowed tier (free/hobby/hobby_plus/pro) in the request — see the endpoint's tier allowlist.",
1219+
},
1220+
"tier_set_failed": {
1221+
AgentAction: "This is the guarded CI test-account endpoint: elevating the test team to the requested tier failed. Operators: check platform_db connectivity and the request before retrying.",
1222+
},
1223+
"user_create_failed": {
1224+
AgentAction: "This is the guarded CI test-account endpoint: creating the test primary user failed. Operators: check platform_db connectivity and the E2E_ACCOUNT_TOKEN / request before retrying.",
1225+
},
12011226
}
12021227

12031228
// ErrorResponse is the canonical JSON shape for every 4xx/5xx response.

internal/handlers/openapi_test.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -682,8 +682,16 @@ func TestOpenAPI_CoversAllRegisteredRoutes(t *testing.T) {
682682
// when a manual backup fails terminally. Not a customer-facing
683683
// surface, so it stays out of the agent-facing OpenAPI spec.
684684
"POST /internal/teams/{id}/backup-quota/refund": true,
685-
"GET /api/v1/usage/wall": true,
686-
"POST /api/v1/experiments/converted": true,
685+
// CI-only ephemeral-test-account surface. Guarded by E2E_ACCOUNT_TOKEN
686+
// (route registers inert / 404s unless the token is configured) and
687+
// only ever driven by the E2E harness against prod — never a
688+
// customer-facing agent surface. Documenting it in the agent-facing
689+
// OpenAPI would mislead agents into thinking they can mint accounts.
690+
// Same rationale as the WORKER_INTERNAL_JWT_SECRET /internal routes above.
691+
"POST /internal/e2e/account": true,
692+
"DELETE /internal/e2e/account/{team_id}": true,
693+
"GET /api/v1/usage/wall": true,
694+
"POST /api/v1/experiments/converted": true,
687695
// POST /auth/exchange — browser-only bridge between the magic-link
688696
// / OAuth callback and the SPA. The handler reads the transient
689697
// instanode_session_exchange cookie (Path=/auth/exchange,
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package models
2+
3+
// e2e_account_errbranches_test.go — white-box (package models) sqlmock coverage
4+
// for the DB-error branches of the CI-only ephemeral-test-account model
5+
// functions. The happy-path / idempotent behaviour is exercised DB-backed in
6+
// e2e_account_models_test.go (package models_test); those paths can't
7+
// deterministically hit the Exec-failed / RowsAffected-failed branches against
8+
// a real working Postgres, so they're covered here via sqlmock to satisfy the
9+
// 100%-patch coverage gate.
10+
//
11+
// Covers:
12+
// - CreateTestCohortTeam — INSERT … RETURNING QueryRow error (team.go)
13+
// - DeleteTeamHard — Exec error + RowsAffected error (team.go)
14+
// - MarkTeamResourcesForReaper — Exec error + RowsAffected error (resource.go)
15+
16+
import (
17+
"context"
18+
"errors"
19+
"testing"
20+
21+
"github.com/DATA-DOG/go-sqlmock"
22+
"github.com/google/uuid"
23+
"github.com/stretchr/testify/require"
24+
)
25+
26+
func TestCreateTestCohortTeam_QueryError(t *testing.T) {
27+
db, mock := newMock(t)
28+
mock.ExpectQuery(`INSERT INTO teams .* is_test_cohort`).
29+
WillReturnError(errors.New("boom"))
30+
31+
team, err := CreateTestCohortTeam(context.Background(), db, "cohort-mint")
32+
require.Error(t, err)
33+
require.Nil(t, team)
34+
require.Contains(t, err.Error(), "models.CreateTestCohortTeam")
35+
require.NoError(t, mock.ExpectationsWereMet())
36+
}
37+
38+
func TestDeleteTeamHard_ExecError(t *testing.T) {
39+
db, mock := newMock(t)
40+
mock.ExpectExec(`DELETE FROM teams WHERE id`).
41+
WillReturnError(errors.New("boom"))
42+
43+
deleted, err := DeleteTeamHard(context.Background(), db, uuid.New())
44+
require.Error(t, err)
45+
require.False(t, deleted)
46+
require.Contains(t, err.Error(), "models.DeleteTeamHard")
47+
require.NoError(t, mock.ExpectationsWereMet())
48+
}
49+
50+
func TestDeleteTeamHard_RowsAffectedError(t *testing.T) {
51+
db, mock := newMock(t)
52+
mock.ExpectExec(`DELETE FROM teams WHERE id`).
53+
WillReturnResult(sqlmock.NewErrorResult(errors.New("raerr")))
54+
55+
deleted, err := DeleteTeamHard(context.Background(), db, uuid.New())
56+
require.Error(t, err)
57+
require.False(t, deleted)
58+
require.Contains(t, err.Error(), "rows_affected")
59+
require.NoError(t, mock.ExpectationsWereMet())
60+
}
61+
62+
func TestMarkTeamResourcesForReaper_ExecError(t *testing.T) {
63+
db, mock := newMock(t)
64+
mock.ExpectExec(`UPDATE resources`).
65+
WillReturnError(errors.New("boom"))
66+
67+
n, err := MarkTeamResourcesForReaper(context.Background(), db, uuid.New())
68+
require.Error(t, err)
69+
require.Equal(t, int64(0), n)
70+
require.Contains(t, err.Error(), "models.MarkTeamResourcesForReaper")
71+
require.NoError(t, mock.ExpectationsWereMet())
72+
}
73+
74+
func TestMarkTeamResourcesForReaper_RowsAffectedError(t *testing.T) {
75+
db, mock := newMock(t)
76+
mock.ExpectExec(`UPDATE resources`).
77+
WillReturnResult(sqlmock.NewErrorResult(errors.New("raerr")))
78+
79+
n, err := MarkTeamResourcesForReaper(context.Background(), db, uuid.New())
80+
require.Error(t, err)
81+
require.Equal(t, int64(0), n)
82+
require.Contains(t, err.Error(), "rows_affected")
83+
require.NoError(t, mock.ExpectationsWereMet())
84+
}

internal/router/route_donebar_guard_test.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -418,6 +418,21 @@ var routeTestMap = map[string]string{
418418
"GET /api/v1/usage/wall": "TestMiscBlock_UsageWall_RealDBContract",
419419
"GET /api/v1/webhooks/:token/requests": "TestMiscBlock_WebhookInspector_TokenScopedAndIsolated",
420420
"POST /api/v1/experiments/converted": "TestExperimentsConverted_WritesAuditRow",
421+
422+
// ── CI-only ephemeral-test-account surface (guarded; inert by default) —
423+
// DB-backed handler-integration suite
424+
// (internal/handlers/internal_e2e_account_test.go). The create row points at
425+
// the mint happy-path test (is_test_cohort team + email-verified primary +
426+
// a session JWT that authenticates through the production RequireAuth chain);
427+
// the reap row points at the happy-path purge test (an is_test_cohort team's
428+
// resources marked for the reaper + the team tombstoned). The CRITICAL safety
429+
// arm (a non-test-cohort real team can NEVER be reaped → 403 not_test_cohort),
430+
// the 404-when-inert / wrong-token guard, tier=team/growth 400, paid-tier,
431+
// idempotent reap, and per-token rate-limit/fail-open arms are covered in the
432+
// same suite (+ internal_e2e_account_errpaths_test.go for the DB/redis/sign
433+
// failure arms).
434+
"POST /internal/e2e/account": "TestE2EAccount_Create_FreeTier_MintsTestCohortAndAuthenticatingJWT",
435+
"DELETE /internal/e2e/account/:team_id": "TestE2EAccount_Reap_TestCohortTeam_Purged",
421436
}
422437

423438
// routeCoverageExemptions lists routes that have NO mapped e2e integration test

0 commit comments

Comments
 (0)