deer-flow/frontend/tests/unit/content/docs-links.test.ts
Wu Shuwen 5860423eb6
docs: align custom agent naming with API (#4944)
* docs: align custom agent naming with API

* docs: document custom agent API gate

* docs: correct custom agent storage guidance

* docs: correct custom agent config path

* docs: clarify custom agent name scope

* docs: align agent storage placeholder

* docs: clarify custom agent file updates

* docs: qualify custom agent storage

* docs: clarify agent database storage

* Fix formatting in docs-links.test.ts

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-09-02 10:14:45 +08:00

139 lines
4.6 KiB
TypeScript

import { readFileSync, readdirSync } from "node:fs";
import { join, relative, sep } from "node:path";
import { describe, expect, it } from "@rstest/core";
const CONTENT_ROOT = join(process.cwd(), "src/content");
const DOC_LANGUAGES = ["en", "zh"] as const;
const DOCS_ORIGIN = "https://docs.example";
const LINK_PATTERN =
/(?<!!)\[[^\]]*\]\(\s*(?:<([^>]+)>|([^\s)]+))(?:\s+["'][^"']*["'])?\s*\)|\bhref\s*=\s*(?:\{\s*)?["']([^"']+)["'](?:\s*\})?/g;
const UNLOCALIZED_DOCS_PATH = /^\/docs(?=\/|[?#]|$)/;
function findMdxFiles(directory: string): string[] {
return readdirSync(directory, { withFileTypes: true }).flatMap((entry) => {
const path = join(directory, entry.name);
return entry.isDirectory()
? findMdxFiles(path)
: entry.name.endsWith(".mdx")
? [path]
: [];
});
}
function routeForMdx(path: string, lang: string): string {
const localeRoot = join(CONTENT_ROOT, lang);
const relativePath = relative(localeRoot, path).split(sep).join("/");
const pagePath = relativePath
.replace(/\.mdx$/, "")
.replace(/(?:^|\/)index$/, "");
return `/${lang}/docs${pagePath ? `/${pagePath}` : ""}`;
}
function extractLinks(source: string): Array<{ href: string; line: number }> {
return [...source.matchAll(LINK_PATTERN)].map((match) => ({
href: match[1] ?? match[2] ?? match[3] ?? "",
line: source.slice(0, match.index).split("\n").length,
}));
}
function resolveDocsPath(
href: string,
lang: string,
sourceRoute: string,
): string | undefined {
const localizedHref = UNLOCALIZED_DOCS_PATH.test(href)
? `/${lang}${href}`
: href;
const url = new URL(localizedHref, `${DOCS_ORIGIN}${sourceRoute}`);
if (
url.origin !== DOCS_ORIGIN ||
!/^\/(?:en|zh)\/docs(?:\/|$)/.test(url.pathname)
) {
return undefined;
}
return url.pathname.replace(/\/$/, "");
}
describe("documentation content links", () => {
it("only links to documentation routes backed by MDX pages", () => {
const mdxFiles = DOC_LANGUAGES.flatMap((lang) =>
findMdxFiles(join(CONTENT_ROOT, lang)),
);
const routes = new Set(
DOC_LANGUAGES.flatMap((lang) =>
mdxFiles
.filter((path) => path.startsWith(join(CONTENT_ROOT, lang)))
.map((path) => routeForMdx(path, lang)),
),
);
const brokenLinks = DOC_LANGUAGES.flatMap((lang) =>
findMdxFiles(join(CONTENT_ROOT, lang)).flatMap((path) => {
const source = readFileSync(path, "utf8");
const sourceRoute = routeForMdx(path, lang);
return extractLinks(source).flatMap(({ href, line }) => {
const targetRoute = resolveDocsPath(href, lang, sourceRoute);
if (!targetRoute || routes.has(targetRoute)) {
return [];
}
return [
`${relative(CONTENT_ROOT, path).split(sep).join("/")}:${line} -> ${href} (${targetRoute})`,
];
});
}),
);
expect(brokenLinks).toEqual([]);
});
it("documents the custom-agent create request naming contract", () => {
const customAgentApiDocs = DOC_LANGUAGES.map((lang) =>
readFileSync(
join(CONTENT_ROOT, lang, "application/agents-and-threads.mdx"),
"utf8",
),
);
const customAgentDocs = [
...customAgentApiDocs,
...DOC_LANGUAGES.map((lang) =>
readFileSync(
join(CONTENT_ROOT, lang, "harness/lead-agent.mdx"),
"utf8",
),
),
];
for (const source of customAgentDocs) {
expect(source).toContain("agents_api.enabled");
expect(source).toContain("agent_storage.backend: file");
expect(source).toContain("agent_storage.backend: db");
expect(source).toContain("database.backend: sqlite");
expect(source).toContain("database.backend: postgres");
expect(source).toContain("backend/scripts/migrate_agents_to_db.py");
expect(source).toContain("users/{user_id}/agents/{name}/config.yaml");
expect(source).not.toContain("display_name");
}
for (const source of customAgentApiDocs) {
expect(source).toContain("^[A-Za-z0-9-]+$");
expect(source).toContain("backend/scripts/migrate_user_isolation.py");
}
const chineseApiGuide = readFileSync(
join(CONTENT_ROOT, "zh/application/agents-and-threads.mdx"),
"utf8",
);
expect(chineseApiGuide).toContain('"name": "data-analyst"');
expect(chineseApiGuide).toContain("下一次 Agent 调用时自动加载,无需重启");
const englishApiGuide = readFileSync(
join(CONTENT_ROOT, "en/application/agents-and-threads.mdx"),
"utf8",
);
expect(englishApiGuide).toContain(
"changes are picked up on the agent's next invocation",
);
});
});