- pier: TestSessionReserve (A-WR-03), TestLockScope (A-WR-07), TestScopeWidening (A-WR-04), TestStatelessFanOut (A-WR-05, A-WR-06), TestPersonalTokenBinding (A-WR-09), TestDispatchHardening (A-WR-01, A-WR-02, A-WR-08, A-IN-04, A-IN-05, A-IN-08), TestCatalogPathFields (A-WR-02, A-IN-07), TestPromptWriteCeiling (C-IN-01), TestTokenScopedRevoke (B-WR-04, C-IN-02), TestResolveClientAddress (B-IN-04); test seam WrapStore - pact TestFrontendTokenScopes, towel TestClientIP, cabana TestTokenScopeNameParity - cabana TestAccountContributionURL (B-WR-03), TestAccountContributionsIsolation (B-WR-06, B-IN-03), TestRecordActionPromptLocalized (B-IN-02), and the Postgres TestAccountTokenRevokeEvent (B-WR-04) and TestResolveBackendTokenRemote (B-IN-04) - vitest: hotkeysEditing.test.ts (B-WR-01, B-WR-02) and the extended AccountContributions.test.ts (B-WR-03, B-WR-06, B-IN-01) - check-phase19.7.sh requires every name (GO_TESTS, GO_TAGGED_TESTS, DOCKER_TESTS, ADMIN_FILES) and scans the new files (PHASE_TEST_FILES)
64 lines
2.0 KiB
Go
64 lines
2.0 KiB
Go
package pier_test
|
|
|
|
import (
|
|
"context"
|
|
"slices"
|
|
"testing"
|
|
"time"
|
|
|
|
"git.golem15.com/golem15/summercms/modules/pact"
|
|
"github.com/modelcontextprotocol/go-sdk/mcp"
|
|
)
|
|
|
|
// promptNames lists the prompt names a session sees, sorted.
|
|
func promptNames(t *testing.T, ctx context.Context, cs *mcp.ClientSession) []string {
|
|
t.Helper()
|
|
list, err := cs.ListPrompts(ctx, nil)
|
|
if err != nil {
|
|
t.Fatalf("prompts/list: %v", err)
|
|
}
|
|
var out []string
|
|
for _, p := range list.Prompts {
|
|
out = append(out, p.Name)
|
|
}
|
|
slices.Sort(out)
|
|
return out
|
|
}
|
|
|
|
// TestPromptWriteCeiling pins plan 06's prompt ceiling (C-IN-01, P-28): a
|
|
// prompt declaring Writes is listed for a read and write grant and hidden
|
|
// from a read-only grant (and cannot be fetched by it), while a prompt
|
|
// without Writes is listed for both.
|
|
func TestPromptWriteCeiling(t *testing.T) {
|
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
defer cancel()
|
|
draft := summarizePrompt()
|
|
draft.Name = "draft"
|
|
draft.Writes = true
|
|
in := startInstance(t, newApp(t, appSpec{guards: []pact.MCPGuard{&acmeGuard{}}}),
|
|
acmePlugin{tools: []pact.MCPTool{echoTool()}, prompts: []pact.MCPPrompt{summarizePrompt(), draft}})
|
|
t.Cleanup(in.pier().StopReaper)
|
|
|
|
for _, c := range []struct {
|
|
name string
|
|
token string
|
|
want []string
|
|
}{
|
|
{"read and write grant", "oas_at_test", []string{"acme_draft", "acme_summarize"}},
|
|
{"read-only grant", "oas_at_read", []string{"acme_summarize"}},
|
|
} {
|
|
t.Run(c.name, func(t *testing.T) {
|
|
for _, version := range []string{"", "newest"} {
|
|
cs := sdkClient(t, ctx, in.url(), c.token, clientOpts{version: version})
|
|
if got := promptNames(t, ctx, cs); !slices.Equal(got, c.want) {
|
|
t.Fatalf("version %q: prompts = %v, want %v", version, got, c.want)
|
|
}
|
|
_, err := cs.GetPrompt(ctx, &mcp.GetPromptParams{Name: "acme_draft", Arguments: map[string]string{"topic": "x"}})
|
|
if allowed := slices.Contains(c.want, "acme_draft"); (err == nil) != allowed {
|
|
t.Fatalf("version %q: get acme_draft err = %v, allowed %v", version, err, allowed)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|