refactor(supply-api): precheck main env before config load
This commit is contained in:
129
docs/plans/2026-04-15-supply-api-main-env-precheck-plan.md
Normal file
129
docs/plans/2026-04-15-supply-api-main-env-precheck-plan.md
Normal 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"
|
||||
```
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user