1package main_test
2
3import (
4 "go/parser"
5 "go/token"
6 "io/fs"
7 "path/filepath"
8 "strings"
9 "testing"
10)
11
12// boundaryRule expresses a single import-boundary constraint: no .go file
13// under root may import a package whose path has disallowedPrefix.
14type boundaryRule struct {
15 name string
16 root string
17 disallowedPrefix string
18}
19
20func TestImportBoundaries(t *testing.T) {
21 rules := []boundaryRule{
22 {
23 name: "pkg must not import internal",
24 root: "pkg",
25 disallowedPrefix: "github.com/bnema/vev/internal",
26 },
27 {
28 name: "usecase must not import adapters",
29 root: "internal/usecase",
30 disallowedPrefix: "github.com/bnema/vev/internal/adapters",
31 },
32 {
33 name: "usecase must not import platform",
34 root: "internal/usecase",
35 disallowedPrefix: "github.com/bnema/vev/internal/platform",
36 },
37 }
38
39 for _, rule := range rules {
40 t.Run(rule.name, func(t *testing.T) {
41 violations := findImportViolations(t, rule.root, rule.disallowedPrefix)
42 if len(violations) > 0 {
43 t.Errorf("found disallowed imports of %q under %q:\n%s", rule.disallowedPrefix, rule.root, strings.Join(violations, "\n"))
44 }
45 })
46 }
47}
48
49// findImportViolations walks root, parses each .go file's imports, and
50// returns a description for every import whose path starts with
51// disallowedPrefix.
52func findImportViolations(t *testing.T, root, disallowedPrefix string) []string {
53 t.Helper()
54
55 var violations []string
56 fset := token.NewFileSet()
57
58 err := filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error {
59 if err != nil {
60 return err
61 }
62 if d.IsDir() || !strings.HasSuffix(path, ".go") {
63 return nil
64 }
65
66 f, err := parser.ParseFile(fset, path, nil, parser.ImportsOnly)
67 if err != nil {
68 return err
69 }
70
71 for _, imp := range f.Imports {
72 importPath := strings.Trim(imp.Path.Value, `"`)
73 if strings.HasPrefix(importPath, disallowedPrefix) {
74 violations = append(violations, path+": imports "+importPath)
75 }
76 }
77 return nil
78 })
79 if err != nil {
80 t.Fatalf("walking %s: %v", root, err)
81 }
82
83 return violations
84}