refactor(api): enforce protocol-neutral service boundaries
- refactor: move HTTP status and DTO concerns out of model and service layers into API and handler code. - feat: add admin service boundary and route auth through services instead of direct repository access. - test: add architecture and error tests covering package boundaries, redaction, and service behavior. - docs: record the protocol-neutral boundary decision and update architecture and roadmap notes.
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
package internal_test
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestArchitecture_ModelAndServiceAreProtocolNeutral(t *testing.T) {
|
||||
for _, dir := range []string{"model", "service"} {
|
||||
imports := packageImports(t, dir)
|
||||
for _, forbidden := range []string{
|
||||
"net/http",
|
||||
"github.com/gin-gonic/gin",
|
||||
"github.com/dhao2001/mygo/internal/api",
|
||||
} {
|
||||
if imports[forbidden] {
|
||||
t.Fatalf("internal/%s imports forbidden package %q", dir, forbidden)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestArchitecture_HTTPDoesNotImportRepositories(t *testing.T) {
|
||||
for _, dir := range []string{"handler", "middleware"} {
|
||||
imports := packageImports(t, dir)
|
||||
if imports["github.com/dhao2001/mygo/internal/repository"] {
|
||||
t.Fatalf("internal/%s imports internal/repository; use service boundaries instead", dir)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func packageImports(t *testing.T, dir string) map[string]bool {
|
||||
t.Helper()
|
||||
|
||||
imports := map[string]bool{}
|
||||
pkgs := parsePackage(t, dir)
|
||||
for _, pkg := range pkgs {
|
||||
for _, file := range pkg.Files {
|
||||
for _, spec := range file.Imports {
|
||||
path, err := strconv.Unquote(spec.Path.Value)
|
||||
if err != nil {
|
||||
t.Fatalf("unquote import path %s: %v", spec.Path.Value, err)
|
||||
}
|
||||
imports[path] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
return imports
|
||||
}
|
||||
|
||||
func parsePackage(t *testing.T, dir string) map[string]*ast.Package {
|
||||
t.Helper()
|
||||
|
||||
fset := token.NewFileSet()
|
||||
pkgs, err := parser.ParseDir(fset, dir, func(info os.FileInfo) bool {
|
||||
name := info.Name()
|
||||
return !strings.HasSuffix(name, "_test.go")
|
||||
}, parser.ParseComments)
|
||||
if err != nil {
|
||||
t.Fatalf("parse internal/%s: %v", dir, err)
|
||||
}
|
||||
return pkgs
|
||||
}
|
||||
Reference in New Issue
Block a user