package spaserver import ( "context" "go/ast" "go/parser" "go/token" "os" "path/filepath" "strings" "testing" "code.nochebuena.dev/einherjar/contracts/lifecycle" "code.nochebuena.dev/einherjar/contracts/logging" ) // Compile-time interface check (CT-5 / I-8). var _ lifecycle.Component = (*Server)(nil) // TestAtMostOneExportedTypePerFile enforces CT-6: at most one exported TypeSpec // per non-test, non-doc .go file in the root package. func TestAtMostOneExportedTypePerFile(t *testing.T) { fset := token.NewFileSet() pkgs, err := parser.ParseDir(fset, ".", func(fi os.FileInfo) bool { name := fi.Name() return !strings.HasSuffix(name, "_test.go") && name != "doc.go" }, 0) if err != nil { t.Fatalf("parse: %v", err) } for _, pkg := range pkgs { for path, file := range pkg.Files { base := filepath.Base(path) count := 0 for _, decl := range file.Decls { gd, ok := decl.(*ast.GenDecl) if !ok { continue } for _, spec := range gd.Specs { ts, ok := spec.(*ast.TypeSpec) if ok && ts.Name.IsExported() { count++ } } } if count > 1 { t.Errorf("%s: %d exported TypeSpecs (max 1)", base, count) } } } } // TestDefaultConfig_Defaults verifies that DefaultConfig returns non-zero values (S-4). func TestDefaultConfig_Defaults(t *testing.T) { cfg := DefaultConfig() if cfg.Port == 0 { t.Error("Port must have a non-zero default") } if cfg.StaticDir == "" { t.Error("StaticDir must have a non-empty default") } } // TestDefaultConfig_EnvOverride verifies that environment variables are respected. func TestDefaultConfig_EnvOverride(t *testing.T) { t.Setenv("EINHERJAR_SPA_PORT", "9090") t.Setenv("EINHERJAR_SPA_STATIC_DIR", "/tmp/www") cfg := DefaultConfig() if cfg.Port != 9090 { t.Errorf("Port = %d, want 9090", cfg.Port) } if cfg.StaticDir != "/tmp/www" { t.Errorf("StaticDir = %q, want /tmp/www", cfg.StaticDir) } } // TestServer_Lifecycle verifies that OnInit/OnStart/OnStop complete without error // on a free port. func TestServer_Lifecycle(t *testing.T) { cfg := Config{Port: 0, StaticDir: t.TempDir()} srv := NewServer(newStubLogger(), cfg) if err := srv.OnInit(); err != nil { t.Fatalf("OnInit: %v", err) } if err := srv.OnStart(); err != nil { t.Fatalf("OnStart: %v", err) } if err := srv.OnStop(); err != nil { t.Fatalf("OnStop: %v", err) } } // --- helpers --- type stubLogger struct{} func newStubLogger() *stubLogger { return &stubLogger{} } func (s *stubLogger) Debug(msg string, args ...any) {} func (s *stubLogger) Info(msg string, args ...any) {} func (s *stubLogger) Warn(msg string, args ...any) {} func (s *stubLogger) Error(msg string, err error, args ...any) {} func (s *stubLogger) With(args ...any) logging.Logger { return s } func (s *stubLogger) WithContext(ctx context.Context) logging.Logger { return s }