mirror of
https://github.com/zap-proto/zip.git
synced 2026-10-19 15:38:57 +00:00
v1.37.9 fixed the REST path and the three by-name seams that already looked the op up in the composed registry. Two did not: the graph resolved its ops from that same registry and then invoked them without saying so, and the in-process invoke did the same. So the contract fell back to the declaration — for anything declared on a group, the bare leaf — and a rule reading op.Path governed the REST call while missing the graph call to the SAME operation. A test now asks both seams about one op and requires the same answer. Co-authored-by: Hanzo Dev <[email protected]>
76 lines
2.3 KiB
Go
76 lines
2.3 KiB
Go
package zip_test
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
"github.com/zap-proto/zip"
|
|
)
|
|
|
|
type probeIn struct{}
|
|
type probeOut struct {
|
|
OK bool `json:"ok"`
|
|
}
|
|
|
|
type crm struct{}
|
|
|
|
// Probe answers whether the surface is up.
|
|
func (crm) Probe(context.Context, *probeIn) (*probeOut, error) { return &probeOut{OK: true}, nil }
|
|
|
|
// EVERY SEAM STATES THE ADDRESS, not just the REST route.
|
|
//
|
|
// An op reached by NAME — through the graph, a tools/call, the call plane or an
|
|
// in-process invoke — is the same operation as the one reached by path, so the
|
|
// rule has to see the same identity either way. The graph resolved ops out of
|
|
// the composed registry and then invoked them without saying so, which left the
|
|
// contract falling back to the declaration: for anything declared on a group,
|
|
// the bare leaf. A rule reading op.Path governed the REST call and not the graph
|
|
// call to the same operation.
|
|
func TestGraph_ARuleSeesTheServedAddress(t *testing.T) {
|
|
app := zip.New(zip.Config{AppName: "crm", DisableStartupMessage: true})
|
|
app.Group("").Group("/v1/crm").Post("/probe", crm{}.Probe, zip.WithOperationID("crm_probe"))
|
|
app.MountGraph("/v1/graphql")
|
|
|
|
var seen []string
|
|
app.Authorize(func(_ context.Context, op zip.Op, _ any) (zip.Decision, error) {
|
|
seen = append(seen, op.Method+" "+op.Path+" "+op.OperationID)
|
|
return zip.Decision{Effect: zip.Allow}, nil
|
|
})
|
|
if err := app.Build(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Through the graph, by name.
|
|
body, _ := json.Marshal(zip.GraphRequest{Query: `mutation { crm_probe { ok } }`})
|
|
req := httptest.NewRequest(http.MethodPost, "/v1/graphql", bytes.NewReader(body))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
resp, err := app.Test(req)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
_ = resp.Body.Close()
|
|
|
|
// And over REST, by path.
|
|
rr := httptest.NewRequest(http.MethodPost, "/v1/crm/probe", bytes.NewReader([]byte(`{}`)))
|
|
rr.Header.Set("Content-Type", "application/json")
|
|
resp2, err := app.Test(rr)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
_ = resp2.Body.Close()
|
|
|
|
if len(seen) != 2 {
|
|
t.Fatalf("authorizer ran %d times, want 2 (graph and REST): %v", len(seen), seen)
|
|
}
|
|
const want = "POST /v1/crm/probe crm_probe"
|
|
for i, got := range seen {
|
|
if got != want {
|
|
t.Errorf("call %d saw %q, want %q — both seams answer for one operation", i, got, want)
|
|
}
|
|
}
|
|
}
|