test(mcp): K5 — prompts + resources integration scenarios

- PromptsList_FromTemplates: system template visible via prompts/list + prompts/get
- ResourceRead_NotFound: non-existent pm://projects/{uuid} returns error
- ResourceList_ScopeFilter: in-scope project resource readable; resources listed

All 14 spec §8.2 integration scenarios now complete.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-08 13:31:18 +08:00
parent f708e102d8
commit e4cb55dae2
+116 -4
View File
@@ -53,6 +53,7 @@ import (
tcpg "github.com/testcontainers/testcontainers-go/modules/postgres"
"github.com/yan1h/agent-coding-workflow/internal/audit"
"github.com/yan1h/agent-coding-workflow/internal/chat"
"github.com/yan1h/agent-coding-workflow/internal/infra/db"
"github.com/yan1h/agent-coding-workflow/internal/infra/logger"
"github.com/yan1h/agent-coding-workflow/internal/mcp"
@@ -908,17 +909,128 @@ func (s *mcpIntegrationSuite) seedCrashedSessionWithLiveToken(ctx context.Contex
}
// ---------------------------------------------------------------------------
// K5 stubs
// K5: Prompts + Resources
// ---------------------------------------------------------------------------
func (s *mcpIntegrationSuite) testPromptsListFromTemplates(t *testing.T) {
t.Skip("filled in K5")
t.Parallel()
// Seed a system-scope template via chat.TemplateService
chatRepo := chat.NewRepository(s.pool)
tplSvc := chat.NewTemplateService(chatRepo, s.auditRec, nil)
tpl, err := tplSvc.Create(s.ctx, s.adminID, "test-system-prompt", "You are a helpful assistant.", chat.TemplateScopeSystem)
require.NoError(t, err)
require.NotNil(t, tpl)
// Build an in-memory MCP server with Templates registered
callerResolver := mcp.NewCallerResolver(s.userSvc)
projectRepo := project.NewPostgresRepository(s.pool)
deps := mcp.ServerDeps{
Caller: callerResolver,
Projects: s.projectSvc,
Reqs: project.NewRequirementService(projectRepo, mcp.AuditWrap(s.auditRec)),
Issues: s.issueSvc,
Templates: tplSvc,
}
srv := mcp.NewMCPServer(deps)
require.NoError(t, mcp.RegisterSystemPrompts(srv, deps, s.adminID))
client := mcpsdk.NewClient(&mcpsdk.Implementation{Name: "k5-prompts", Version: "0.0.1"}, nil)
t1, t2 := mcpsdk.NewInMemoryTransports()
if _, err := srv.Connect(s.ctx, t1, nil); err != nil {
t.Fatal(err)
}
sess, err := client.Connect(s.ctx, t2, nil)
require.NoError(t, err)
defer sess.Close()
list, err := sess.ListPrompts(s.ctx, nil)
require.NoError(t, err)
var found bool
for _, p := range list.Prompts {
if p.Name == "test-system-prompt" {
found = true
break
}
}
assert.True(t, found, "expected prompt %q in list", "test-system-prompt")
got, err := sess.GetPrompt(s.ctx, &mcpsdk.GetPromptParams{Name: "test-system-prompt"})
require.NoError(t, err)
require.NotEmpty(t, got.Messages)
}
func (s *mcpIntegrationSuite) testResourceReadNotFound(t *testing.T) {
t.Skip("filled in K5")
t.Parallel()
// Resources are registered by NewMCPServer; use in-memory transport
callerResolver := mcp.NewCallerResolver(s.userSvc)
projectRepo := project.NewPostgresRepository(s.pool)
deps := mcp.ServerDeps{
Caller: callerResolver,
Projects: s.projectSvc,
Reqs: project.NewRequirementService(projectRepo, mcp.AuditWrap(s.auditRec)),
Issues: s.issueSvc,
}
srv := mcp.NewMCPServer(deps)
client := mcpsdk.NewClient(&mcpsdk.Implementation{Name: "k5-resources", Version: "0.0.1"}, nil)
t1, t2 := mcpsdk.NewInMemoryTransports()
if _, err := srv.Connect(s.ctx, t1, nil); err != nil {
t.Fatal(err)
}
sess, err := client.Connect(s.ctx, t2, nil)
require.NoError(t, err)
defer sess.Close()
_, err = sess.ReadResource(s.ctx, &mcpsdk.ReadResourceParams{
URI: "pm://projects/" + uuid.New().String(),
})
require.Error(t, err, "reading non-existent resource should error")
msg := strings.ToLower(err.Error())
assert.True(t,
strings.Contains(msg, "not found") || strings.Contains(msg, "no such") || strings.Contains(msg, "404"),
"expected not_found, got: %v", err)
}
func (s *mcpIntegrationSuite) testResourceListScopeFilter(t *testing.T) {
t.Skip("filled in K5")
t.Parallel()
// Resources are statically registered. Scope filtering applies during READ.
// Verify: list returns resources; reading in-scope project works.
callerResolver := mcp.NewCallerResolver(s.userSvc)
projectRepo := project.NewPostgresRepository(s.pool)
deps := mcp.ServerDeps{
Caller: callerResolver,
Projects: s.projectSvc,
Reqs: project.NewRequirementService(projectRepo, mcp.AuditWrap(s.auditRec)),
Issues: s.issueSvc,
}
srv := mcp.NewMCPServer(deps)
srv.AddReceivingMiddleware(func(next mcpsdk.MethodHandler) mcpsdk.MethodHandler {
return func(ctx context.Context, method string, req mcpsdk.Request) (mcpsdk.Result, error) {
ctx = context.WithValue(ctx, mcp.AuthContextKey, s.userSession())
return next(ctx, method, req)
}
})
client := mcpsdk.NewClient(&mcpsdk.Implementation{Name: "k5-scope", Version: "0.0.1"}, nil)
t1, t2 := mcpsdk.NewInMemoryTransports()
if _, err := srv.Connect(s.ctx, t1, nil); err != nil {
t.Fatal(err)
}
sess, err := client.Connect(s.ctx, t2, nil)
require.NoError(t, err)
defer sess.Close()
list, err := sess.ListResources(s.ctx, nil)
require.NoError(t, err)
assert.NotEmpty(t, list.Resources, "expected registered resources")
// Reading the in-scope project resource should work
_, err = sess.ReadResource(s.ctx, &mcpsdk.ReadResourceParams{
URI: "pm://projects/" + s.projectID.String(),
})
require.NoError(t, err, "in-scope project should be readable")
}