Files
scriptorium/internal/adapter/dependency_test.go

133 lines
3.7 KiB
Go

package adapter_test
import (
"fmt"
"go/parser"
"go/token"
"io/fs"
"os"
"path/filepath"
"runtime"
"strconv"
"strings"
"testing"
)
const scriptoriumModulePath = "gitea.maximumdirect.net/eric/scriptorium"
var forbiddenFrameworkPackageRoots = []string{
scriptoriumModulePath + "/internal/domain",
scriptoriumModulePath + "/internal/usecase",
scriptoriumModulePath + "/internal/promptdef",
scriptoriumModulePath + "/internal/prompt",
scriptoriumModulePath + "/internal/profile",
scriptoriumModulePath + "/internal/validate",
scriptoriumModulePath + "/internal/llm",
scriptoriumModulePath + "/internal/artifact",
}
type forbiddenFrameworkImport struct {
filePath string
importPath string
}
func TestScriptoriumAdaptersUseOnlyPublicFrameworkBoundary(t *testing.T) {
_, testFile, _, ok := runtime.Caller(0)
if !ok {
t.Fatal("locate dependency guard source")
}
adapterDir := filepath.Dir(testFile)
violations, err := findForbiddenFrameworkImports([]string{
filepath.Join(adapterDir, "cli"),
filepath.Join(adapterDir, "http"),
filepath.Join(adapterDir, "..", "format"),
})
if err != nil {
t.Fatalf("scan framework imports: %v", err)
}
for _, violation := range violations {
t.Errorf("%s imports forbidden framework package %s", violation.filePath, violation.importPath)
}
}
func TestForbiddenFrameworkImportScannerDetectsNestedPackageFamilies(t *testing.T) {
root := t.TempDir()
nestedDir := filepath.Join(root, "nested", "adapter")
if err := os.MkdirAll(nestedDir, 0o755); err != nil {
t.Fatalf("create nested source directory: %v", err)
}
sourcePath := filepath.Join(nestedDir, "imports.go")
source := `package nested
import (
_ "gitea.maximumdirect.net/eric/scriptorium"
_ "gitea.maximumdirect.net/eric/scriptorium/internal/profile/builtin"
)
`
if err := os.WriteFile(sourcePath, []byte(source), 0o644); err != nil {
t.Fatalf("write nested source fixture: %v", err)
}
violations, err := findForbiddenFrameworkImports([]string{root})
if err != nil {
t.Fatalf("scan nested source fixture: %v", err)
}
if len(violations) != 1 {
t.Fatalf("expected one forbidden import, got %#v", violations)
}
if violations[0].filePath != sourcePath {
t.Fatalf("unexpected importing file: %q", violations[0].filePath)
}
wantImport := scriptoriumModulePath + "/internal/profile/builtin"
if violations[0].importPath != wantImport {
t.Fatalf("unexpected forbidden import: %q", violations[0].importPath)
}
}
func findForbiddenFrameworkImports(roots []string) ([]forbiddenFrameworkImport, error) {
var violations []forbiddenFrameworkImport
for _, root := range roots {
err := filepath.WalkDir(root, func(path string, entry fs.DirEntry, err error) error {
if err != nil {
return err
}
if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".go") || strings.HasSuffix(entry.Name(), "_test.go") {
return nil
}
file, err := parser.ParseFile(token.NewFileSet(), path, nil, parser.ImportsOnly)
if err != nil {
return fmt.Errorf("parse imports in %s: %w", path, err)
}
for _, imported := range file.Imports {
importPath, err := strconv.Unquote(imported.Path.Value)
if err != nil {
return fmt.Errorf("parse import path in %s: %w", path, err)
}
if isForbiddenFrameworkImport(importPath) {
violations = append(violations, forbiddenFrameworkImport{
filePath: path,
importPath: importPath,
})
}
}
return nil
})
if err != nil {
return nil, fmt.Errorf("walk source root %s: %w", root, err)
}
}
return violations, nil
}
func isForbiddenFrameworkImport(importPath string) bool {
for _, root := range forbiddenFrameworkPackageRoots {
if importPath == root || strings.HasPrefix(importPath, root+"/") {
return true
}
}
return false
}