Drop Env==\"\" as a dev alias; pin compose app/events ENV alignment and main depends_on events with unit tests. Co-authored-by: Cursor <cursoragent@cursor.com>
111 lines
3.0 KiB
Go
111 lines
3.0 KiB
Go
package config_test
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
"runtime"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func composeRepoRoot(t *testing.T) string {
|
|
t.Helper()
|
|
_, file, _, ok := runtime.Caller(0)
|
|
if !ok {
|
|
t.Fatal("runtime.Caller")
|
|
}
|
|
return filepath.Clean(filepath.Join(filepath.Dir(file), "..", ".."))
|
|
}
|
|
|
|
var composeServiceENV = regexp.MustCompile(`(?m)^\s+ENV:\s*(\S+)\s*$`)
|
|
|
|
func extractServiceENV(composeYAML, serviceName string) (string, error) {
|
|
// Find " serviceName:" at top-level service indentation (two spaces).
|
|
marker := "\n " + serviceName + ":\n"
|
|
idx := strings.Index(composeYAML, marker)
|
|
if idx < 0 {
|
|
// file may start with the service after "services:\n"
|
|
alt := "\nservices:\n " + serviceName + ":\n"
|
|
idx = strings.Index(composeYAML, alt)
|
|
if idx < 0 {
|
|
return "", os.ErrNotExist
|
|
}
|
|
idx += len("\nservices:")
|
|
}
|
|
rest := composeYAML[idx+1:] // start at " serviceName:"
|
|
// Next top-level service starts at "\n <name>:" where name is not indented further.
|
|
next := regexp.MustCompile(`(?m)^ [a-zA-Z0-9_-]+:\s*$`)
|
|
locs := next.FindAllStringIndex(rest, -1)
|
|
block := rest
|
|
if len(locs) >= 2 {
|
|
// locs[0] is current service header; locs[1] is next sibling
|
|
block = rest[:locs[1][0]]
|
|
}
|
|
m := composeServiceENV.FindStringSubmatch(block)
|
|
if m == nil {
|
|
return "", os.ErrNotExist
|
|
}
|
|
return strings.Trim(m[1], `"'`), nil
|
|
}
|
|
|
|
func TestComposeAppEventsENVAligned(t *testing.T) {
|
|
root := composeRepoRoot(t)
|
|
files := []string{
|
|
"docker-compose.dev.yml",
|
|
"docker-compose.test.yml",
|
|
"docker-compose.main.yml",
|
|
}
|
|
for _, name := range files {
|
|
raw, err := os.ReadFile(filepath.Join(root, name))
|
|
if err != nil {
|
|
t.Fatalf("%s: %v", name, err)
|
|
}
|
|
text := string(raw)
|
|
appEnv, err := extractServiceENV(text, "app")
|
|
if err != nil {
|
|
t.Fatalf("%s: app ENV: %v", name, err)
|
|
}
|
|
eventsEnv, err := extractServiceENV(text, "events")
|
|
if err != nil {
|
|
t.Fatalf("%s: events ENV: %v", name, err)
|
|
}
|
|
if appEnv != eventsEnv {
|
|
t.Fatalf("%s: app ENV=%q != events ENV=%q", name, appEnv, eventsEnv)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestComposeMainAppDependsOnEventsHealthy(t *testing.T) {
|
|
root := composeRepoRoot(t)
|
|
raw, err := os.ReadFile(filepath.Join(root, "docker-compose.main.yml"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
text := string(raw)
|
|
marker := "\n app:\n"
|
|
idx := strings.Index(text, marker)
|
|
if idx < 0 {
|
|
t.Fatal("app service not found in docker-compose.main.yml")
|
|
}
|
|
rest := text[idx+1:]
|
|
next := regexp.MustCompile(`(?m)^ [a-zA-Z0-9_-]+:\s*$`)
|
|
locs := next.FindAllStringIndex(rest, -1)
|
|
block := rest
|
|
if len(locs) >= 2 {
|
|
block = rest[:locs[1][0]]
|
|
}
|
|
// Nail E-2: app depends_on events with service_healthy
|
|
re := regexp.MustCompile(`(?m)^\s+events:\s*\n\s+condition:\s*service_healthy\s*$`)
|
|
if !re.MatchString(block) {
|
|
t.Fatalf("docker-compose.main.yml app must depends_on events with condition: service_healthy\n--- app block ---\n%s", block)
|
|
}
|
|
appEnv, err := extractServiceENV(text, "app")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if appEnv != "main" {
|
|
t.Fatalf("main app ENV want main, got %q", appEnv)
|
|
}
|
|
}
|