test: validate documentation references

This commit is contained in:
youfak 2026-07-29 21:30:09 +08:00
parent 125740f58d
commit 35a201cab1
6 changed files with 161 additions and 2 deletions

View File

@ -3,12 +3,16 @@
## 1. 加载规则 ## 1. 加载规则
主配置格式为 YAML根字段 `version` 当前固定为 `1`。加载器启用严格字段 主配置格式为 YAML根字段 `version` 当前固定为 `1`。加载器启用严格字段
检查,拼写错误或未来版本字段不会被静默忽略。推荐启动命令显式传入配置路径: 检查,拼写错误或未来版本字段不会被静默忽略。当前仓库可使用与未来进程启动
相同的严格加载器校验本地部署配置:
```powershell ```powershell
go run ./cmd/proxy-controller -config configs/proxy-pool.yaml go run ./deploy/tools/configcheck deploy/config/local.yaml
``` ```
规划中的生产入口为 `proxy-controller -config CONFIG_FILE`;该命令完成实现和
进程级测试前,不作为当前可执行能力。
所有时间值使用 Go duration例如 `500ms`、`30s`、`5m`。示例中的 所有时间值使用 Go duration例如 `500ms`、`30s`、`5m`。示例中的
`${TOKEN}`、`${PASSWORD}`、`${POSTGRES_URL}` 等由加载器从同名环境变量 `${TOKEN}`、`${PASSWORD}`、`${POSTGRES_URL}` 等由加载器从同名环境变量
展开;这些值不得写入日志、指标、配置转储或错误响应。生产配置优先使用 展开;这些值不得写入日志、指标、配置转储或错误响应。生产配置优先使用

135
docs/docs_test.go Normal file
View File

@ -0,0 +1,135 @@
package docs_test
import (
"fmt"
"net/url"
"os"
"path/filepath"
"regexp"
"slices"
"strings"
"testing"
)
var (
markdownLinkPattern = regexp.MustCompile(`\[[^\]]+\]\(([^)]+)\)`)
goCommandPattern = regexp.MustCompile(`\bgo\s+(?:run|build)\s+(\./[^\s` + "`" + `]+)`)
)
func TestMarkdownRelativeLinksResolve(t *testing.T) {
repositoryRoot := repositoryRoot(t)
for _, document := range markdownDocuments(t, repositoryRoot) {
document := document
t.Run(relativeTestName(repositoryRoot, document), func(t *testing.T) {
content := readDocument(t, document)
for _, match := range markdownLinkPattern.FindAllStringSubmatch(content, -1) {
target := strings.TrimSpace(match[1])
if target == "" || strings.HasPrefix(target, "#") || hasExternalScheme(target) {
continue
}
if title := strings.Index(target, ` "`); title >= 0 {
target = target[:title]
}
target = strings.Trim(target, "<>")
if fragment := strings.IndexByte(target, '#'); fragment >= 0 {
target = target[:fragment]
}
if target == "" {
continue
}
unescaped, err := url.PathUnescape(target)
if err != nil {
t.Errorf("link %q is not path-encoded correctly: %v", match[1], err)
continue
}
resolved := filepath.Clean(filepath.Join(filepath.Dir(document), filepath.FromSlash(unescaped)))
if _, err := os.Stat(resolved); err != nil {
t.Errorf("link %q resolves to missing path %s: %v", match[1], resolved, err)
}
}
})
}
}
func TestDocumentedGoCommandsReferenceExistingTargets(t *testing.T) {
repositoryRoot := repositoryRoot(t)
documents := []string{
"README.md",
"deploy/README.md",
"docs/configuration/reference.md",
"docs/development/guide.md",
"docs/testing/strategy.md",
"docs/testing/test-strategy.md",
}
for _, relative := range documents {
document := filepath.Join(repositoryRoot, filepath.FromSlash(relative))
content := readDocument(t, document)
for _, match := range goCommandPattern.FindAllStringSubmatch(content, -1) {
target := strings.TrimRight(match[1], ",.;:")
if strings.Contains(target, "...") {
continue
}
resolved := filepath.Clean(filepath.Join(repositoryRoot, filepath.FromSlash(strings.TrimPrefix(target, "./"))))
if _, err := os.Stat(resolved); err != nil {
t.Errorf("%s documents missing Go target %q: %v", relative, target, err)
}
}
}
}
func markdownDocuments(t *testing.T, repositoryRoot string) []string {
t.Helper()
documents := []string{filepath.Join(repositoryRoot, "README.md")}
for _, directory := range []string{"docs", "deploy", "diagrams"} {
root := filepath.Join(repositoryRoot, directory)
err := filepath.WalkDir(root, func(path string, entry os.DirEntry, err error) error {
if err != nil {
return err
}
if !entry.IsDir() && strings.EqualFold(filepath.Ext(entry.Name()), ".md") {
documents = append(documents, path)
}
return nil
})
if err != nil {
t.Fatalf("walk %s: %v", directory, err)
}
}
slices.Sort(documents)
return documents
}
func repositoryRoot(t *testing.T) string {
t.Helper()
workingDirectory, err := os.Getwd()
if err != nil {
t.Fatalf("get working directory: %v", err)
}
root := filepath.Clean(filepath.Join(workingDirectory, ".."))
if _, err := os.Stat(filepath.Join(root, "go.mod")); err != nil {
t.Fatalf("locate repository root from %s: %v", workingDirectory, err)
}
return root
}
func readDocument(t *testing.T, path string) string {
t.Helper()
content, err := os.ReadFile(path)
if err != nil {
t.Fatalf("read %s: %v", path, err)
}
return string(content)
}
func hasExternalScheme(target string) bool {
parsed, err := url.Parse(target)
return err == nil && parsed.Scheme != ""
}
func relativeTestName(repositoryRoot, document string) string {
relative, err := filepath.Rel(repositoryRoot, document)
if err != nil {
return fmt.Sprintf("document-%x", document)
}
return filepath.ToSlash(relative)
}

View File

@ -8,6 +8,7 @@
- **领域单测**状态机、TTL、路由、容量、Fetch 分类和 Extraction 原子性。 - **领域单测**状态机、TTL、路由、容量、Fetch 分类和 Extraction 原子性。
- **契约测试**配置、OpenAPI、Protobuf 和 Provider Adapter fixture。 - **契约测试**配置、OpenAPI、Protobuf 和 Provider Adapter fixture。
- **文档契约**:相对链接必须可解析,公开 Go 命令必须指向仓库内现有目标。
- **集成测试**Redis 活动池原子契约、PostgreSQL 管理事务、Leader/限流、 - **集成测试**Redis 活动池原子契约、PostgreSQL 管理事务、Leader/限流、
Outbox 与重建。 Outbox 与重建。
- **端到端测试**HTTP、CONNECT、Admin、Distribution 和优雅停机。 - **端到端测试**HTTP、CONNECT、Admin、Distribution 和优雅停机。
@ -37,6 +38,9 @@ go test -race ./internal/...
go build ./... go build ./...
``` ```
`go test ./docs` 自动扫描 README、设计/部署文档和公开执行指南,拒绝失效的
相对链接以及指向尚不存在 Go 入口的运行命令。
真实 Redis 8.2 活动池契约使用独立 Compose fixture 真实 Redis 8.2 活动池契约使用独立 Compose fixture
```powershell ```powershell

View File

@ -33,6 +33,7 @@
- Redis TTL 活动池、Leader 租约、限流、短期幂等窗口和失联恢复。 - Redis TTL 活动池、Leader 租约、限流、短期幂等窗口和失联恢复。
- Snapshot/Delta/ACK/Report 的版本与校验和兼容性。 - Snapshot/Delta/ACK/Report 的版本与校验和兼容性。
- OpenAPI 错误模型、认证矩阵、批量 fulfillment。 - OpenAPI 错误模型、认证矩阵、批量 fulfillment。
- README、设计/部署文档中的相对链接,以及公开 Go 命令引用的仓库目标。
### 集成与端到端 ### 集成与端到端
@ -77,6 +78,9 @@ go vet ./...
go build ./... go build ./...
``` ```
`go test ./docs` 是文档契约门禁:递归校验相对链接,并确认用户指南中的
`go run`/`go build` 具体目标真实存在;通配包命令继续由 Go 工具链验证。
Redis 活动池 Adapter 与内存参考实现共享同一套公用行为契约。真实 Redis 8.2 Redis 活动池 Adapter 与内存参考实现共享同一套公用行为契约。真实 Redis 8.2
fixture 的执行命令是: fixture 的执行命令是:

View File

@ -164,3 +164,12 @@ Routing 自上而下匹配,首条命中停止;支持 Gateway 与 Extract 两
缺失。 缺失。
- Available Slots 当前纳入 AVAILABLE、TTL、安全余量、Max、Active、Reserved - Available Slots 当前纳入 AVAILABLE、TTL、安全余量、Max、Active、Reserved
ownership、route/target health 与 Gateway reserve 尚未进入同一聚合模型。 ownership、route/target health 与 Gateway reserve 尚未进入同一聚合模型。
## 文档可执行性审计2026-07-29
- 文档相对链接人工扫描通过,但原先缺少持续门禁;新增 `docs` 包契约测试,统一
扫描 README、docs、deploy 与 diagrams防止链接随文件调整后失效。
- 配置参考曾把尚不存在的 `cmd/proxy-controller` 作为推荐启动命令;现改为真实
可执行的 `deploy/tools/configcheck`,并明确生产 Controller 入口仍是计划能力。
- 文档中的具体 `go run`/`go build` 目标现在必须存在;包含 `...` 的通配包命令
由 Go 工具链自身解析并在完整验证中执行。

View File

@ -2,6 +2,9 @@
## 2026-07-29 ## 2026-07-29
- 新增文档契约测试,递归验证 README、docs、deploy、diagrams 的相对链接,并
拒绝公开指南引用不存在的具体 Go 命令目标;修正配置参考中尚未实现的
`cmd/proxy-controller` 启动命令,改为当前真实可执行的严格配置校验工具。
- 已复核 `implementation-plan.md` 的验收项机器契约、OpenAPI/Protobuf 验证、 - 已复核 `implementation-plan.md` 的验收项机器契约、OpenAPI/Protobuf 验证、
完整文档导航、20 份配置示例和 35 张 Mermaid 图已有仓库及验证证据,修正 完整文档导航、20 份配置示例和 35 张 Mermaid 图已有仓库及验证证据,修正
滞后勾选;进一步核对发现 descriptor 编译尚未进入 CI同时验证脚本和双平台 滞后勾选;进一步核对发现 descriptor 编译尚未进入 CI同时验证脚本和双平台