Files
docs/tests/unit_tests/test_builder.py
Florence Morris 2f9e4c6723 Reconcile MDA docs with public beta changes (#5329)
Follow-up cleanup branch for PR #5262, based on the current
`fjmorris/DOC-1403` branch. These commits were verified against
`managed-deepagents==0.5.0.dev89`.

Commit 1 updates the Managed Deep Agents docs to match the public beta
identity model: identity scopes memory and credentials, threads are
always user-owned, and organization-scoped identity/memory is no longer
documented. It removes the outdated identity scope explorer and updates
related MDA docs and snippets.

Commit 2 updates the Slack connector docs for BYOK app setup: users
bring their own Slack app, configure `SLACK_SIGNING_SECRET` and
`SLACK_BOT_TOKEN`, and point Events API requests at
`/connectors/slack/events`. It removes stale managed Slack app
provisioning claims from the Slack connector page, connector overview,
CLI reference, and deploy docs.

Commit 3 refreshes the integrations catalog and `mda connect` docs for
the current public beta behavior: the catalog now matches `mda connect
--list`, includes marketplace MCP connectors such as Notion, Cloudflare,
Neon, Netlify, Prisma, Vanta, and Close, and documents OAuth,
secret-backed, credential-free, and BYOK Slack setup. It also updates
the CLI flag table and uses `LANGSMITH_WORKSPACE_ID` for tool server
routing.

Commit 4 reconciles the CLI reference with the current public beta
command surface: it adds `mda build`, `mda logs`, and `mda delete` /
`mda destroy`, documents current `mda init` flags, fixes `mda dev
--no-browser`, removes stale `mda deploy --host-url`, and corrects
deploy/log/delete defaults.

Commit 5 runs a cross-page consistency cleanup after the identity,
Slack, integrations, and CLI updates: it removes stale channels
terminology from the overview, replaces tenant wording with organization
wording, and cleans up generic ingress language while preserving literal
`MDA_INGRESS_SECRET` and `X-MDA-Ingress-Secret` names.

Commit 6 fixes the MDA identity page markup found during validation by
removing duplicate closing `Card` / `CardGroup` tags. Validation passed
with `make lint_prose`, `make broken-links`, and `make
broken-links-with-anchors`.

Commit 7 updates the MDA install and release-stage wording: Python
install examples now use `uv tool install --prerelease allow
managed-deepagents`, and access/release-stage copy now says public beta
instead of private beta across the overview, deploy, middleware,
quickstart, and shared snippets.

---------

Co-authored-by: Christian Bromann <git@bromann.dev>
Co-authored-by: Harrison Chase <hw.chase.17@gmail.com>
Co-authored-by: victorm-lc <victor@langchain.dev>
2026-08-07 07:35:55 -07:00

667 lines
24 KiB
Python

"""Tests for the DocumentationBuilder class.
This module contains comprehensive tests for the DocumentationBuilder class,
covering all methods and edge cases including file extension handling,
directory structure preservation, and error conditions.
"""
from pathlib import Path
import pytest
from pipeline.core.builder import DocumentationBuilder
from tests.unit_tests.utils import File, file_system
def test_builder_initialization() -> None:
"""Test DocumentationBuilder initialization.
Verifies that the builder is correctly initialized with the provided
source and build directories, and that the copy_extensions set contains
the expected file extensions.
"""
with file_system([]) as fs:
builder = DocumentationBuilder(fs.src_dir, fs.build_dir)
assert builder.src_dir == fs.src_dir
assert builder.build_dir == fs.build_dir
assert builder.copy_extensions == {
".mdx",
".md",
".json",
".svg",
".png",
".jpg",
".jpeg",
".gif",
".mp4",
".webm",
".yml",
".yaml",
".css",
".js",
".jsx",
".tsx",
".txt",
".woff2",
".woff",
".ttf",
".html",
}
def test_build_all_empty_directory() -> None:
"""Test building from an empty directory.
Verifies that the builder handles empty source directories correctly.
"""
with file_system([]) as fs:
builder = DocumentationBuilder(fs.src_dir, fs.build_dir)
builder.build_all()
assert not fs.list_build_files()
def test_build_all_copies_tsx_snippets() -> None:
"""Test that local TSX snippet components are copied to build/snippets."""
files = [
File(
path="snippets/example-component.tsx",
content="export default function Example() { return null; }",
),
]
with file_system(files) as fs:
builder = DocumentationBuilder(fs.src_dir, fs.build_dir)
builder.build_all()
assert fs.build_file_exists("snippets/example-component.tsx")
def test_build_all_supported_files() -> None:
"""Test building all supported file types.
Verifies that the builder correctly copies all supported file types
while maintaining directory structure.
"""
files = [
# LangGraph (oss) files - both Python and JavaScript versions
File(path="oss/index.mdx", content="# Welcome"),
File(path="oss/config.json", content='{"name": "test"}'),
File(path="oss/guides/setup.md", content="# Setup Guide"),
# LangGraph Platform files
File(path="langgraph-platform/index.mdx", content="# Platform"),
File(path="langgraph-platform/guide.md", content="# Guide"),
# LangChain Labs files
File(path="labs/index.mdx", content="# Labs"),
# Shared files
File(path="images/logo.png", bytes=b"PNG_DATA"),
File(path="docs.json", content='{"name": "test"}'),
]
with file_system(files) as fs:
builder = DocumentationBuilder(fs.src_dir, fs.build_dir)
builder.build_all()
# Verify all files were copied with correct structure
build_files = {str(p) for p in fs.list_build_files()}
# Python version of LangGraph files
assert "oss/python/index.mdx" in build_files
assert "oss/python/config.json" in build_files
assert "oss/python/guides/setup.md" in build_files
# JavaScript version of LangGraph files
assert "oss/javascript/index.mdx" in build_files
assert "oss/javascript/config.json" in build_files
assert "oss/javascript/guides/setup.md" in build_files
# LangGraph Platform files
assert "langgraph-platform/index.mdx" in build_files
assert "langgraph-platform/guide.md" in build_files
# LangChain Labs files
assert "labs/index.mdx" in build_files
# Shared files
assert "images/logo.png" in build_files
assert "docs.json" in build_files
# Total number of files should be:
# - 3 files * 2 versions (Python/JavaScript) for LangGraph
# - 2 files for Platform
# - 1 file for Labs
# - 2 shared files
assert len(build_files) == 11
def test_build_all_unsupported_files() -> None:
"""Test building with unsupported file types.
Verifies that the builder skips unsupported file types.
"""
files = [
# LangGraph files with supported and unsupported types
File(
path="oss/index.mdx",
content="# Welcome",
),
File(
path="oss/ignored.txt",
content="This should be ignored",
),
File(
path="oss/data.csv",
content="col1,col2\n1,2",
),
# Platform files with supported and unsupported types
File(
path="langgraph-platform/guide.md",
content="# Guide",
),
File(
path="langgraph-platform/ignored.txt",
content="This should be ignored",
),
# Labs files with supported and unsupported types
File(
path="labs/index.mdx",
content="# Labs",
),
File(
path="labs/data.csv",
content="col1,col2\n1,2",
),
# Shared files with supported and unsupported types
File(
path="images/logo.png",
bytes=b"PNG_DATA",
),
File(
path="ignored.txt",
content="This should be ignored",
),
]
with file_system(files) as fs:
builder = DocumentationBuilder(fs.src_dir, fs.build_dir)
builder.build_all()
# Verify only supported files were copied
build_files = {str(p) for p in fs.list_build_files()}
# Python version of LangGraph files (only .mdx)
assert "oss/python/index.mdx" in build_files
assert "oss/python/ignored.txt" not in build_files
assert "oss/python/data.csv" not in build_files
# JavaScript version of LangGraph files (only .mdx)
assert "oss/javascript/index.mdx" in build_files
assert "oss/javascript/ignored.txt" not in build_files
assert "oss/javascript/data.csv" not in build_files
# Platform files (only .md)
assert "langgraph-platform/guide.md" in build_files
assert "langgraph-platform/ignored.txt" not in build_files
# Labs files (only .mdx)
assert "labs/index.mdx" in build_files
assert "labs/data.csv" not in build_files
# Shared files (only .png)
assert "images/logo.png" in build_files
assert "ignored.txt" not in build_files
# Total number of files should be:
# - 1 file * 2 versions (Python/JavaScript) for LangGraph
# - 1 file for Platform
# - 1 file for Labs
# - 1 shared file
assert len(build_files) == 4
def test_build_single_file() -> None:
"""Test building a single file.
Verifies that the builder correctly copies a single file
when requested.
"""
files = [
File(
path="index.mdx",
content="# Welcome",
),
File(
path="config.json",
content='{"name": "test"}',
),
]
with file_system(files) as fs:
builder = DocumentationBuilder(fs.src_dir, fs.build_dir)
builder.build_file(fs.src_dir / "index.mdx")
# Verify only the requested file was copied
build_files = fs.list_build_files()
assert len(build_files) == 1
assert Path("index.mdx") in build_files
assert not fs.build_file_exists("config.json")
def test_build_multiple_files() -> None:
"""Test building multiple specific files.
Verifies that the builder correctly copies multiple specified files
while maintaining directory structure.
"""
files = [
File(
path="index.mdx",
content="# Welcome",
),
File(
path="config.json",
content='{"name": "test"}',
),
File(
path="guides/setup.md",
content="# Setup Guide",
),
]
with file_system(files) as fs:
builder = DocumentationBuilder(fs.src_dir, fs.build_dir)
builder.build_files(
[
fs.src_dir / "index.mdx",
fs.src_dir / "guides/setup.md",
],
)
# Verify only specified files were copied
build_files = fs.list_build_files()
assert len(build_files) == 2
assert Path("index.mdx") in build_files
assert Path("guides/setup.mdx") in build_files
assert not fs.build_file_exists("config.json")
def test_build_nonexistent_file() -> None:
"""Test building a nonexistent file.
Verifies that the builder handles attempts to build
nonexistent files gracefully.
"""
with file_system([]) as fs:
builder = DocumentationBuilder(fs.src_dir, fs.build_dir)
with pytest.raises(AssertionError):
builder.build_file(fs.src_dir / "nonexistent.md")
def test_rewrite_oss_links_inserts_language() -> None:
"""Bare /oss/ links get the target language inserted after 'oss'."""
with file_system([]) as fs:
builder = DocumentationBuilder(fs.src_dir, fs.build_dir)
content = "[Deep Agents](/oss/deepagents/overview)"
assert (
builder._rewrite_oss_links(content, "python")
== "[Deep Agents](/oss/python/deepagents/overview)"
)
assert (
builder._rewrite_oss_links(content, "js")
== "[Deep Agents](/oss/javascript/deepagents/overview)"
)
def test_rewrite_oss_links_preserves_existing_language() -> None:
"""Links that already specify a language are left untouched.
Regression test: unversioned langsmith pages are built with
target_language="python", so a link that already points at
/oss/python/... or /oss/javascript/... must not have a second
language segment inserted (which produced /oss/python/python/...).
"""
with file_system([]) as fs:
builder = DocumentationBuilder(fs.src_dir, fs.build_dir)
py = "[LangChain](/oss/python/langchain/overview)"
js = "[LangChain](/oss/javascript/langchain/overview)"
# Building langsmith uses target_language="python"; both must survive.
assert builder._rewrite_oss_links(py, "python") == py
assert builder._rewrite_oss_links(js, "python") == js
def test_rewrite_oss_links_preserves_deepagents_code() -> None:
"""Deep Agents Code URLs stay language-agnostic (no python/javascript insert)."""
with file_system([]) as fs:
builder = DocumentationBuilder(fs.src_dir, fs.build_dir)
link = "[Overview](/oss/deepagents/code/overview)"
assert builder._rewrite_oss_links(link, "python") == link
assert builder._rewrite_oss_links(link, "js") == link
# Sibling OSS paths still get the language prefix.
other = "[SDK](/oss/deepagents/quickstart)"
assert (
builder._rewrite_oss_links(other, "python")
== "[SDK](/oss/python/deepagents/quickstart)"
)
def test_unversioned_oss_code_builds_once() -> None:
"""Deep Agents Code pages build to oss/deepagents/code/, not per-language copies."""
files = [
File(
path="oss/deepagents/code/overview.mdx",
content=(
"---\ntitle: Code\n---\n\n"
"See [SDK](/oss/deepagents/quickstart) and "
"[Config](/oss/deepagents/code/configuration).\n"
),
),
File(
path="oss/deepagents/quickstart.mdx",
content="---\ntitle: Quickstart\n---\n\nSDK docs.\n",
),
]
with file_system(files) as fs:
builder = DocumentationBuilder(fs.src_dir, fs.build_dir)
code_src = fs.src_dir / "oss" / "deepagents" / "code" / "overview.mdx"
assert builder.is_unversioned_oss_file(code_src)
assert not builder.is_unversioned_oss_file(
fs.src_dir / "oss" / "deepagents" / "quickstart.mdx"
)
builder.build_file(code_src)
unversioned = fs.build_dir / "oss" / "deepagents" / "code" / "overview.mdx"
assert unversioned.exists()
assert not (
fs.build_dir / "oss" / "python" / "deepagents" / "code" / "overview.mdx"
).exists()
assert not (
fs.build_dir / "oss" / "javascript" / "deepagents" / "code" / "overview.mdx"
).exists()
content = unversioned.read_text()
assert "/oss/python/deepagents/quickstart" in content
assert "/oss/deepagents/code/configuration" in content
assert "/oss/python/deepagents/code/" not in content
def test_unversioned_oss_openwiki_builds_once() -> None:
"""OpenWiki pages build to oss/openwiki/, not per-language copies."""
files = [
File(
path="oss/openwiki/overview.mdx",
content=(
"---\ntitle: OpenWiki\n---\n\n"
"See [SDK](/oss/deepagents/quickstart) and "
"[Quickstart](/oss/openwiki/quickstart).\n"
),
),
File(
path="oss/deepagents/quickstart.mdx",
content="---\ntitle: Quickstart\n---\n\nSDK docs.\n",
),
]
with file_system(files) as fs:
builder = DocumentationBuilder(fs.src_dir, fs.build_dir)
openwiki_src = fs.src_dir / "oss" / "openwiki" / "overview.mdx"
assert builder.is_unversioned_oss_file(openwiki_src)
link = "[Overview](/oss/openwiki/overview)"
assert builder._rewrite_oss_links(link, "python") == link
assert builder._rewrite_oss_links(link, "js") == link
builder.build_file(openwiki_src)
unversioned = fs.build_dir / "oss" / "openwiki" / "overview.mdx"
assert unversioned.exists()
assert not (
fs.build_dir / "oss" / "python" / "openwiki" / "overview.mdx"
).exists()
assert not (
fs.build_dir / "oss" / "javascript" / "openwiki" / "overview.mdx"
).exists()
content = unversioned.read_text()
assert "/oss/python/deepagents/quickstart" in content
assert "/oss/openwiki/quickstart" in content
assert "/oss/python/openwiki/" not in content
def test_safe_source_files_skips_symlinks() -> None:
"""Source collection rejects symlinks so host files cannot be exfiltrated."""
files = [
File(
path="oss/openwiki/overview.mdx",
content="---\ntitle: OpenWiki\n---\n\nOverview.\n",
),
]
with file_system(files) as fs:
openwiki_dir = fs.src_dir / "oss" / "openwiki"
secret_target = fs.temp_dir / "outside-secret.txt"
secret_target.write_text("secret-value\n", encoding="utf-8")
symlink_path = openwiki_dir / "leaked.env"
symlink_path.symlink_to(secret_target)
builder = DocumentationBuilder(fs.src_dir, fs.build_dir)
collected = builder._safe_source_files(openwiki_dir)
assert openwiki_dir / "overview.mdx" in collected
assert symlink_path not in collected
assert all(not path.is_symlink() for path in collected)
builder._build_unversioned_oss_openwiki()
assert not (fs.build_dir / "oss" / "openwiki" / "leaked.env").exists()
overview = fs.build_dir / "oss" / "openwiki" / "overview.mdx"
assert overview.exists()
assert "secret-value" not in overview.read_text()
def test_rewrite_oss_links_skips_images_and_none() -> None:
"""Image paths and a None target language are passed through unchanged."""
with file_system([]) as fs:
builder = DocumentationBuilder(fs.src_dir, fs.build_dir)
img = '<img src="/oss/images/diagram.png" />'
assert builder._rewrite_oss_links(img, "python") == img
link = "[x](/oss/deepagents/overview)"
assert builder._rewrite_oss_links(link, None) == link
def test_rewrite_snippet_imports_for_language() -> None:
"""MDX snippet imports are scoped under /snippets/{python|javascript}/."""
with file_system([]) as fs:
builder = DocumentationBuilder(fs.src_dir, fs.build_dir)
content = (
"import RequiresLanggraphServer from "
"'/snippets/oss/requires-langgraph-server.mdx';\n"
'import { PatternEmbed } from "/snippets/pattern-embed.jsx"\n'
)
assert builder._rewrite_snippet_imports_for_language(content, "python") == (
"import RequiresLanggraphServer from "
"'/snippets/python/oss/requires-langgraph-server.mdx';\n"
'import { PatternEmbed } from "/snippets/pattern-embed.jsx"\n'
)
assert builder._rewrite_snippet_imports_for_language(content, "js") == (
"import RequiresLanggraphServer from "
"'/snippets/javascript/oss/requires-langgraph-server.mdx';\n"
'import { PatternEmbed } from "/snippets/pattern-embed.jsx"\n'
)
already = (
"import X from '/snippets/python/oss/requires-langgraph-server.mdx';\n"
)
assert builder._rewrite_snippet_imports_for_language(already, "js") == already
def test_snippet_oss_links_are_language_prefixed_not_relative() -> None:
"""Shared snippets with /oss/ links get absolute language-prefixed copies.
Regression for nested consumers such as langchain/frontend/branching-chat:
a fixed ``../langgraph/local-server`` relative link resolved incorrectly to
``/oss/{lang}/langchain/langgraph/local-server``.
"""
files = [
File(
path="snippets/oss/requires-langgraph-server.mdx",
content=(
"<Note>\n"
"This feature requires the "
"[LangGraph Agent Server](/oss/langgraph/local-server).\n"
"</Note>\n"
),
),
File(
path="oss/langchain/frontend/branching-chat.mdx",
content=(
"---\ntitle: Branching chat\n---\n\n"
"import RequiresLanggraphServer from "
"'/snippets/oss/requires-langgraph-server.mdx';\n\n"
"<RequiresLanggraphServer />\n"
),
),
]
with file_system(files) as fs:
builder = DocumentationBuilder(fs.src_dir, fs.build_dir)
builder.build_all()
default = (
fs.build_dir / "snippets" / "oss" / "requires-langgraph-server.mdx"
).read_text()
py_snippet = (
fs.build_dir
/ "snippets"
/ "python"
/ "oss"
/ "requires-langgraph-server.mdx"
).read_text()
js_snippet = (
fs.build_dir
/ "snippets"
/ "javascript"
/ "oss"
/ "requires-langgraph-server.mdx"
).read_text()
assert "/oss/python/langgraph/local-server" in default
assert "/oss/python/langgraph/local-server" in py_snippet
assert "/oss/javascript/langgraph/local-server" in js_snippet
assert "../langgraph/local-server" not in default
assert "../langgraph/local-server" not in py_snippet
assert "../langgraph/local-server" not in js_snippet
py_page = (
fs.build_dir
/ "oss"
/ "python"
/ "langchain"
/ "frontend"
/ "branching-chat.mdx"
).read_text()
js_page = (
fs.build_dir
/ "oss"
/ "javascript"
/ "langchain"
/ "frontend"
/ "branching-chat.mdx"
).read_text()
assert "from '/snippets/python/oss/requires-langgraph-server.mdx'" in py_page
assert (
"from '/snippets/javascript/oss/requires-langgraph-server.mdx'" in js_page
)
def test_rewrite_managed_deep_agents_links_inserts_language() -> None:
"""Managed Deep Agents links get the target language route."""
with file_system([]) as fs:
builder = DocumentationBuilder(fs.src_dir, fs.build_dir)
content = (
"[Quickstart](/langsmith/managed-deep-agents-quickstart)\n"
'<Card href="/langsmith/managed-deep-agents-tools#example" />\n'
"[Python](/langsmith/python/managed-deep-agents-overview)"
)
python_content = builder._rewrite_managed_deep_agents_links(content, "python")
assert "/langsmith/python/managed-deep-agents-quickstart" in python_content
assert "/langsmith/python/managed-deep-agents-tools#example" in python_content
assert python_content.count("/langsmith/python/") == 3
js_content = builder._rewrite_managed_deep_agents_links(content, "js")
assert "/langsmith/javascript/managed-deep-agents-quickstart" in js_content
assert "/langsmith/javascript/managed-deep-agents-tools#example" in js_content
assert "/langsmith/python/managed-deep-agents-overview" in js_content
def test_build_all_creates_managed_deep_agents_language_routes() -> None:
"""Managed Deep Agents pages and snippets build for both languages."""
files = [
File(
path="langsmith/managed-deep-agents-overview.mdx",
content=(
"---\ntitle: Managed Deep Agents\n---\n\n"
"import NextSteps from "
"'/snippets/langsmith/managed-deep-agents-next-steps.mdx';\n\n"
"[Quickstart](/langsmith/managed-deep-agents-quickstart)\n\n"
"[Deep Agents](/oss/deepagents/overview)\n"
),
),
File(
path="langsmith/managed-deep-agents-quickstart.mdx",
content="---\ntitle: Quickstart\n---\n",
),
File(
path="snippets/langsmith/managed-deep-agents-next-steps.mdx",
content=(
"[Tools](/langsmith/managed-deep-agents-tools)\n"
"[Deep Agents](/oss/deepagents/overview)\n"
":::python\nPython only.\n:::\n"
":::js\nTypeScript only.\n:::\n"
),
),
]
with file_system(files) as fs:
builder = DocumentationBuilder(fs.src_dir, fs.build_dir)
builder.build_all()
default_page = (
fs.build_dir / "langsmith" / "managed-deep-agents-overview.mdx"
).read_text()
python_page = (
fs.build_dir / "langsmith" / "python" / "managed-deep-agents-overview.mdx"
).read_text()
js_page = (
fs.build_dir
/ "langsmith"
/ "javascript"
/ "managed-deep-agents-overview.mdx"
).read_text()
assert "/langsmith/python/managed-deep-agents-quickstart" in default_page
assert "/langsmith/python/managed-deep-agents-quickstart" in python_page
assert "/langsmith/javascript/managed-deep-agents-quickstart" in js_page
assert "/oss/python/deepagents/overview" in python_page
assert "/oss/javascript/deepagents/overview" in js_page
assert (
"from '/snippets/python/langsmith/managed-deep-agents-next-steps.mdx'"
in python_page
)
assert (
"from '/snippets/javascript/langsmith/managed-deep-agents-next-steps.mdx'"
in js_page
)
python_snippet = (
fs.build_dir
/ "snippets"
/ "python"
/ "langsmith"
/ "managed-deep-agents-next-steps.mdx"
).read_text()
js_snippet = (
fs.build_dir
/ "snippets"
/ "javascript"
/ "langsmith"
/ "managed-deep-agents-next-steps.mdx"
).read_text()
assert "/langsmith/python/managed-deep-agents-tools" in python_snippet
assert "Python only." in python_snippet
assert "TypeScript only." not in python_snippet
assert "/langsmith/javascript/managed-deep-agents-tools" in js_snippet
assert "TypeScript only." in js_snippet
assert "Python only." not in js_snippet