fix(09): WR-03 refuse writes that the compiled list and form do not declare

This commit is contained in:
Jakub Zych
2026-10-01 21:04:31 +02:00
parent 28aa073de0
commit f2ab93f291
8 changed files with 127 additions and 5 deletions

View File

@@ -498,3 +498,63 @@ func containsString(items []string, want string) bool {
}
return false
}
// TestCRUDOperationsFollowDeclarations pins WR-03: the compiled list and form
// decide which writes the server accepts, not only what the SPA shows.
func TestCRUDOperationsFollowDeclarations(t *testing.T) {
_, httpSvc, cc, db, hooks := hookFixture(t)
ctx := principalCtx(hooks, superUser())
created := crudCall(httpSvc, http.MethodPost, "", []byte(`{"name":"Ada"}`), ctx)
if created.Code != http.StatusCreated {
t.Fatalf("declared create=%d %s", created.Code, created.Body.String())
}
id := uintString(decodeData(t, created.Body.Bytes())["id"])
bulk := func() *httptest.ResponseRecorder {
req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(`{"ids":[`+id+`]}`)).WithContext(ctx)
req.SetPathValue("vendor", "acme")
req.SetPathValue("plugin", "demo")
req.SetPathValue("controller", "records")
rec := httptest.NewRecorder()
httpSvc.bulkDelete(rec, req)
return rec
}
forbidden := func(name string, rec *httptest.ResponseRecorder) {
t.Helper()
if rec.Code != http.StatusForbidden || !strings.Contains(rec.Body.String(), `"forbidden"`) {
t.Fatalf("%s=%d %s, want 403 forbidden", name, rec.Code, rec.Body.String())
}
}
// No toolbar create button: the create route is closed, update is not.
list := *cc.List
cc.List = &list
cc.List.ToolbarButtons = []string{"delete"}
forbidden("create without a toolbar create button", crudCall(httpSvc, http.MethodPost, "", []byte(`{"name":"Bea"}`), ctx))
if got := crudCall(httpSvc, http.MethodPut, id, []byte(`{"name":"Cid"}`), ctx); got.Code != http.StatusOK {
t.Fatalf("update=%d %s", got.Code, got.Body.String())
}
// No toolbar delete button: bulk delete is closed.
cc.List.ToolbarButtons = []string{"create"}
forbidden("bulk delete without a toolbar delete button", bulk())
if n := countCrud(t, db); n != 1 {
t.Fatalf("rows=%d, a refused bulk delete removed data", n)
}
// No form: nothing can be created, updated or deleted one by one.
form := cc.Form
cc.Form = nil
forbidden("create without a form", crudCall(httpSvc, http.MethodPost, "", []byte(`{"name":"Dan"}`), ctx))
forbidden("update without a form", crudCall(httpSvc, http.MethodPut, id, []byte(`{"name":"Eve"}`), ctx))
forbidden("delete without a form", crudCall(httpSvc, http.MethodDelete, id, nil, ctx))
if n := countCrud(t, db); n != 1 {
t.Fatalf("rows=%d, a refused write changed data", n)
}
cc.Form = form
// Declared again: bulk delete works.
cc.List.ToolbarButtons = []string{"create", "delete"}
if got := bulk(); got.Code != http.StatusOK || deletedCount(t, got.Body.Bytes()) != 1 {
t.Fatalf("declared bulk delete=%d %s", got.Code, got.Body.String())
}
}