test(17-06): framework and admin SPA unit tests for the blog companions
- signpost: activation refusals, host overrides, D-02 pattern layers incl. PatternFrom, ReferenceOptions depths - seaglass: removal report, bluemonday bypass corpus, Check - pact Option.Depth omitempty and the menu item type contracts; party activates menu item types after Register and before the first Boot - cabana: repeater compile and decode, dependsOn compile and recheck, richeditor keys and notices, reorder and singleton boot checks (Docker-free); repeater save, dependent recheck, richeditor save, reorder and singleton routes on Postgres; conformance cases for the documented 404, 409 and 422 answers of the new routes - admin: richeditor value contract, link popover, notices and chunk failure; repeater bounds, remove and reorder, collapsed rows; dependent dropdown states and last request wins; reorder announcements, partition switch and tree rules; SingletonForm spec - gate: required names in the go, admin and postgres stages
This commit is contained in:
@@ -223,6 +223,105 @@ func TestPhase10OpenAPIConformance(t *testing.T) {
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
|
||||
// The documented error answers of the Phase 17 routes, after a fresh
|
||||
// login (the last case logged out).
|
||||
if rec := env.send(t, http.MethodPost, "/auth/login", map[string]string{"login": env.login, "password": adminTestPassword}, false); rec.Code != http.StatusOK {
|
||||
t.Fatalf("re-login status=%d body=%s", rec.Code, rec.Body.String())
|
||||
}
|
||||
for _, tc := range companionsErrorCases() {
|
||||
t.Run(fmt.Sprintf("%s %d %s", tc.key, tc.status, tc.name), func(t *testing.T) {
|
||||
rec := tc.call(t, env)
|
||||
if rec.Code != tc.status {
|
||||
t.Fatalf("status=%d want %d body=%s", rec.Code, tc.status, rec.Body.String())
|
||||
}
|
||||
dec := json.NewDecoder(bytes.NewReader(rec.Body.Bytes()))
|
||||
dec.DisallowUnknownFields()
|
||||
var body cabana.ErrorEnvelope
|
||||
if err := dec.Decode(&body); err != nil || body.Error.Code == "" {
|
||||
t.Fatalf("body is not an ErrorEnvelope: %v\n%s", err, rec.Body.String())
|
||||
}
|
||||
if tc.code != "" && body.Error.Code != tc.code {
|
||||
t.Fatalf("error code = %q, want %q", body.Error.Code, tc.code)
|
||||
}
|
||||
method, path, _ := strings.Cut(tc.key, " ")
|
||||
if got := spec.ref(path, strings.ToLower(method), fmt.Sprint(tc.status)); got != "cabana.ErrorEnvelope" {
|
||||
t.Fatalf("admin.json documents %q for %s %d, the handler writes cabana.ErrorEnvelope", got, tc.key, tc.status)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// companionsErrorCase is one documented error answer of a Phase 17 route.
|
||||
type companionsErrorCase struct {
|
||||
key string
|
||||
status int
|
||||
name string
|
||||
code string
|
||||
call func(t *testing.T, env *conformEnv) *httptest.ResponseRecorder
|
||||
}
|
||||
|
||||
// companionsErrorCases are the 404, 409 and 422 answers the dependent
|
||||
// options, reorder and singleton routes document, and the 404 of the list,
|
||||
// create and delete routes on a singleton controller.
|
||||
func companionsErrorCases() []companionsErrorCase {
|
||||
partitionLinks := func(t *testing.T, e *conformEnv) (uint, []uint) {
|
||||
t.Helper()
|
||||
group := companionsGroup{Title: "Errors " + e.stamp}
|
||||
if err := e.db.Create(&group).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var ids []uint
|
||||
for i := range 2 {
|
||||
link := companionsLink{Title: fmt.Sprintf("err-%d-%s", i, e.stamp), GroupID: &group.ID, SortOrder: i + 1}
|
||||
if err := e.db.Create(&link).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ids = append(ids, link.ID)
|
||||
}
|
||||
return group.ID, ids
|
||||
}
|
||||
profileID := func(t *testing.T, e *conformEnv) uint {
|
||||
t.Helper()
|
||||
return dataID(t, e.send(t, http.MethodGet, "/acme/companions/profiles/singleton", nil, true).Body.Bytes())
|
||||
}
|
||||
return []companionsErrorCase{
|
||||
{"GET /{vendor}/{plugin}/{controller}/fields/{field}/dependent-options", 404, "field without dependsOn", "not_found", func(t *testing.T, e *conformEnv) *httptest.ResponseRecorder {
|
||||
return e.send(t, http.MethodGet, dependentOptionsPath("title", "acme-page"), nil, true)
|
||||
}},
|
||||
{"GET /{vendor}/{plugin}/{controller}/fields/{field}/dependent-options", 401, "anonymous", "", func(t *testing.T, e *conformEnv) *httptest.ResponseRecorder {
|
||||
return e.send(t, http.MethodGet, dependentOptionsPath("reference", "acme-page"), nil, false)
|
||||
}},
|
||||
{"GET /{vendor}/{plugin}/{controller}/reorder", 404, "unknown partition", "not_found", func(t *testing.T, e *conformEnv) *httptest.ResponseRecorder {
|
||||
return e.send(t, http.MethodGet, "/acme/companions/links/reorder?partition=999999", nil, true)
|
||||
}},
|
||||
{"GET /{vendor}/{plugin}/{controller}/reorder", 422, "partition not an id", "validation_failed", func(t *testing.T, e *conformEnv) *httptest.ResponseRecorder {
|
||||
return e.send(t, http.MethodGet, "/acme/companions/links/reorder?partition=abc", nil, true)
|
||||
}},
|
||||
{"POST /{vendor}/{plugin}/{controller}/reorder", 409, "stale set", "reorder_stale", func(t *testing.T, e *conformEnv) *httptest.ResponseRecorder {
|
||||
group, ids := partitionLinks(t, e)
|
||||
return e.send(t, http.MethodPost, "/acme/companions/links/reorder", map[string]any{"partition": group, "nodes": []any{reorderNode(ids[0], nil, 1)}}, true)
|
||||
}},
|
||||
{"POST /{vendor}/{plugin}/{controller}/reorder", 422, "cycle", "validation_failed", func(t *testing.T, e *conformEnv) *httptest.ResponseRecorder {
|
||||
group, ids := partitionLinks(t, e)
|
||||
return e.send(t, http.MethodPost, "/acme/companions/links/reorder", map[string]any{"partition": group, "nodes": []any{reorderNode(ids[0], &ids[1], 1), reorderNode(ids[1], &ids[0], 1)}}, true)
|
||||
}},
|
||||
{"POST /{vendor}/{plugin}/{controller}/reorder", 404, "unknown partition", "not_found", func(t *testing.T, e *conformEnv) *httptest.ResponseRecorder {
|
||||
return e.send(t, http.MethodPost, "/acme/companions/links/reorder", map[string]any{"partition": 999999, "nodes": []any{}}, true)
|
||||
}},
|
||||
{"GET /{vendor}/{plugin}/{controller}/singleton", 404, "not a singleton", "not_found", func(t *testing.T, e *conformEnv) *httptest.ResponseRecorder {
|
||||
return e.send(t, http.MethodGet, "/acme/companions/links/singleton", nil, true)
|
||||
}},
|
||||
{"GET /{vendor}/{plugin}/{controller}", 404, "list of a singleton", "not_found", func(t *testing.T, e *conformEnv) *httptest.ResponseRecorder {
|
||||
return e.send(t, http.MethodGet, "/acme/companions/profiles", nil, true)
|
||||
}},
|
||||
{"POST /{vendor}/{plugin}/{controller}", 404, "create on a singleton", "not_found", func(t *testing.T, e *conformEnv) *httptest.ResponseRecorder {
|
||||
return e.send(t, http.MethodPost, "/acme/companions/profiles", map[string]any{"title": "x"}, true)
|
||||
}},
|
||||
{"DELETE /{vendor}/{plugin}/{controller}/{id}", 404, "delete on a singleton", "not_found", func(t *testing.T, e *conformEnv) *httptest.ResponseRecorder {
|
||||
return e.send(t, http.MethodDelete, fmt.Sprintf("/acme/companions/profiles/%d", profileID(t, e)), nil, true)
|
||||
}},
|
||||
}
|
||||
}
|
||||
|
||||
// conformCases is the conformance case of every inventoried admin route, in
|
||||
|
||||
Reference in New Issue
Block a user