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) }