import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { describe, expect, it } from "vitest"; import { resolveIntegrationDocumentation } from "./documentation.js"; function createPluginDocsFixture( pluginId: string, files: Record, ): string { const rootDirectory = mkdtempSync(join(tmpdir(), "sdk-docs-")); const pluginDirectory = join(rootDirectory, pluginId); mkdirSync(pluginDirectory, { recursive: true }); for (const [relativePath, content] of Object.entries(files)) { const absolutePath = join(pluginDirectory, relativePath); mkdirSync(join(absolutePath, ".."), { recursive: true }); writeFileSync(absolutePath, content); } return rootDirectory; } describe("resolveIntegrationDocumentation", () => { it("returns base README when no manifest exists", async () => { const integrationsDirectory = createPluginDocsFixture("dropbox", { "README.md": "base-docs", }); try { const docs = await resolveIntegrationDocumentation("dropbox", { pluginVersion: "0.4.0", integrationsDirectory, }); expect(docs).toBe("base-docs"); } finally { rmSync(integrationsDirectory, { recursive: true, force: true }); } }); it("applies matching overlays in manifest order", async () => { const integrationsDirectory = createPluginDocsFixture("dropbox", { "README.md": "base-docs", "docs.manifest.json": JSON.stringify({ overlays: [ { file: "overlays/01.md", versionRange: ">=0.4.0" }, { file: "overlays/02.md", versionRange: ">=0.5.0" }, { file: "overlays/03.md", versionRange: ">=0.5.0" }, ], }), "overlays/01.md": "overlay-one", "overlays/02.md": "overlay-two", "overlays/03.md": "overlay-three", }); try { const docs = await resolveIntegrationDocumentation("dropbox", { pluginVersion: "0.5.0", integrationsDirectory, }); expect(docs).toBe( ["base-docs", "overlay-one", "overlay-two", "overlay-three"].join( "\n\n", ), ); } finally { rmSync(integrationsDirectory, { recursive: true, force: true }); } }); it("skips overlays whose version range does not match", async () => { const integrationsDirectory = createPluginDocsFixture("dropbox", { "README.md": "base-docs", "docs.manifest.json": JSON.stringify({ overlays: [ { file: "overlays/match.md", versionRange: ">=0.4.0 <0.6.0" }, { file: "overlays/skip.md", versionRange: ">=0.6.0" }, ], }), "overlays/match.md": "overlay-match", "overlays/skip.md": "overlay-skip", }); try { const docs = await resolveIntegrationDocumentation("dropbox", { pluginVersion: "0.5.1", integrationsDirectory, }); expect(docs).toBe(["base-docs", "overlay-match"].join("\n\n")); } finally { rmSync(integrationsDirectory, { recursive: true, force: true }); } }); it("returns base docs when pluginVersion is missing", async () => { const integrationsDirectory = createPluginDocsFixture("dropbox", { "README.md": "base-docs", "docs.manifest.json": JSON.stringify({ overlays: [{ file: "overlays/01.md", versionRange: ">=0.4.0" }], }), "overlays/01.md": "overlay-one", }); try { const docs = await resolveIntegrationDocumentation("dropbox", { integrationsDirectory, }); expect(docs).toBe("base-docs"); } finally { rmSync(integrationsDirectory, { recursive: true, force: true }); } }); it("throws on invalid manifest shape", async () => { const integrationsDirectory = createPluginDocsFixture("dropbox", { "README.md": "base-docs", "docs.manifest.json": JSON.stringify({ overlays: [{ versionRange: ">=0.4.0" }], }), }); try { await expect( resolveIntegrationDocumentation("dropbox", { pluginVersion: "0.5.0", integrationsDirectory, }), ).rejects.toThrowError(/Invalid overlay entry/); } finally { rmSync(integrationsDirectory, { recursive: true, force: true }); } }); it("replaces markdown sections via overlay directives", async () => { const integrationsDirectory = createPluginDocsFixture("dropbox", { "README.md": [ "# Dropbox", "", "## Authentication", "Old auth docs", "", "## Limits", "Old limits docs", ].join("\n"), "docs.manifest.json": JSON.stringify({ overlays: [ { file: "overlays/replace-auth.md", versionRange: ">=0.4.0" }, ], }), "overlays/replace-auth.md": [ "## @replace: Authentication", "New auth docs", "With multiple lines", ].join("\n"), }); try { const docs = await resolveIntegrationDocumentation("dropbox", { pluginVersion: "0.4.1", integrationsDirectory, }); expect(docs).toContain( "## Authentication\nNew auth docs\nWith multiple lines", ); expect(docs).toContain("## Limits\nOld limits docs"); expect(docs).not.toContain("Old auth docs"); } finally { rmSync(integrationsDirectory, { recursive: true, force: true }); } }); it("applies replace overlays in manifest order", async () => { const integrationsDirectory = createPluginDocsFixture("dropbox", { "README.md": ["# Dropbox", "", "## Authentication", "v1 auth"].join("\n"), "docs.manifest.json": JSON.stringify({ overlays: [ { file: "overlays/replace-auth-v2.md", versionRange: ">=0.4.0" }, { file: "overlays/replace-auth-v3.md", versionRange: ">=0.4.0" }, ], }), "overlays/replace-auth-v2.md": [ "## @replace: Authentication", "v2 auth", ].join("\n"), "overlays/replace-auth-v3.md": [ "## @replace: Authentication", "v3 auth", ].join("\n"), }); try { const docs = await resolveIntegrationDocumentation("dropbox", { pluginVersion: "0.4.1", integrationsDirectory, }); expect(docs).toContain("## Authentication\nv3 auth"); expect(docs).not.toContain("v1 auth"); expect(docs).not.toContain("v2 auth"); } finally { rmSync(integrationsDirectory, { recursive: true, force: true }); } }); it("throws when replace target section does not exist", async () => { const integrationsDirectory = createPluginDocsFixture("dropbox", { "README.md": ["# Dropbox", "", "## Authentication", "auth docs"].join( "\n", ), "docs.manifest.json": JSON.stringify({ overlays: [ { file: "overlays/missing-target.md", versionRange: ">=0.4.0" }, ], }), "overlays/missing-target.md": [ "## @replace: Limits", "new limits docs", ].join("\n"), }); try { await expect( resolveIntegrationDocumentation("dropbox", { pluginVersion: "0.4.1", integrationsDirectory, }), ).rejects.toThrowError(/could not find section/i); } finally { rmSync(integrationsDirectory, { recursive: true, force: true }); } }); it("handles mixed append and replace in a single overlay", async () => { const integrationsDirectory = createPluginDocsFixture("dropbox", { "README.md": [ "# Dropbox", "", "## Setup", "Old setup content", "", "## Usage", "Usage content", ].join("\n"), "docs.manifest.json": JSON.stringify({ overlays: [{ file: "overlays/mixed.md", versionRange: ">=0.4.0" }], }), "overlays/mixed.md": [ "Appended intro paragraph", "", "## @replace: Setup", "New setup content", ].join("\n"), }); try { const docs = await resolveIntegrationDocumentation("dropbox", { pluginVersion: "0.5.0", integrationsDirectory, }); expect(docs).toContain("Appended intro paragraph"); expect(docs).toContain("## Setup\nNew setup content"); expect(docs).not.toContain("Old setup content"); expect(docs).toContain("## Usage\nUsage content"); } finally { rmSync(integrationsDirectory, { recursive: true, force: true }); } }); it("handles multiple @replace directives in a single overlay", async () => { const integrationsDirectory = createPluginDocsFixture("dropbox", { "README.md": [ "# Dropbox", "", "## Auth", "Old auth", "", "## Limits", "Old limits", "", "## Usage", "Usage content", ].join("\n"), "docs.manifest.json": JSON.stringify({ overlays: [ { file: "overlays/multi-replace.md", versionRange: ">=0.4.0" }, ], }), "overlays/multi-replace.md": [ "## @replace: Auth", "New auth", "", "## @replace: Limits", "New limits", ].join("\n"), }); try { const docs = await resolveIntegrationDocumentation("dropbox", { pluginVersion: "0.4.0", integrationsDirectory, }); expect(docs).toContain("## Auth\nNew auth"); expect(docs).toContain("## Limits\nNew limits"); expect(docs).toContain("## Usage\nUsage content"); expect(docs).not.toContain("Old auth"); expect(docs).not.toContain("Old limits"); } finally { rmSync(integrationsDirectory, { recursive: true, force: true }); } }); it("replaces the last section in the document", async () => { const integrationsDirectory = createPluginDocsFixture("dropbox", { "README.md": [ "# Dropbox", "", "## First", "First content", "", "## Last", "Old last content", "More old last content", ].join("\n"), "docs.manifest.json": JSON.stringify({ overlays: [ { file: "overlays/replace-last.md", versionRange: ">=0.4.0" }, ], }), "overlays/replace-last.md": [ "## @replace: Last", "New last content", ].join("\n"), }); try { const docs = await resolveIntegrationDocumentation("dropbox", { pluginVersion: "0.4.0", integrationsDirectory, }); expect(docs).toContain("## Last\nNew last content"); expect(docs).toContain("## First\nFirst content"); expect(docs).not.toContain("Old last content"); } finally { rmSync(integrationsDirectory, { recursive: true, force: true }); } }); it("respects heading hierarchy when replacing nested sections", async () => { const integrationsDirectory = createPluginDocsFixture("dropbox", { "README.md": [ "# Dropbox", "", "## Auth", "Auth intro", "", "### OAuth", "OAuth details", "", "### API Key", "API key details", "", "## Usage", "Usage content", ].join("\n"), "docs.manifest.json": JSON.stringify({ overlays: [ { file: "overlays/replace-oauth.md", versionRange: ">=0.4.0" }, ], }), "overlays/replace-oauth.md": [ "### @replace: OAuth", "New OAuth flow", ].join("\n"), }); try { const docs = await resolveIntegrationDocumentation("dropbox", { pluginVersion: "0.4.0", integrationsDirectory, }); expect(docs).toContain("### OAuth\nNew OAuth flow"); expect(docs).toContain("### API Key\nAPI key details"); expect(docs).toContain("## Usage\nUsage content"); expect(docs).not.toContain("OAuth details"); } finally { rmSync(integrationsDirectory, { recursive: true, force: true }); } }); it("ignores empty overlay content (whitespace only)", async () => { const integrationsDirectory = createPluginDocsFixture("dropbox", { "README.md": "base-docs", "docs.manifest.json": JSON.stringify({ overlays: [{ file: "overlays/empty.md", versionRange: ">=0.4.0" }], }), "overlays/empty.md": " \n \n ", }); try { const docs = await resolveIntegrationDocumentation("dropbox", { pluginVersion: "0.5.0", integrationsDirectory, }); expect(docs).toBe("base-docs"); } finally { rmSync(integrationsDirectory, { recursive: true, force: true }); } }); it("resolves documentation for aliased plugin IDs", async () => { const integrationsDirectory = createPluginDocsFixture("graphql", { "README.md": "graphql-docs", }); try { const docs = await resolveIntegrationDocumentation("graphqlintegration", { integrationsDirectory, }); expect(docs).toBe("graphql-docs"); } finally { rmSync(integrationsDirectory, { recursive: true, force: true }); } }); describe("semver matching", () => { it("matches exact version with = operator", async () => { const integrationsDirectory = createPluginDocsFixture("dropbox", { "README.md": "base", "docs.manifest.json": JSON.stringify({ overlays: [{ file: "overlays/exact.md", versionRange: "=0.4.0" }], }), "overlays/exact.md": "exact-match", }); try { expect( await resolveIntegrationDocumentation("dropbox", { pluginVersion: "0.4.0", integrationsDirectory, }), ).toBe("base\n\nexact-match"); expect( await resolveIntegrationDocumentation("dropbox", { pluginVersion: "0.4.1", integrationsDirectory, }), ).toBe("base"); } finally { rmSync(integrationsDirectory, { recursive: true, force: true }); } }); it("matches strict less-than with < operator", async () => { const integrationsDirectory = createPluginDocsFixture("dropbox", { "README.md": "base", "docs.manifest.json": JSON.stringify({ overlays: [{ file: "overlays/legacy.md", versionRange: "<1.0.0" }], }), "overlays/legacy.md": "legacy-overlay", }); try { expect( await resolveIntegrationDocumentation("dropbox", { pluginVersion: "0.9.9", integrationsDirectory, }), ).toBe("base\n\nlegacy-overlay"); expect( await resolveIntegrationDocumentation("dropbox", { pluginVersion: "1.0.0", integrationsDirectory, }), ).toBe("base"); } finally { rmSync(integrationsDirectory, { recursive: true, force: true }); } }); it("matches <= operator at boundary", async () => { const integrationsDirectory = createPluginDocsFixture("dropbox", { "README.md": "base", "docs.manifest.json": JSON.stringify({ overlays: [{ file: "overlays/lte.md", versionRange: "<=1.0.0" }], }), "overlays/lte.md": "lte-overlay", }); try { expect( await resolveIntegrationDocumentation("dropbox", { pluginVersion: "1.0.0", integrationsDirectory, }), ).toBe("base\n\nlte-overlay"); expect( await resolveIntegrationDocumentation("dropbox", { pluginVersion: "1.0.1", integrationsDirectory, }), ).toBe("base"); } finally { rmSync(integrationsDirectory, { recursive: true, force: true }); } }); it("matches > operator", async () => { const integrationsDirectory = createPluginDocsFixture("dropbox", { "README.md": "base", "docs.manifest.json": JSON.stringify({ overlays: [{ file: "overlays/gt.md", versionRange: ">1.0.0" }], }), "overlays/gt.md": "gt-overlay", }); try { expect( await resolveIntegrationDocumentation("dropbox", { pluginVersion: "1.0.0", integrationsDirectory, }), ).toBe("base"); expect( await resolveIntegrationDocumentation("dropbox", { pluginVersion: "1.0.1", integrationsDirectory, }), ).toBe("base\n\ngt-overlay"); } finally { rmSync(integrationsDirectory, { recursive: true, force: true }); } }); it("matches compound range with multiple comparators", async () => { const integrationsDirectory = createPluginDocsFixture("dropbox", { "README.md": "base", "docs.manifest.json": JSON.stringify({ overlays: [ { file: "overlays/range.md", versionRange: ">=1.0.0 <2.0.0" }, ], }), "overlays/range.md": "range-overlay", }); try { expect( await resolveIntegrationDocumentation("dropbox", { pluginVersion: "0.9.9", integrationsDirectory, }), ).toBe("base"); expect( await resolveIntegrationDocumentation("dropbox", { pluginVersion: "1.5.0", integrationsDirectory, }), ).toBe("base\n\nrange-overlay"); expect( await resolveIntegrationDocumentation("dropbox", { pluginVersion: "2.0.0", integrationsDirectory, }), ).toBe("base"); } finally { rmSync(integrationsDirectory, { recursive: true, force: true }); } }); it("handles spaced operators in version ranges", async () => { const integrationsDirectory = createPluginDocsFixture("dropbox", { "README.md": "base", "docs.manifest.json": JSON.stringify({ overlays: [ { file: "overlays/spaced.md", versionRange: ">= 0.4.0 < 1.0.0" }, ], }), "overlays/spaced.md": "spaced-overlay", }); try { expect( await resolveIntegrationDocumentation("dropbox", { pluginVersion: "0.3.9", integrationsDirectory, }), ).toBe("base"); expect( await resolveIntegrationDocumentation("dropbox", { pluginVersion: "0.5.0", integrationsDirectory, }), ).toBe("base\n\nspaced-overlay"); expect( await resolveIntegrationDocumentation("dropbox", { pluginVersion: "1.0.0", integrationsDirectory, }), ).toBe("base"); } finally { rmSync(integrationsDirectory, { recursive: true, force: true }); } }); }); describe("manifest validation", () => { it("throws when overlays key is not an array", async () => { const integrationsDirectory = createPluginDocsFixture("dropbox", { "README.md": "base", "docs.manifest.json": JSON.stringify({ overlays: "not-an-array" }), }); try { await expect( resolveIntegrationDocumentation("dropbox", { pluginVersion: "0.4.0", integrationsDirectory, }), ).rejects.toThrowError(/overlays.*must be an array/i); } finally { rmSync(integrationsDirectory, { recursive: true, force: true }); } }); it("throws when overlay entry has empty file", async () => { const integrationsDirectory = createPluginDocsFixture("dropbox", { "README.md": "base", "docs.manifest.json": JSON.stringify({ overlays: [{ file: " ", versionRange: ">=0.4.0" }], }), }); try { await expect( resolveIntegrationDocumentation("dropbox", { pluginVersion: "0.4.0", integrationsDirectory, }), ).rejects.toThrowError(/Invalid "file"/); } finally { rmSync(integrationsDirectory, { recursive: true, force: true }); } }); it("throws when overlay entry has neither versionRange nor sdkVersionRange", async () => { const integrationsDirectory = createPluginDocsFixture("dropbox", { "README.md": "base", "docs.manifest.json": JSON.stringify({ overlays: [{ file: "overlays/01.md" }], }), }); try { await expect( resolveIntegrationDocumentation("dropbox", { pluginVersion: "0.4.0", integrationsDirectory, }), ).rejects.toThrowError(/Invalid overlay entry/); } finally { rmSync(integrationsDirectory, { recursive: true, force: true }); } }); it("throws when overlay entry has empty versionRange and no sdkVersionRange", async () => { const integrationsDirectory = createPluginDocsFixture("dropbox", { "README.md": "base", "docs.manifest.json": JSON.stringify({ overlays: [{ file: "overlays/01.md", versionRange: " " }], }), }); try { await expect( resolveIntegrationDocumentation("dropbox", { pluginVersion: "0.4.0", integrationsDirectory, }), ).rejects.toThrowError(/Invalid "versionRange"/); } finally { rmSync(integrationsDirectory, { recursive: true, force: true }); } }); }); describe("sdkVersionRange", () => { it("applies overlay when sdkVersion matches sdkVersionRange", async () => { const integrationsDirectory = createPluginDocsFixture("graphql", { "README.md": "base-graphql-docs", "docs.manifest.json": JSON.stringify({ overlays: [ { file: "overlays/headers.md", sdkVersionRange: ">=0.0.2" }, ], }), "overlays/headers.md": "dynamic-headers-docs", }); try { const docs = await resolveIntegrationDocumentation("graphql", { sdkVersion: "0.0.2", integrationsDirectory, }); expect(docs).toBe("base-graphql-docs\n\ndynamic-headers-docs"); } finally { rmSync(integrationsDirectory, { recursive: true, force: true }); } }); it("skips overlay when sdkVersion does not match sdkVersionRange", async () => { const integrationsDirectory = createPluginDocsFixture("graphql", { "README.md": "base-graphql-docs", "docs.manifest.json": JSON.stringify({ overlays: [ { file: "overlays/headers.md", sdkVersionRange: ">=0.0.2" }, ], }), "overlays/headers.md": "dynamic-headers-docs", }); try { const docs = await resolveIntegrationDocumentation("graphql", { sdkVersion: "0.0.1", integrationsDirectory, }); expect(docs).toBe("base-graphql-docs"); } finally { rmSync(integrationsDirectory, { recursive: true, force: true }); } }); it("skips overlay when sdkVersion is not provided", async () => { const integrationsDirectory = createPluginDocsFixture("graphql", { "README.md": "base-graphql-docs", "docs.manifest.json": JSON.stringify({ overlays: [ { file: "overlays/headers.md", sdkVersionRange: ">=0.0.2" }, ], }), "overlays/headers.md": "dynamic-headers-docs", }); try { const docs = await resolveIntegrationDocumentation("graphql", { pluginVersion: "0.0.10", integrationsDirectory, }); expect(docs).toBe("base-graphql-docs"); } finally { rmSync(integrationsDirectory, { recursive: true, force: true }); } }); it("requires both versionRange and sdkVersionRange to match when both are specified", async () => { const integrationsDirectory = createPluginDocsFixture("graphql", { "README.md": "base", "docs.manifest.json": JSON.stringify({ overlays: [ { file: "overlays/both.md", versionRange: ">=0.0.10", sdkVersionRange: ">=0.0.2", }, ], }), "overlays/both.md": "both-match-overlay", }); try { // Both match expect( await resolveIntegrationDocumentation("graphql", { pluginVersion: "0.0.10", sdkVersion: "0.0.2", integrationsDirectory, }), ).toBe("base\n\nboth-match-overlay"); // Only plugin matches expect( await resolveIntegrationDocumentation("graphql", { pluginVersion: "0.0.10", sdkVersion: "0.0.1", integrationsDirectory, }), ).toBe("base"); // Only sdk matches expect( await resolveIntegrationDocumentation("graphql", { pluginVersion: "0.0.9", sdkVersion: "0.0.2", integrationsDirectory, }), ).toBe("base"); } finally { rmSync(integrationsDirectory, { recursive: true, force: true }); } }); it("allows overlay with only sdkVersionRange (no versionRange)", async () => { const integrationsDirectory = createPluginDocsFixture("graphql", { "README.md": "base", "docs.manifest.json": JSON.stringify({ overlays: [ { file: "overlays/sdk-only.md", sdkVersionRange: ">=0.0.2" }, ], }), "overlays/sdk-only.md": "sdk-gated-overlay", }); try { // No pluginVersion needed — only sdkVersion matters const docs = await resolveIntegrationDocumentation("graphql", { sdkVersion: "0.0.3", integrationsDirectory, }); expect(docs).toBe("base\n\nsdk-gated-overlay"); } finally { rmSync(integrationsDirectory, { recursive: true, force: true }); } }); }); describe("restapiintegration responseType gating (real docs)", () => { it("documents text responseType for agents whose sdk-api supports text only", async () => { const docs = await resolveIntegrationDocumentation("restapiintegration", { sdkVersion: "0.0.3", }); expect(docs).toContain('responseType: "text"'); expect(docs).not.toContain('responseType: "binary"'); expect(docs).not.toContain("does not support the `responseType`"); }); it("documents binary responseType only for agents whose sdk-api supports it", async () => { const docs = await resolveIntegrationDocumentation("restapiintegration", { sdkVersion: "0.0.4", }); expect(docs).toContain('responseType: "text"'); expect(docs).toContain('responseType: "binary"'); expect(docs).not.toContain("does not support the `responseType`"); }); it("notes non-support instead of documenting responseType on older agents", async () => { const docs = await resolveIntegrationDocumentation("restapiintegration", { sdkVersion: "0.0.2", }); expect(docs).toContain("does not support the `responseType`"); expect(docs).not.toContain('responseType: "text"'); expect(docs).not.toContain('responseType: "binary"'); }); it("stays silent about responseType when the agent reports no sdk-api version", async () => { const docs = await resolveIntegrationDocumentation("restapiintegration"); expect(docs).not.toContain("responseType"); }); }); it("blocks overlay paths outside the plugin directory", async () => { const integrationsDirectory = createPluginDocsFixture("dropbox", { "README.md": "base", "docs.manifest.json": JSON.stringify({ overlays: [ { file: "../other-plugin/secret.md", versionRange: ">=0.4.0" }, ], }), }); try { await expect( resolveIntegrationDocumentation("dropbox", { pluginVersion: "0.4.0", integrationsDirectory, }), ).rejects.toThrowError(/must be inside plugin directory/i); } finally { rmSync(integrationsDirectory, { recursive: true, force: true }); } }); });