161 lines
4.8 KiB
Go
161 lines
4.8 KiB
Go
package tools
|
|||
|
|
|
||
|
|
import (
|
||
|
|
"path/filepath"
|
||
|
|
"strings"
|
||
|
|
"testing"
|
||
|
|
|
||
|
|
"code.nochebuena.dev/einherjar/mcp/internal/index"
|
||
|
|
)
|
||
|
|
|
||
|
|
// loadRealIndex builds the index from the sibling framework checkout at test
|
||
|
|
// time, so the env tools are tested against the framework's actual struct tags.
|
||
|
|
// It deliberately does NOT read data/index.json: that file is only an empty
|
||
|
|
// placeholder in the repo (the real index is regenerated by cmd/indexer at
|
||
|
|
// image-build time). When the sibling modules aren't present — e.g. a CI that
|
||
|
|
// checks out only this module — the test skips rather than fails.
|
||
|
|
func loadRealIndex(t *testing.T) *index.Index {
|
||
|
|
t.Helper()
|
||
|
|
idx, err := index.Build(filepath.Join("..", "..", ".."))
|
||
|
|
if err != nil || idx == nil || len(idx.Modules) == 0 {
|
||
|
|
t.Skip("framework checkout not available; skipping index-derived test")
|
||
|
|
}
|
||
|
|
return idx
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestRenderEnvExample(t *testing.T) {
|
||
|
|
idx := loadRealIndex(t)
|
||
|
|
got := renderEnvExample(idx, "myapp")
|
||
|
|
|
||
|
|
// Required, no default → uncommented with a runnable dev value.
|
||
|
|
mustContain := []string{
|
||
|
|
"APP_ENV=local",
|
||
|
|
"APP_CORS_ORIGINS=*",
|
||
|
|
"EINHERJAR_PG_HOST=localhost",
|
||
|
|
"EINHERJAR_PG_USER=postgres",
|
||
|
|
"EINHERJAR_PG_PASSWORD=postgres",
|
||
|
|
"EINHERJAR_PG_NAME=myapp",
|
||
|
|
// logz is composed → its vars (both defaulted) are documented, commented.
|
||
|
|
"# EINHERJAR_LOG_LEVEL=INFO",
|
||
|
|
"# EINHERJAR_LOG_JSON=false",
|
||
|
|
// Has a framework default → commented line documenting it.
|
||
|
|
"# EINHERJAR_SERVER_HOST=0.0.0.0",
|
||
|
|
"# EINHERJAR_SERVER_PORT=8080",
|
||
|
|
"# EINHERJAR_PG_PORT=5432",
|
||
|
|
"# EINHERJAR_PG_SSL_MODE=disable",
|
||
|
|
}
|
||
|
|
for _, s := range mustContain {
|
||
|
|
if !strings.Contains(got, s) {
|
||
|
|
t.Errorf(".env.example missing line: %q\n---\n%s", s, got)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// The historical drift names must never appear.
|
||
|
|
mustNotContain := []string{
|
||
|
|
"EINHERJAR_PG_DATABASE",
|
||
|
|
"EINHERJAR_SERVER_ADDR",
|
||
|
|
// CORS lives on web.Config, which the scaffold does not compose — so it
|
||
|
|
// must not be emitted (it would be a dead var).
|
||
|
|
"EINHERJAR_SERVER_CORS_ORIGINS",
|
||
|
|
}
|
||
|
|
for _, s := range mustNotContain {
|
||
|
|
if strings.Contains(got, s) {
|
||
|
|
t.Errorf(".env.example must not contain %q\n---\n%s", s, got)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// A required var must never be silently emitted as a commented line.
|
||
|
|
for _, line := range strings.Split(got, "\n") {
|
||
|
|
if strings.HasPrefix(strings.TrimSpace(line), "# EINHERJAR_PG_NAME") {
|
||
|
|
t.Errorf("required EINHERJAR_PG_NAME emitted commented: %q", line)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestCheckEnvUnknownVar(t *testing.T) {
|
||
|
|
idx := loadRealIndex(t)
|
||
|
|
env := "EINHERJAR_PG_DATABASE=x\nEINHERJAR_SERVER_ADDR=:8080\nAPP_ENV=local\n"
|
||
|
|
findings, err := checkEnvVars(idx, env, nil)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
unknown := map[string]bool{}
|
||
|
|
for _, f := range findings {
|
||
|
|
if f.Kind == "unknown-var" {
|
||
|
|
unknown[f.Var] = true
|
||
|
|
}
|
||
|
|
}
|
||
|
|
for _, want := range []string{"EINHERJAR_PG_DATABASE", "EINHERJAR_SERVER_ADDR"} {
|
||
|
|
if !unknown[want] {
|
||
|
|
t.Errorf("expected unknown-var finding for %s; got %+v", want, findings)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
// APP_* must never be flagged.
|
||
|
|
for _, f := range findings {
|
||
|
|
if f.Var == "APP_ENV" {
|
||
|
|
t.Errorf("APP_ENV should never be flagged: %+v", f)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestCheckEnvMissingRequired(t *testing.T) {
|
||
|
|
idx := loadRealIndex(t)
|
||
|
|
// Compose db-postgres but set none of its required vars.
|
||
|
|
findings, err := checkEnvVars(idx, "APP_ENV=local\n", []string{"db-postgres"})
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
missing := map[string]bool{}
|
||
|
|
for _, f := range findings {
|
||
|
|
if f.Kind == "missing-required" {
|
||
|
|
missing[f.Var] = true
|
||
|
|
}
|
||
|
|
}
|
||
|
|
for _, want := range []string{"EINHERJAR_PG_HOST", "EINHERJAR_PG_USER", "EINHERJAR_PG_PASSWORD", "EINHERJAR_PG_NAME"} {
|
||
|
|
if !missing[want] {
|
||
|
|
t.Errorf("expected missing-required for %s; got %+v", want, findings)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestCheckEnvNotComposed(t *testing.T) {
|
||
|
|
idx := loadRealIndex(t)
|
||
|
|
// A real MinIO var while only composing db-postgres → dead (not-composed).
|
||
|
|
env := "EINHERJAR_MINIO_ENDPOINT=x\nEINHERJAR_PG_HOST=h\nEINHERJAR_PG_USER=u\nEINHERJAR_PG_PASSWORD=p\nEINHERJAR_PG_NAME=n\n"
|
||
|
|
findings, err := checkEnvVars(idx, env, []string{"db-postgres"})
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
var got bool
|
||
|
|
for _, f := range findings {
|
||
|
|
if f.Kind == "not-composed" && f.Var == "EINHERJAR_MINIO_ENDPOINT" {
|
||
|
|
got = true
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if !got {
|
||
|
|
t.Errorf("expected not-composed for EINHERJAR_MINIO_ENDPOINT; got %+v", findings)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestCheckEnvClean(t *testing.T) {
|
||
|
|
idx := loadRealIndex(t)
|
||
|
|
// The scaffold's own output, checked against the modules it composes, is clean.
|
||
|
|
env := renderEnvExample(idx, "myapp")
|
||
|
|
findings, err := checkEnvVars(idx, env, []string{"core", "web", "db-postgres"})
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
for _, f := range findings {
|
||
|
|
if f.Severity == "error" {
|
||
|
|
t.Errorf("scaffold .env.example should have no errors, got %+v", f)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestCheckEnvUnknownModule(t *testing.T) {
|
||
|
|
idx := loadRealIndex(t)
|
||
|
|
if _, err := checkEnvVars(idx, "", []string{"nope"}); err == nil {
|
||
|
|
t.Error("expected error for unknown module")
|
||
|
|
}
|
||
|
|
}
|