fix(10.1): WR-06 honour the widget field's context on the action route
widgetAction derives the form from the request (create without record_id, update with one) and answers 404 when the field's context hides the widget on that form, reusing contextAllows as the save path does. An update-only action can no longer run with a nil record through a direct POST.
This commit is contained in:
@@ -11,9 +11,11 @@ import (
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
"testing/fstest"
|
||||
"time"
|
||||
|
||||
"git.golem15.com/golem15/summercms/modules/bouncer"
|
||||
"git.golem15.com/golem15/summercms/modules/pact"
|
||||
"git.golem15.com/golem15/summercms/modules/towel"
|
||||
"golang.org/x/net/html"
|
||||
"golang.org/x/net/html/atom"
|
||||
@@ -349,3 +351,69 @@ type vmNode struct {
|
||||
Label string
|
||||
Next *vmNode
|
||||
}
|
||||
|
||||
// TestPhase101WidgetContext covers WR-06: the widget route honours the
|
||||
// field's context like the save path. Without record_id the request is the
|
||||
// create form's, with one the update form's; a widget its context hides on
|
||||
// that form answers 404 and its action never runs.
|
||||
func TestPhase101WidgetContext(t *testing.T) {
|
||||
const lookup = ` lookup:
|
||||
label: acme.demo::lang.gadgets.lookup
|
||||
type: widget
|
||||
widget: acme-demo-lookup
|
||||
action: lookup
|
||||
fill: [name, active]
|
||||
`
|
||||
for _, tc := range []struct {
|
||||
context string
|
||||
body string
|
||||
want int
|
||||
}{
|
||||
{context: "update", body: `{}`, want: http.StatusNotFound},
|
||||
{context: "[update, preview]", body: `{"values":{}}`, want: http.StatusNotFound},
|
||||
{context: "create", body: `{"record_id":1}`, want: http.StatusNotFound},
|
||||
{context: "create", body: `{}`, want: http.StatusOK},
|
||||
{context: "", body: `{}`, want: http.StatusOK},
|
||||
} {
|
||||
t.Run(tc.context+" "+tc.body, func(t *testing.T) {
|
||||
fsys := extFS(t)
|
||||
fields := string(fsys["models/gadget/fields.yaml"].Data)
|
||||
if !strings.Contains(fields, lookup) {
|
||||
t.Fatalf("fixture fields.yaml changed:\n%s", fields)
|
||||
}
|
||||
if tc.context != "" {
|
||||
fields = strings.Replace(fields, lookup, lookup+" context: "+tc.context+"\n", 1)
|
||||
}
|
||||
fsys["models/gadget/fields.yaml"] = &fstest.MapFile{Data: []byte(fields)}
|
||||
ran := 0
|
||||
ctl := newExtController()
|
||||
ctl.actions = []pact.AdminAction{{
|
||||
Name: "lookup", Label: "acme.demo::lang.gadgets.lookup",
|
||||
Run: func(context.Context, pact.AdminActionInput) (pact.AdminActionResult, error) {
|
||||
ran++
|
||||
return pact.AdminActionResult{}, nil
|
||||
},
|
||||
}, extActions()[1]}
|
||||
app, _ := extTranslator(t)
|
||||
reg, _ := mustCompileExt(t, ctl, fsys)
|
||||
svc := &service{app: app, reg: reg}
|
||||
req := httptest.NewRequest(http.MethodPost, adminAPI("/acme/demo/gadgets/widgets/lookup"), strings.NewReader(tc.body))
|
||||
req.SetPathValue("vendor", "acme")
|
||||
req.SetPathValue("plugin", "demo")
|
||||
req.SetPathValue("controller", "gadgets")
|
||||
req.SetPathValue("field", "lookup")
|
||||
req = req.WithContext(bouncer.WithUser(req.Context(), &bouncer.Principal{ID: 1, Backend: true, IsSuperuser: true}))
|
||||
rec := httptest.NewRecorder()
|
||||
svc.widgetAction(rec, req)
|
||||
if rec.Code != tc.want {
|
||||
t.Fatalf("status=%d body=%s, want %d", rec.Code, rec.Body.String(), tc.want)
|
||||
}
|
||||
if wantRan := tc.want == http.StatusOK; (ran == 1) != wantRan {
|
||||
t.Fatalf("action ran %d times, want ran=%v", ran, wantRan)
|
||||
}
|
||||
if tc.want == http.StatusNotFound {
|
||||
assertErrorCode(t, rec.Body.Bytes(), "not_found")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user