− package utils
−
− import (
− "go/parser"
− "go/token"
− "os/exec"
− "strings"
− "testing"
− )
−
− // TestPackageDependencies enforces the project's package dependency rules
− // defined in CLAUDE.md. Packages may only import packages below them:
− //
− // cmd → tools, tui, utils, db
− // tools → utils, db
− // tui → tools, utils
− // utils → (nothing — leaf package)
− // db → utils
− //
− // Note: This test lives in utils/ only because it needs to be in some
− // package. It validates rules for ALL project packages.
− func TestPackageDependencies(t *testing.T) {
− rules := map[string]map[string]bool{
− // pkg → set of allowed skraak-internal imports
− "skraak/cmd": {"skraak/tools": true, "skraak/tui": true, "skraak/utils": true, "skraak/db": true},
− "skraak/tools": {"skraak/utils": true, "skraak/db": true},
− "skraak/tui": {"skraak/tools": true, "skraak/utils": true},
− "skraak/utils": {}, // leaf package — no skraak imports allowed
− "skraak/db": {"skraak/utils": true},
− }
−
− for pkg, allowed := range rules {
− imports := getInternalImports(t, pkg)
− for _, imp := range imports {
− if !allowed[imp] {
− t.Errorf("%s imports %s — not allowed by dependency rules", pkg, imp)
− }
− }
− }
− }
−
− // TestNoDirectDBImportInUtils checks that no file in utils/ imports skraak/db.
− // This is a fast source-level check that doesn't require `go list`.
− func TestNoDirectDBImportInUtils(t *testing.T) {
− fset := token.NewFileSet()
− pkgs, err := parser.ParseDir(fset, ".", nil, parser.ImportsOnly)
− if err != nil {
− t.Fatalf("parse utils/: %v", err)
− }
−
− for _, pkg := range pkgs {
− for filename, file := range pkg.Files {
− for _, imp := range file.Imports {
− path := strings.Trim(imp.Path.Value, `"`)
− if path == "skraak/db" {
− t.Errorf("%s: forbidden import of skraak/db (utils is the leaf package)", filename)
− }
− }
− }
− }
− }
−
− // getInternalImports returns the skraak-internal imports for a package using `go list`.
− func getInternalImports(t *testing.T, pkg string) []string {
− t.Helper()
− out, err := exec.Command("go", "list", "-f", "{{range .Imports}}{{.}}\n{{end}}", pkg).Output()
− if err != nil {
− t.Fatalf("go list %s: %v", pkg, err)
− }
−
− var internal []string
− for line := range strings.SplitSeq(string(out), "\n") {
− line = strings.TrimSpace(line)
− if strings.HasPrefix(line, "skraak/") || line == "skraak" {
− internal = append(internal, line)
− }
− }
− return internal
− }