From e4cb55dae27faddf171df52b958f551e6299c09a Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Fri, 8 May 2026 13:31:18 +0800 Subject: [PATCH] =?UTF-8?q?test(mcp):=20K5=20=E2=80=94=20prompts=20+=20res?= =?UTF-8?q?ources=20integration=20scenarios?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- internal/app/mcp_integration_test.go | 120 ++++++++++++++++++++++++++- 1 file changed, 116 insertions(+), 4 deletions(-) diff --git a/internal/app/mcp_integration_test.go b/internal/app/mcp_integration_test.go index e2c5e8a..1b852a0 100644 --- a/internal/app/mcp_integration_test.go +++ b/internal/app/mcp_integration_test.go @@ -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") }