Skip to content

Commit 7b83b5c

Browse files
committed
test(handler): assert destructive ops record audit rows
1 parent 6cfa8c5 commit 7b83b5c

1 file changed

Lines changed: 105 additions & 0 deletions

File tree

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
package handler
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"net/http"
7+
"testing"
8+
9+
"github.com/freeCodeCamp/artemis/internal/registry"
10+
"github.com/stretchr/testify/assert"
11+
"github.com/stretchr/testify/require"
12+
)
13+
14+
func bearerTok() map[string]string { return map[string]string{"Authorization": "Bearer tok"} }
15+
16+
func TestSiteDelete_RecordsExactlyOneAudit(t *testing.T) {
17+
h, _ := newTestHandlers(t, staffCallerGH(),
18+
&fakeSites{bySite: map[string][]string{"example": {"team-eng"}}}, newFakeR2())
19+
fa := &fakeAudit{}
20+
h.Audit = fa
21+
22+
w := withChiRoute(http.MethodDelete, "/api/site/{slug}",
23+
"/api/site/example", nil, bearerTok(),
24+
RequestID(h.RequireGitHubBearer(http.HandlerFunc(h.SiteDelete))).ServeHTTP,
25+
context.Background())
26+
require.Equal(t, http.StatusNoContent, w.Code, w.Body.String())
27+
28+
require.Len(t, fa.events, 1, "a site delete records exactly one audit row")
29+
assert.Equal(t, "site.delete", fa.events[0].Action)
30+
assert.Equal(t, "alice", fa.events[0].Actor)
31+
assert.Equal(t, "example", fa.events[0].Site)
32+
assert.Equal(t, "success", fa.events[0].Outcome)
33+
}
34+
35+
func TestSitePurge_RecordsExactlyOneAudit(t *testing.T) {
36+
store := newFakeR2()
37+
store.objects["example/deploys/20260420-141522-abc1234/index.html"] = []byte("hi")
38+
store.aliases["example/production"] = "20260420-141522-abc1234"
39+
store.objects["example/production"] = []byte("20260420-141522-abc1234")
40+
41+
h, _ := newTestHandlers(t, staffCallerGH(),
42+
&fakeSites{bySite: map[string][]string{"example": {"team-eng"}}}, store)
43+
h.Tombstones = &fakeTombstones{}
44+
fa := &fakeAudit{}
45+
h.Audit = fa
46+
47+
w := withChiRoute(http.MethodDelete, "/api/site/{slug}",
48+
"/api/site/example?purge=true", nil, bearerTok(),
49+
RequestID(h.RequireGitHubBearer(http.HandlerFunc(h.SiteDelete))).ServeHTTP,
50+
context.Background())
51+
require.Equal(t, http.StatusOK, w.Code, w.Body.String())
52+
53+
require.Len(t, fa.events, 1, "a whole-site purge records exactly one audit row")
54+
assert.Equal(t, "site.purge", fa.events[0].Action)
55+
assert.Equal(t, "alice", fa.events[0].Actor)
56+
assert.Equal(t, "example", fa.events[0].Site)
57+
assert.Equal(t, "success", fa.events[0].Outcome)
58+
}
59+
60+
func TestSiteRollback_RecordsExactlyOneAudit(t *testing.T) {
61+
store := newFakeR2()
62+
store.objects["www/deploys/20260419-090000-d2/index.html"] = []byte("ok")
63+
store.aliases["www/production"] = "d1-current"
64+
h, _ := newTestHandlers(t, authedGH(), standardSites(), store)
65+
fa := &fakeAudit{}
66+
h.Audit = fa
67+
68+
body, _ := json.Marshal(SiteRollbackRequest{To: "20260419-090000-d2", ExpectedCurrent: "d1-current"})
69+
w := withChiRoute(http.MethodPost, "/api/site/{site}/rollback",
70+
"/api/site/www/rollback", body, bearerTok(),
71+
RequestID(h.RequireGitHubBearer(http.HandlerFunc(h.SiteRollback))).ServeHTTP,
72+
context.Background())
73+
require.Equal(t, http.StatusOK, w.Code, w.Body.String())
74+
75+
require.Len(t, fa.events, 1, "a rollback records exactly one audit row")
76+
assert.Equal(t, "site.rollback", fa.events[0].Action)
77+
assert.Equal(t, "alice", fa.events[0].Actor)
78+
assert.Equal(t, "www", fa.events[0].Site)
79+
assert.Equal(t, "success", fa.events[0].Outcome)
80+
assert.Equal(t, "20260419-090000-d2", fa.events[0].Detail["to"], "the target deploy is captured in the audit detail")
81+
}
82+
83+
func TestRestore_RecordsAuditWithIdempotentOutcome(t *testing.T) {
84+
deployID := "20260420-141522-abc1234"
85+
store := newFakeR2()
86+
store.objects["www/deploys/"+deployID+"/index.html"] = []byte("hi")
87+
88+
h, _ := newTestHandlers(t, authedGH(), standardSites(), store)
89+
h.Trash = &fakeTrash{restoreErr: registry.ErrNotFound}
90+
fa := &fakeAudit{}
91+
h.Audit = fa
92+
93+
w := withChiRoute(http.MethodPost, "/api/site/{site}/deploys/{deployId}/restore",
94+
"/api/site/www/deploys/"+deployID+"/restore", nil, bearerTok(),
95+
RequestID(h.RequireGitHubBearer(http.HandlerFunc(h.SiteDeployRestore))).ServeHTTP,
96+
context.Background())
97+
require.Equal(t, http.StatusOK, w.Code, w.Body.String())
98+
99+
require.Len(t, fa.events, 1, "an idempotent restore still records exactly one audit row")
100+
assert.Equal(t, "site.deploy.restore", fa.events[0].Action)
101+
assert.Equal(t, "alice", fa.events[0].Actor)
102+
assert.Equal(t, deployID, fa.events[0].DeployID)
103+
assert.Equal(t, "idempotent", fa.events[0].Outcome,
104+
"the non-success outcome is audited verbatim, not coerced to success")
105+
}

0 commit comments

Comments
 (0)