From 70dc9fd1a47f99f45adca2ecfc9ef0e6168eff14 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 10 Jul 2026 10:04:40 +0000 Subject: [PATCH] test: cover Code file scope forwarding and schema sync contracts - Add repositories-file-scope tests for file_content get/blame (#602) - Lock in DeployAwsAgentCoreRevision, IdentitiesConfig bundle contracts (#584) - Lock in DynamicStageNodeV1 v1 stage contracts (#567) - Add harness_schema path resolution for new synced definitions Co-authored-by: Rohan Gupta --- .../registry/repositories-file-scope.test.ts | 129 ++++++++++++++++++ tests/schemas/schema-bundle-contract.test.ts | 67 +++++++++ tests/tools/harness-schema-tool.test.ts | 48 +++++++ 3 files changed, 244 insertions(+) create mode 100644 tests/registry/repositories-file-scope.test.ts diff --git a/tests/registry/repositories-file-scope.test.ts b/tests/registry/repositories-file-scope.test.ts new file mode 100644 index 00000000..3b95fb75 --- /dev/null +++ b/tests/registry/repositories-file-scope.test.ts @@ -0,0 +1,129 @@ +/** + * Regression tests for Harness Code file_content scope forwarding (#602). + * Project-scoped repositories require orgIdentifier/projectIdentifier on + * content and blame APIs — without them the Code service cannot resolve the repo. + */ +import { describe, expect, it, vi } from "vitest"; +import type { Config } from "../../src/config.js"; +import type { HarnessClient } from "../../src/client/harness-client.js"; +import { Registry } from "../../src/registry/index.js"; + +function makeConfig(overrides: Partial = {}): Config { + return { + HARNESS_API_KEY: "pat.test", + HARNESS_ACCOUNT_ID: "test-account", + HARNESS_BASE_URL: "https://app.harness.io", + HARNESS_ORG: "default", + HARNESS_PROJECT: "test-project", + HARNESS_API_TIMEOUT_MS: 30000, + HARNESS_MAX_RETRIES: 3, + HARNESS_MAX_BODY_SIZE_MB: 10, + HARNESS_RATE_LIMIT_RPS: 10, + HARNESS_READ_ONLY: false, + HARNESS_SKIP_ELICITATION: false, + HARNESS_ALLOW_HTTP: false, + HARNESS_FME_BASE_URL: "https://api.split.io", + LOG_LEVEL: "info", + ...overrides, + }; +} + +function makeClient(requestFn: (...args: unknown[]) => unknown): HarnessClient { + return { + request: requestFn, + account: "test-account", + } as unknown as HarnessClient; +} + +describe("file_content scope query params", () => { + it("forwards org_id and project_id on get for project-scoped repos", async () => { + const registry = new Registry(makeConfig({ HARNESS_TOOLSETS: "repositories" })); + const mockRequest = vi.fn().mockResolvedValue({ type: "file", content: "hello" }); + const client = makeClient(mockRequest); + + await registry.dispatch(client, "file_content", "get", { + repo_id: "my-repo", + path: "README.md", + git_ref: "main", + org_id: "AI_Devops", + project_id: "Sanity", + }); + + expect(mockRequest).toHaveBeenCalledWith(expect.objectContaining({ + method: "GET", + path: "/code/api/v1/repos/my-repo/content/README.md", + params: expect.objectContaining({ + orgIdentifier: "AI_Devops", + projectIdentifier: "Sanity", + git_ref: "main", + }), + })); + }); + + it("omits org/project query params when scope is not provided (account-scoped repo)", async () => { + const registry = new Registry(makeConfig({ HARNESS_TOOLSETS: "repositories" })); + const mockRequest = vi.fn().mockResolvedValue({ type: "file", content: "hello" }); + const client = makeClient(mockRequest); + + await registry.dispatch(client, "file_content", "get", { + repo_id: "account-repo", + path: "src/index.ts", + }); + + const call = mockRequest.mock.calls[0]![0] as { params: Record }; + expect(call.params.orgIdentifier).toBeUndefined(); + expect(call.params.projectIdentifier).toBeUndefined(); + }); + + it("forwards org_id and project_id on blame execute action", async () => { + const registry = new Registry(makeConfig({ HARNESS_TOOLSETS: "repositories" })); + const mockRequest = vi.fn().mockResolvedValue({ lines: [] }); + const client = makeClient(mockRequest); + + await registry.dispatchExecute(client, "file_content", "blame", { + repo_id: "my-repo", + path: "src/main.go", + git_ref: "main", + line_from: 1, + line_to: 50, + org_id: "AI_Devops", + project_id: "Sanity", + }); + + expect(mockRequest).toHaveBeenCalledWith(expect.objectContaining({ + method: "GET", + path: "/code/api/v1/repos/my-repo/blame/src%2Fmain.go", + params: expect.objectContaining({ + orgIdentifier: "AI_Devops", + projectIdentifier: "Sanity", + git_ref: "main", + line_from: 1, + line_to: 50, + }), + })); + }); + + it("uses explicit org_id/project_id over configured defaults for scoped repos", async () => { + const registry = new Registry(makeConfig({ + HARNESS_TOOLSETS: "repositories", + HARNESS_ORG: "configured-org", + HARNESS_PROJECT: "configured-project", + })); + const mockRequest = vi.fn().mockResolvedValue({ type: "file", content: "data" }); + const client = makeClient(mockRequest); + + await registry.dispatch(client, "file_content", "get", { + repo_id: "scoped-repo", + path: "pkg/app.go", + org_id: "explicit-org", + project_id: "explicit-project", + }); + + expect(mockRequest).toHaveBeenCalledWith(expect.objectContaining({ + params: expect.objectContaining({ + orgIdentifier: "explicit-org", + projectIdentifier: "explicit-project", + }), + })); + }); +}); diff --git a/tests/schemas/schema-bundle-contract.test.ts b/tests/schemas/schema-bundle-contract.test.ts index 8651f413..dc51f3f8 100644 --- a/tests/schemas/schema-bundle-contract.test.ts +++ b/tests/schemas/schema-bundle-contract.test.ts @@ -153,4 +153,71 @@ describe("schema bundle contract", () => { expect(cdSteps).toHaveProperty("DeployGoogleAgentRuntimeRevisionStepNode_template"); expect(cdSteps).toHaveProperty("DeployGoogleAgentRuntimeRevisionStepInfo"); }); + + it("includes upstream DeployAwsAgentCoreRevision step definitions in v0 pipeline", () => { + const pipelineDefs = SCHEMAS.pipeline.definitions as Record>; + const cdSteps = pipelineDefs.pipeline.steps.cd as Record; + + expect(cdSteps).toHaveProperty("DeployAwsAgentCoreRevisionStepNode"); + expect(cdSteps).toHaveProperty("DeployAwsAgentCoreRevisionStepInfo"); + + const stepNode = cdSteps.DeployAwsAgentCoreRevisionStepNode as { + properties: { type: { enum: string[] } }; + }; + expect(stepNode.properties.type.enum).toContain("DeployAwsAgentCoreRevision"); + + const stepInfo = cdSteps.DeployAwsAgentCoreRevisionStepInfo as { + properties: Record; + }; + expect(stepInfo.properties).toHaveProperty("connectorRef"); + expect(stepInfo.properties).toHaveProperty("image"); + }); + + it("includes upstream DeployAwsAgentCoreRevision step definitions in v0 template", () => { + const templateDefs = SCHEMAS.template.definitions as Record>; + const cdSteps = templateDefs.pipeline.steps.cd as Record; + + expect(cdSteps).toHaveProperty("DeployAwsAgentCoreRevisionStepNode"); + expect(cdSteps).toHaveProperty("DeployAwsAgentCoreRevisionStepNode_template"); + expect(cdSteps).toHaveProperty("DeployAwsAgentCoreRevisionStepInfo"); + }); + + it("includes upstream IdentitiesConfig and IdentitySpec in v0 pipeline common definitions", () => { + const common = (SCHEMAS.pipeline.definitions as Record>).pipeline + .common as Record; + + expect(common).toHaveProperty("IdentitiesConfig"); + expect(common).toHaveProperty("IdentitySpec"); + + const identitiesConfig = common.IdentitiesConfig as { + maxProperties: number; + additionalProperties: { $ref: string }; + }; + expect(identitiesConfig.maxProperties).toBe(10); + expect(identitiesConfig.additionalProperties.$ref).toContain("IdentitySpec"); + + const identitySpec = common.IdentitySpec as { + properties: Record; + }; + expect(identitySpec.properties).toHaveProperty("audience"); + expect(identitySpec.properties).toHaveProperty("subjectTemplate"); + expect(identitySpec.properties.scope?.enum).toContain("STEP"); + }); + + it("includes upstream DynamicStageNodeV1 in v1 pipeline and template unified stages", () => { + for (const key of ["pipeline_v1", "template_v1"] as const) { + const defs = SCHEMAS[key].definitions as Record>; + const unified = defs[key].stages.unified as Record; + + expect(unified).toHaveProperty("DynamicStageNodeV1"); + + const dynamicStage = unified.DynamicStageNodeV1 as { + required: string[]; + properties: { dynamic: { properties: Record } }; + }; + expect(dynamicStage.required).toContain("dynamic"); + expect(dynamicStage.properties.dynamic.properties).toHaveProperty("source"); + expect(dynamicStage.properties.dynamic.properties).toHaveProperty("source-config"); + } + }); }); diff --git a/tests/tools/harness-schema-tool.test.ts b/tests/tools/harness-schema-tool.test.ts index 0dcfe67d..2db95046 100644 --- a/tests/tools/harness-schema-tool.test.ts +++ b/tests/tools/harness-schema-tool.test.ts @@ -355,6 +355,54 @@ describe("harness_schema nested static definition lookup", () => { const schema = parsed.schema as { properties?: Record }; expect(schema.properties?.user?.description).toContain("clone container"); }); + + it("resolves newly synced DeployAwsAgentCoreRevision step definition from v0 pipeline", async () => { + const server = makeMcpServer(); + registerSchemaTool(server, undefined, undefined); + const result = await server.call("harness_schema", { + resource_type: "pipeline", + path: "DeployAwsAgentCoreRevisionStepNode", + }); + const parsed = parseResult(result) as Record; + + expect(result.isError).toBeFalsy(); + expect(parsed.path).toBe("steps.cd.DeployAwsAgentCoreRevisionStepNode"); + expect(parsed.requested_path).toBe("DeployAwsAgentCoreRevisionStepNode"); + const schema = parsed.schema as { properties?: { type?: { enum?: string[] } } }; + expect(schema.properties?.type?.enum).toContain("DeployAwsAgentCoreRevision"); + }); + + it("resolves IdentitiesConfig from v0 pipeline common definitions", async () => { + const server = makeMcpServer(); + registerSchemaTool(server, undefined, undefined); + const result = await server.call("harness_schema", { + resource_type: "pipeline", + path: "IdentitiesConfig", + }); + const parsed = parseResult(result) as Record; + + expect(result.isError).toBeFalsy(); + expect(parsed.path).toBe("common.IdentitiesConfig"); + expect(parsed.requested_path).toBe("IdentitiesConfig"); + const schema = parsed.schema as { maxProperties?: number }; + expect(schema.maxProperties).toBe(10); + }); + + it("resolves DynamicStageNodeV1 from v1 pipeline unified stages", async () => { + const server = makeMcpServer(); + registerSchemaTool(server, undefined, undefined); + const result = await server.call("harness_schema", { + resource_type: "pipeline_v1", + path: "DynamicStageNodeV1", + }); + const parsed = parseResult(result) as Record; + + expect(result.isError).toBeFalsy(); + expect(parsed.path).toBe("stages.unified.DynamicStageNodeV1"); + expect(parsed.requested_path).toBe("DynamicStageNodeV1"); + const schema = parsed.schema as { required?: string[] }; + expect(schema.required).toContain("dynamic"); + }); }); // Wrapper definitions that are grouping objects (not schema nodes) must still