Some checks failed
ClawSweeper Dispatch / dispatch (push) Has been cancelled
CodeQL / Security High (actions) (push) Has been cancelled
CodeQL / Security High (channel-runtime-boundary) (push) Has been cancelled
CodeQL / Security High (core-auth-secrets) (push) Has been cancelled
CodeQL / Security High (mcp-process-tool-boundary) (push) Has been cancelled
CodeQL / Security High (network-ssrf-boundary) (push) Has been cancelled
CodeQL / Security High (plugin-trust-boundary) (push) Has been cancelled
CodeQL / Security High (process-exec-boundary) (push) Has been cancelled
Docs Sync Publish Repo / sync-publish-repo (push) Has been cancelled
Docs / docs (push) Has been cancelled
OpenClaw Stable Main Closeout / Resolve stable release closeout inputs (push) Has been cancelled
OpenClaw Stable Main Closeout / Verify stable main closeout (push) Has been cancelled
Workflow Sanity / no-tabs (push) Has been cancelled
Workflow Sanity / actionlint (push) Has been cancelled
Workflow Sanity / generated-doc-baselines (push) Has been cancelled
CI / runner-admission (push) Has been cancelled
CI / preflight (push) Has been cancelled
CI / security-fast (push) Has been cancelled
CI / pnpm-store-warmup (push) Has been cancelled
CI / build-artifacts (push) Has been cancelled
CI / native-i18n (push) Has been cancelled
CI / ${{ matrix.check_name }} (push) Has been cancelled
CI / ${{ matrix.checkName }} (push) Has been cancelled
CI / checks-node-compat-node22 (push) Has been cancelled
CI / check-bundled-channel-config-metadata (push) Has been cancelled
CI / check-dependencies (push) Has been cancelled
CI / check-guards (push) Has been cancelled
CI / check-lint (push) Has been cancelled
CI / check-prod-types (push) Has been cancelled
CI / check-shrinkwrap (push) Has been cancelled
CI / check-test-types (push) Has been cancelled
CI / check-additional-boundaries-a (push) Has been cancelled
CI / check-additional-boundaries-bcd (push) Has been cancelled
CI / check-additional-extension-bundled (push) Has been cancelled
CI / check-additional-extension-channels (push) Has been cancelled
CI / check-additional-extension-package-boundary (push) Has been cancelled
CI / check-additional-runtime-topology-architecture (push) Has been cancelled
CI / check-session-accessor-boundary (push) Has been cancelled
CI / check-session-transcript-reader-boundary (push) Has been cancelled
CI / check-docs (push) Has been cancelled
CI / skills-python (push) Has been cancelled
CI / macos-swift (push) Has been cancelled
CI / ios-build (push) Has been cancelled
CI / ci-timings-summary (push) Has been cancelled
Native App Locale Refresh / Refresh native fa (push) Has been cancelled
Native App Locale Refresh / Refresh native fr (push) Has been cancelled
Native App Locale Refresh / Refresh native hi (push) Has been cancelled
Native App Locale Refresh / Refresh native id (push) Has been cancelled
Native App Locale Refresh / Refresh native it (push) Has been cancelled
Native App Locale Refresh / Refresh native ja-JP (push) Has been cancelled
Control UI Locale Refresh / plan (push) Has been cancelled
Control UI Locale Refresh / Refresh ${{ matrix.locale }} (push) Has been cancelled
Control UI Locale Refresh / Commit control UI locale refresh (push) Has been cancelled
Live Media Runner Image / Build live media runner image (push) Has been cancelled
Native App Locale Refresh / Refresh native ar (push) Has been cancelled
Native App Locale Refresh / Refresh native de (push) Has been cancelled
Native App Locale Refresh / Refresh native es (push) Has been cancelled
Native App Locale Refresh / Refresh native ko (push) Has been cancelled
Native App Locale Refresh / Refresh native nl (push) Has been cancelled
Native App Locale Refresh / Refresh native pl (push) Has been cancelled
Native App Locale Refresh / Refresh native pt-BR (push) Has been cancelled
Native App Locale Refresh / Refresh native ru (push) Has been cancelled
Native App Locale Refresh / Refresh native sv (push) Has been cancelled
Native App Locale Refresh / Refresh native th (push) Has been cancelled
Native App Locale Refresh / Refresh native tr (push) Has been cancelled
Native App Locale Refresh / Refresh native uk (push) Has been cancelled
Native App Locale Refresh / Refresh native vi (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-CN (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-TW (push) Has been cancelled
Native App Locale Refresh / Commit native locale refresh (push) Has been cancelled
Plugin Init Scaffold Validation / Validate provider scaffold (push) Has been cancelled
Plugin NPM Release / preview_plugins_npm (push) Has been cancelled
Plugin NPM Release / Validate release publish approval (push) Has been cancelled
Plugin NPM Release / preview_plugin_pack (push) Has been cancelled
Plugin NPM Release / publish_plugins_npm (push) Has been cancelled
Sandbox Common Smoke / sandbox-common-smoke (push) Has been cancelled
Website Installer Sync / static (push) Has been cancelled
Website Installer Sync / linux-docker (push) Has been cancelled
Website Installer Sync / macos-installer (push) Has been cancelled
Website Installer Sync / windows-installer (push) Has been cancelled
Website Installer Sync / sync-website (push) Has been cancelled
Adolf is a fork/vendored clone of github.com/openclaw/openclaw (v2026.6.11), free to diverge. Tree copied sans upstream .git; upstream remote added for future syncs. Node pinned to 24 (.nvmrc); engines already require >=22.19. Preserves docs/ARCHITECTURE.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LeqyaxJF2nbRXJtae2kNB2
292 lines
9.3 KiB
Go
292 lines
9.3 KiB
Go
package main
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestPostprocessLocalizedDocsFixesStaleLinksAfterLaterPagesExist(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
docsRoot := t.TempDir()
|
|
writeFile(t, filepath.Join(docsRoot, "docs.json"), `{"redirects":[]}`)
|
|
writeFile(t, filepath.Join(docsRoot, "gateway", "index.md"), "# Gateway\n")
|
|
writeFile(t, filepath.Join(docsRoot, "gateway", "troubleshooting.md"), "# Troubleshooting\n")
|
|
writeFile(t, filepath.Join(docsRoot, "zh-CN", "gateway", "index.md"), stringsJoin(
|
|
"---",
|
|
"title: 网关",
|
|
"x-i18n:",
|
|
" source_hash: test",
|
|
"---",
|
|
"",
|
|
"See [Troubleshooting](/gateway/troubleshooting).",
|
|
))
|
|
writeFile(t, filepath.Join(docsRoot, "zh-CN", "gateway", "troubleshooting.md"), stringsJoin(
|
|
"---",
|
|
"title: 故障排除",
|
|
"x-i18n:",
|
|
" source_hash: test",
|
|
"---",
|
|
"",
|
|
"# 故障排除",
|
|
))
|
|
|
|
if err := postprocessLocalizedDocs(docsRoot, "zh-CN", []string{
|
|
filepath.Join(docsRoot, "zh-CN", "gateway", "index.md"),
|
|
filepath.Join(docsRoot, "zh-CN", "gateway", "troubleshooting.md"),
|
|
}); err != nil {
|
|
t.Fatalf("postprocessLocalizedDocs failed: %v", err)
|
|
}
|
|
|
|
got := mustReadFile(t, filepath.Join(docsRoot, "zh-CN", "gateway", "index.md"))
|
|
if !strings.Contains(got, "---\ntitle: 网关\nx-i18n:\n source_hash: test\n postprocess_version: "+localizedLinkPostprocessVersion+"\n---\n\n") {
|
|
t.Fatalf("front matter corrupted after rewrite:\n%s", got)
|
|
}
|
|
want := "See [Troubleshooting](/zh-CN/gateway/troubleshooting)."
|
|
if !containsLine(got, want) {
|
|
t.Fatalf("expected rewritten localized link %q in output:\n%s", want, got)
|
|
}
|
|
}
|
|
|
|
func TestPostprocessLocalizedDocsRewritesPublishedPageLinksForEachLocale(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
lang string
|
|
title string
|
|
wantPrefix string
|
|
}{
|
|
{name: "zh-CN", lang: "zh-CN", title: "网关", wantPrefix: "/zh-CN"},
|
|
{name: "ja-JP", lang: "ja-JP", title: "ゲートウェイ", wantPrefix: "/ja-JP"},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tt := tt
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
docsRoot := t.TempDir()
|
|
writeFile(t, filepath.Join(docsRoot, "docs.json"), `{"redirects":[]}`)
|
|
writeFile(t, filepath.Join(docsRoot, "gateway", "index.md"), "# Gateway\n")
|
|
writeFile(t, filepath.Join(docsRoot, "gateway", "troubleshooting.md"), "# Troubleshooting\n")
|
|
writeFile(t, filepath.Join(docsRoot, "providers", "example-provider.md"), "# Example provider\n")
|
|
writeFile(t, filepath.Join(docsRoot, tt.lang, "gateway", "troubleshooting.md"), "# Localized troubleshooting\n")
|
|
writeFile(t, filepath.Join(docsRoot, tt.lang, "providers", "example-provider.md"), "# Localized example provider\n")
|
|
|
|
pagePath := filepath.Join(docsRoot, tt.lang, "gateway", "index.md")
|
|
writeFile(t, pagePath, stringsJoin(
|
|
"---",
|
|
"title: "+tt.title,
|
|
"x-i18n:",
|
|
" source_hash: test",
|
|
"---",
|
|
"",
|
|
"See [Troubleshooting](/gateway/troubleshooting).",
|
|
"",
|
|
"See [Example provider](/providers/example-provider).",
|
|
"",
|
|
`<Card href="/gateway/troubleshooting" title="Troubleshooting" />`,
|
|
`<Card href="`+tt.wantPrefix+`/providers/example-provider" title="Example provider" />`,
|
|
))
|
|
|
|
if err := postprocessLocalizedDocs(docsRoot, tt.lang, []string{pagePath}); err != nil {
|
|
t.Fatalf("postprocessLocalizedDocs failed: %v", err)
|
|
}
|
|
|
|
got := mustReadFile(t, pagePath)
|
|
expectedLinks := []string{
|
|
"See [Troubleshooting](" + tt.wantPrefix + "/gateway/troubleshooting).",
|
|
"See [Example provider](" + tt.wantPrefix + "/providers/example-provider).",
|
|
`<Card href="` + tt.wantPrefix + `/gateway/troubleshooting" title="Troubleshooting" />`,
|
|
`<Card href="` + tt.wantPrefix + `/providers/example-provider" title="Example provider" />`,
|
|
}
|
|
for _, want := range expectedLinks {
|
|
if !containsLine(got, want) {
|
|
t.Fatalf("expected rewritten link %q in output:\n%s", want, got)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestPostprocessLocalizedDocsDoesNotTreatThreeLetterSourceDirsAsLocales(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
docsRoot := t.TempDir()
|
|
writeFile(t, filepath.Join(docsRoot, "docs.json"), `{"redirects":[]}`)
|
|
writeFile(t, filepath.Join(docsRoot, "cli", "index.md"), "# CLI\n")
|
|
writeFile(t, filepath.Join(docsRoot, "cli", "AGENTS.md"), "# CLI docs guide\n")
|
|
writeFile(t, filepath.Join(docsRoot, "web", "index.md"), "# Web\n")
|
|
writeFile(t, filepath.Join(docsRoot, "zh-CN", "AGENTS.md"), "# zh-CN\n")
|
|
writeFile(t, filepath.Join(docsRoot, "zh-CN", ".i18n", "README.md"), "# zh-CN i18n\n")
|
|
writeFile(t, filepath.Join(docsRoot, "zh-CN", "cli", "index.md"), "# CLI 本地化\n")
|
|
writeFile(t, filepath.Join(docsRoot, "zh-CN", "web", "index.md"), "# Web 本地化\n")
|
|
|
|
pagePath := filepath.Join(docsRoot, "zh-CN", "gateway", "index.md")
|
|
writeFile(t, pagePath, stringsJoin(
|
|
"---",
|
|
"title: 网关",
|
|
"x-i18n:",
|
|
" source_hash: test",
|
|
"---",
|
|
"",
|
|
"See [CLI](/cli).",
|
|
"",
|
|
"See [Web](/web).",
|
|
))
|
|
|
|
if err := postprocessLocalizedDocs(docsRoot, "zh-CN", []string{pagePath}); err != nil {
|
|
t.Fatalf("postprocessLocalizedDocs failed: %v", err)
|
|
}
|
|
|
|
got := mustReadFile(t, pagePath)
|
|
for _, want := range []string{
|
|
"See [CLI](/zh-CN/cli).",
|
|
"See [Web](/zh-CN/web).",
|
|
} {
|
|
if !containsLine(got, want) {
|
|
t.Fatalf("expected rewritten link %q in output:\n%s", want, got)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestPostprocessLocalizedDocsOnlyTouchesScopedFiles(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
docsRoot := t.TempDir()
|
|
writeFile(t, filepath.Join(docsRoot, "docs.json"), `{"redirects":[]}`)
|
|
writeFile(t, filepath.Join(docsRoot, "gateway", "troubleshooting.md"), "# Troubleshooting\n")
|
|
writeFile(t, filepath.Join(docsRoot, "zh-CN", "gateway", "troubleshooting.md"), "# 故障排除\n")
|
|
|
|
scopedPath := filepath.Join(docsRoot, "zh-CN", "gateway", "index.md")
|
|
unscopedPath := filepath.Join(docsRoot, "zh-CN", "help", "index.md")
|
|
|
|
writeFile(t, scopedPath, stringsJoin(
|
|
"---",
|
|
"title: 网关",
|
|
"---",
|
|
"",
|
|
"See [Troubleshooting](/gateway/troubleshooting).",
|
|
))
|
|
writeFile(t, unscopedPath, stringsJoin(
|
|
"---",
|
|
"title: 帮助",
|
|
"---",
|
|
"",
|
|
"See [Troubleshooting](/gateway/troubleshooting).",
|
|
))
|
|
|
|
beforeUnscoped := mustReadFile(t, unscopedPath)
|
|
if err := postprocessLocalizedDocs(docsRoot, "zh-CN", []string{scopedPath}); err != nil {
|
|
t.Fatalf("postprocessLocalizedDocs failed: %v", err)
|
|
}
|
|
|
|
gotScoped := mustReadFile(t, scopedPath)
|
|
if !containsLine(gotScoped, "See [Troubleshooting](/zh-CN/gateway/troubleshooting).") {
|
|
t.Fatalf("expected scoped file rewrite, got:\n%s", gotScoped)
|
|
}
|
|
|
|
afterUnscoped := mustReadFile(t, unscopedPath)
|
|
if afterUnscoped != beforeUnscoped {
|
|
t.Fatalf("expected unscoped file to remain unchanged\nbefore:\n%s\nafter:\n%s", beforeUnscoped, afterUnscoped)
|
|
}
|
|
}
|
|
|
|
func TestPostprocessLocalizedDocsContinuesAfterUnchangedFile(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
docsRoot := t.TempDir()
|
|
writeFile(t, filepath.Join(docsRoot, "docs.json"), `{"redirects":[]}`)
|
|
writeFile(t, filepath.Join(docsRoot, "gateway", "troubleshooting.md"), "# Troubleshooting\n")
|
|
writeFile(t, filepath.Join(docsRoot, "zh-CN", "gateway", "troubleshooting.md"), "# 故障排除\n")
|
|
|
|
unchangedPath := filepath.Join(docsRoot, "zh-CN", "gateway", "already-localized.md")
|
|
needsRewritePath := filepath.Join(docsRoot, "zh-CN", "gateway", "index.md")
|
|
|
|
writeFile(t, unchangedPath, stringsJoin(
|
|
"---",
|
|
"title: 已本地化",
|
|
"---",
|
|
"",
|
|
"See [Troubleshooting](/zh-CN/gateway/troubleshooting).",
|
|
))
|
|
writeFile(t, needsRewritePath, stringsJoin(
|
|
"---",
|
|
"title: 网关",
|
|
"---",
|
|
"",
|
|
"See [Troubleshooting](/gateway/troubleshooting).",
|
|
))
|
|
|
|
if err := postprocessLocalizedDocs(docsRoot, "zh-CN", []string{unchangedPath, needsRewritePath}); err != nil {
|
|
t.Fatalf("postprocessLocalizedDocs failed: %v", err)
|
|
}
|
|
|
|
got := mustReadFile(t, needsRewritePath)
|
|
if !containsLine(got, "See [Troubleshooting](/zh-CN/gateway/troubleshooting).") {
|
|
t.Fatalf("expected later file rewrite after unchanged file, got:\n%s", got)
|
|
}
|
|
}
|
|
|
|
func TestPostprocessLocalizedDocsFinalizesPostprocessVersionWithoutBodyRewrite(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
docsRoot := t.TempDir()
|
|
path := filepath.Join(docsRoot, "zh-CN", "gateway", "index.md")
|
|
writeFile(t, filepath.Join(docsRoot, "docs.json"), `{"redirects":[]}`)
|
|
writeFile(t, path, stringsJoin(
|
|
"---",
|
|
"title: 网关",
|
|
"x-i18n:",
|
|
" source_hash: test",
|
|
" postprocess_version: "+localizedLinkPostprocessPending,
|
|
"---",
|
|
"",
|
|
"See [Troubleshooting](/zh-CN/gateway/troubleshooting).",
|
|
))
|
|
|
|
if err := postprocessLocalizedDocs(docsRoot, "zh-CN", []string{path}); err != nil {
|
|
t.Fatalf("postprocessLocalizedDocs failed: %v", err)
|
|
}
|
|
|
|
got := mustReadFile(t, path)
|
|
if !strings.Contains(got, " postprocess_version: "+localizedLinkPostprocessVersion) {
|
|
t.Fatalf("expected postprocess version marker to be finalized:\n%s", got)
|
|
}
|
|
if !containsLine(got, "See [Troubleshooting](/zh-CN/gateway/troubleshooting).") {
|
|
t.Fatalf("expected localized link to remain unchanged, got:\n%s", got)
|
|
}
|
|
}
|
|
|
|
func stringsJoin(lines ...string) string {
|
|
result := ""
|
|
for i, line := range lines {
|
|
if i > 0 {
|
|
result += "\n"
|
|
}
|
|
result += line
|
|
}
|
|
return result
|
|
}
|
|
|
|
func mustReadFile(t *testing.T, path string) string {
|
|
t.Helper()
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
t.Fatalf("read failed for %s: %v", path, err)
|
|
}
|
|
return string(data)
|
|
}
|
|
|
|
func containsLine(text, want string) bool {
|
|
for _, line := range strings.Split(text, "\n") {
|
|
if line == want {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|