refactor(supply-api): precheck main env before config load

This commit is contained in:
Your Name
2026-04-15 20:14:59 +08:00
parent 5ae0861fc3
commit bec2a7bd1d
6 changed files with 167 additions and 3 deletions

View File

@@ -0,0 +1,129 @@
# Supply API Main Env Precheck Implementation Plan
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
**Goal:**`supply-api/cmd/supply-api/main.go` 在加载配置之前就拦截非法环境名,统一复用 app 层环境解析,避免输出低价值的配置文件错误。
**Architecture:** 提升 app 层环境解析 helper 为可复用入口,`main` 在推导默认配置路径与调用 `config.LoadFromPath` 前先做 env 校验。空值仍回退 `dev`,非法值直接失败。
**Tech Stack:** Go, Go test
---
### Task 1: 为 main 增加非法 env 预校验
**Files:**
- Modify: `supply-api/cmd/supply-api/main.go`
- Modify: `supply-api/cmd/supply-api/main_test.go`
**Step 1: Write the failing test**
```go
func TestMain_RejectsUnsupportedEnvBeforeLoadingConfig(t *testing.T) {
...
if !strings.Contains(string(output), "unsupported env") {
t.Fatalf("expected unsupported env error, got: %s", string(output))
}
}
```
**Step 2: Run test to verify it fails**
Run: `cd "supply-api" && go test ./cmd/supply-api -run 'TestMain_RejectsUnsupportedEnvBeforeLoadingConfig' -v`
Expected: FAIL因为当前会先报配置文件读取失败
**Step 3: Write minimal implementation**
```go
envName, err := app.ResolveEnv(*env)
if err != nil {
jsonLogger.Fatalf("%v", err)
}
```
**Step 4: Run test to verify it passes**
Run: `cd "supply-api" && go test ./cmd/supply-api -run 'TestMain_RejectsUnsupportedEnvBeforeLoadingConfig' -v`
Expected: PASS
**Step 5: Commit**
```bash
git add supply-api/cmd/supply-api/main.go supply-api/cmd/supply-api/main_test.go
git commit -m "refactor(supply-api): precheck main env values"
```
### Task 2: 让 app 层公开统一环境解析 helper
**Files:**
- Modify: `supply-api/internal/app/runtime.go`
- Modify: `supply-api/internal/app/runtime_test.go`
- Modify: `supply-api/internal/app/bootstrap.go`
**Step 1: Write the failing test**
```go
func TestResolveEnv_RejectsUnsupportedValue(t *testing.T) {
_, err := ResolveEnv("qa")
if err == nil {
t.Fatal("expected unsupported env to fail")
}
}
```
**Step 2: Run test to verify it fails**
Run: `cd "supply-api" && go test ./internal/app -run 'TestResolveEnv_RejectsUnsupportedValue' -v`
Expected: FAIL因为 helper 尚未导出
**Step 3: Write minimal implementation**
```go
func ResolveEnv(env string) (string, error) { ... }
```
**Step 4: Run test to verify it passes**
Run: `cd "supply-api" && go test ./internal/app -run 'TestResolveEnv_RejectsUnsupportedValue' -v`
Expected: PASS
**Step 5: Commit**
```bash
git add supply-api/internal/app/runtime.go supply-api/internal/app/runtime_test.go supply-api/internal/app/bootstrap.go
git commit -m "refactor(supply-api): reuse env resolver across app layer"
```
### Task 3: 验证与收尾
**Files:**
- Verify: `supply-api/cmd/supply-api/main.go`
- Verify: `supply-api/internal/app/runtime.go`
- Verify: `supply-api/internal/app/bootstrap.go`
**Step 1: Run focused tests**
Run: `cd "supply-api" && go test ./internal/app ./cmd/supply-api ./internal/httpapi`
Expected: PASS
**Step 2: Run e2e build-tag tests**
Run: `cd "supply-api" && go test -tags=e2e ./e2e`
Expected: PASS
**Step 3: Run repo exit verification**
Run: `bash "scripts/ci/repo_integrity_check.sh"`
Expected: PASS
**Step 4: Check formatting**
Run: `git diff --check`
Expected: no output
**Step 5: Commit**
```bash
git add docs/plans/2026-04-15-supply-api-main-env-precheck-plan.md supply-api/cmd/supply-api/main.go supply-api/cmd/supply-api/main_test.go supply-api/internal/app/runtime.go supply-api/internal/app/runtime_test.go supply-api/internal/app/bootstrap.go
git commit -m "refactor(supply-api): precheck main env before config load"
```

View File

@@ -20,6 +20,12 @@ func main() {
configPath := flag.String("config", "", "config file path")
flag.Parse()
envName, err := app.ResolveEnv(*env)
if err != nil {
logging.NewLogger("supply-api", logging.LogLevelInfo).Fatalf("%v", err)
}
*env = envName
// 确定配置文件路径
if *configPath == "" {
*configPath = "./config/config." + *env + ".yaml"

View File

@@ -53,6 +53,25 @@ token:
}
}
func TestMain_RejectsUnsupportedEnvBeforeLoadingConfig(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
cmd := exec.CommandContext(ctx, os.Args[0], "-test.run=TestMainHelperProcess", "--", "-env", "qa")
cmd.Env = append(os.Environ(), "GO_WANT_HELPER_PROCESS=1")
output, err := cmd.CombinedOutput()
if ctx.Err() == context.DeadlineExceeded {
t.Fatalf("expected unsupported env to fail fast, but process timed out. output=%s", string(output))
}
if err == nil {
t.Fatalf("expected unsupported env to fail, but process exited successfully. output=%s", string(output))
}
if !strings.Contains(string(output), "unsupported env") {
t.Fatalf("expected unsupported env error, got: %s", string(output))
}
}
func TestMainHelperProcess(t *testing.T) {
if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" {
return

View File

@@ -37,7 +37,7 @@ func BuildServer(opts BuildServerOptions) (*http.Server, error) {
return nil, errors.New("logger is required")
}
env, err := resolveEnv(opts.Env)
env, err := ResolveEnv(opts.Env)
if err != nil {
return nil, err
}

View File

@@ -87,7 +87,7 @@ func buildRuntimeWithFactory(opts RuntimeOptions, factory runtimeFactory) (*Runt
factory.newRedisCache = cache.NewRedisCache
}
env, err := resolveEnv(opts.Env)
env, err := ResolveEnv(opts.Env)
if err != nil {
return nil, err
}
@@ -331,7 +331,7 @@ func (r *Runtime) ShutdownTimeout() time.Duration {
return r.serverConfig.ShutdownTimeout
}
func resolveEnv(env string) (string, error) {
func ResolveEnv(env string) (string, error) {
normalized := strings.ToLower(strings.TrimSpace(env))
if normalized == "" {
return "dev", nil

View File

@@ -64,6 +64,16 @@ func TestBuildRuntime_ProdRequiresDatabase(t *testing.T) {
}
}
func TestResolveEnv_RejectsUnsupportedValue(t *testing.T) {
_, err := ResolveEnv("qa")
if err == nil {
t.Fatal("expected unsupported env to fail")
}
if !strings.Contains(err.Error(), "unsupported env") {
t.Fatalf("unexpected error: %v", err)
}
}
func TestBuildRuntime_RejectsUnsupportedEnv(t *testing.T) {
_, err := buildRuntimeWithFactory(RuntimeOptions{
Env: "qa",