Strengthen public framework dependency guard
This commit is contained in:
@@ -1,8 +1,10 @@
|
|||||||
package adapter_test
|
package adapter_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"go/parser"
|
"go/parser"
|
||||||
"go/token"
|
"go/token"
|
||||||
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
@@ -11,6 +13,24 @@ import (
|
|||||||
"testing"
|
"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) {
|
func TestScriptoriumAdaptersUseOnlyPublicFrameworkBoundary(t *testing.T) {
|
||||||
_, testFile, _, ok := runtime.Caller(0)
|
_, testFile, _, ok := runtime.Caller(0)
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -18,47 +38,95 @@ func TestScriptoriumAdaptersUseOnlyPublicFrameworkBoundary(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
adapterDir := filepath.Dir(testFile)
|
adapterDir := filepath.Dir(testFile)
|
||||||
directories := []string{
|
violations, err := findForbiddenFrameworkImports([]string{
|
||||||
filepath.Join(adapterDir, "cli"),
|
filepath.Join(adapterDir, "cli"),
|
||||||
filepath.Join(adapterDir, "http"),
|
filepath.Join(adapterDir, "http"),
|
||||||
filepath.Join(adapterDir, "..", "format"),
|
filepath.Join(adapterDir, "..", "format"),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("scan framework imports: %v", err)
|
||||||
}
|
}
|
||||||
forbidden := map[string]struct{}{
|
for _, violation := range violations {
|
||||||
"gitea.maximumdirect.net/eric/scriptorium/internal/domain": {},
|
t.Errorf("%s imports forbidden framework package %s", violation.filePath, violation.importPath)
|
||||||
"gitea.maximumdirect.net/eric/scriptorium/internal/usecase": {},
|
}
|
||||||
"gitea.maximumdirect.net/eric/scriptorium/internal/promptdef": {},
|
}
|
||||||
"gitea.maximumdirect.net/eric/scriptorium/internal/prompt": {},
|
|
||||||
"gitea.maximumdirect.net/eric/scriptorium/internal/profile": {},
|
func TestForbiddenFrameworkImportScannerDetectsNestedPackageFamilies(t *testing.T) {
|
||||||
"gitea.maximumdirect.net/eric/scriptorium/internal/profile/builtin": {},
|
root := t.TempDir()
|
||||||
"gitea.maximumdirect.net/eric/scriptorium/internal/validate": {},
|
nestedDir := filepath.Join(root, "nested", "adapter")
|
||||||
"gitea.maximumdirect.net/eric/scriptorium/internal/llm": {},
|
if err := os.MkdirAll(nestedDir, 0o755); err != nil {
|
||||||
"gitea.maximumdirect.net/eric/scriptorium/internal/artifact": {},
|
t.Fatalf("create nested source directory: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, directory := range directories {
|
sourcePath := filepath.Join(nestedDir, "imports.go")
|
||||||
entries, err := os.ReadDir(directory)
|
source := `package nested
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("read source directory %s: %v", directory, err)
|
import (
|
||||||
}
|
_ "gitea.maximumdirect.net/eric/scriptorium"
|
||||||
for _, entry := range entries {
|
_ "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") {
|
if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".go") || strings.HasSuffix(entry.Name(), "_test.go") {
|
||||||
continue
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
path := filepath.Join(directory, entry.Name())
|
|
||||||
file, err := parser.ParseFile(token.NewFileSet(), path, nil, parser.ImportsOnly)
|
file, err := parser.ParseFile(token.NewFileSet(), path, nil, parser.ImportsOnly)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("parse imports in %s: %v", path, err)
|
return fmt.Errorf("parse imports in %s: %w", path, err)
|
||||||
}
|
}
|
||||||
for _, imported := range file.Imports {
|
for _, imported := range file.Imports {
|
||||||
importPath, err := strconv.Unquote(imported.Path.Value)
|
importPath, err := strconv.Unquote(imported.Path.Value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("parse import path in %s: %v", path, err)
|
return fmt.Errorf("parse import path in %s: %w", path, err)
|
||||||
}
|
}
|
||||||
if _, found := forbidden[importPath]; found {
|
if isForbiddenFrameworkImport(importPath) {
|
||||||
t.Errorf("%s directly imports forbidden framework package %s", path, 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
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user