proxy-pool/docs/docs_test.go

152 lines
4.3 KiB
Go

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 TestRunbookCoversHostAndNetworkCapacityBaseline(t *testing.T) {
content := readDocument(t, filepath.Join(repositoryRoot(t), "docs", "operations", "runbook.md"))
for _, required := range []string{
"## 8. 备份与恢复",
"## 10. 主机与网络容量基线",
"ulimit -n",
"conntrack",
"NAT",
"回滚",
} {
if !strings.Contains(content, required) {
t.Errorf("runbook does not document %q", required)
}
}
}
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)
}