From 2e0f6e29aa96cd5ed75e56f3f1f2f7db65c0b426 Mon Sep 17 00:00:00 2001 From: Your Name Date: Fri, 17 Apr 2026 16:20:34 +0800 Subject: [PATCH] fix(supply-api): restore uncached build health --- supply-api/cmd/supply-api/main_test.go | 36 ++++++++++++++++--- supply-api/internal/config/config_test.go | 8 ++--- .../iam/handler/iam_handler_real_test.go | 4 +-- supply-api/internal/pkg/pathutil/path.go | 2 -- supply-api/internal/security/kms_service.go | 7 ++-- 5 files changed, 43 insertions(+), 14 deletions(-) diff --git a/supply-api/cmd/supply-api/main_test.go b/supply-api/cmd/supply-api/main_test.go index 60a9a220..377073cc 100644 --- a/supply-api/cmd/supply-api/main_test.go +++ b/supply-api/cmd/supply-api/main_test.go @@ -2,6 +2,11 @@ package main import ( "context" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "encoding/pem" + "fmt" "os" "os/exec" "path/filepath" @@ -12,7 +17,7 @@ import ( func TestMain_ProdStartupFailsWhenDatabaseUnavailable(t *testing.T) { configPath := filepath.Join(t.TempDir(), "config.prod.yaml") - content := []byte(` + content := []byte(fmt.Sprintf(` server: addr: "127.0.0.1:0" shutdown_timeout: 1s @@ -28,9 +33,10 @@ redis: port: 1 token: issuer: "prod-issuer" - secret_key: "prod-secret" - algorithm: "HS256" -`) + algorithm: "RS256" + public_key: | +%s +`, indentYAMLBlock(mustGenerateRSAPublicKeyPEM(t), " "))) if err := os.WriteFile(configPath, content, 0o600); err != nil { t.Fatalf("failed to write config file: %v", err) } @@ -88,3 +94,25 @@ func TestMainHelperProcess(t *testing.T) { main() os.Exit(0) } + +func mustGenerateRSAPublicKeyPEM(t *testing.T) string { + t.Helper() + + privateKey, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + t.Fatalf("failed to generate RSA key: %v", err) + } + der, err := x509.MarshalPKIXPublicKey(&privateKey.PublicKey) + if err != nil { + t.Fatalf("failed to marshal RSA public key: %v", err) + } + return string(pem.EncodeToMemory(&pem.Block{Type: "PUBLIC KEY", Bytes: der})) +} + +func indentYAMLBlock(value, indent string) string { + lines := strings.Split(strings.TrimRight(value, "\n"), "\n") + for i, line := range lines { + lines[i] = indent + line + } + return strings.Join(lines, "\n") +} diff --git a/supply-api/internal/config/config_test.go b/supply-api/internal/config/config_test.go index 0c445675..131ecf55 100644 --- a/supply-api/internal/config/config_test.go +++ b/supply-api/internal/config/config_test.go @@ -135,7 +135,7 @@ token: } } -func TestLoadFromPath_ProdRejectsMissingHS256SecretKey(t *testing.T) { +func TestLoadFromPath_ProdRejectsHS256Algorithm(t *testing.T) { // 清除环境变量以确保测试隔离 origVal := os.Getenv("SUPPLY_TOKEN_SECRET_KEY") os.Unsetenv("SUPPLY_TOKEN_SECRET_KEY") @@ -166,10 +166,10 @@ token: _, err := LoadFromPath("prod", configPath) if err == nil { - t.Fatal("expected prod config without HS256 secret key to return error") + t.Fatal("expected prod config with HS256 algorithm to return error") } - if !strings.Contains(err.Error(), "token.secret_key") { - t.Fatalf("expected error to mention token.secret_key, got %v", err) + if !strings.Contains(err.Error(), "token.algorithm") { + t.Fatalf("expected error to mention token.algorithm, got %v", err) } } diff --git a/supply-api/internal/iam/handler/iam_handler_real_test.go b/supply-api/internal/iam/handler/iam_handler_real_test.go index 21a9800d..0232bf75 100644 --- a/supply-api/internal/iam/handler/iam_handler_real_test.go +++ b/supply-api/internal/iam/handler/iam_handler_real_test.go @@ -8,6 +8,7 @@ import ( "strings" "testing" + "lijiaoqiao/supply-api/internal/pkg/pathutil" "lijiaoqiao/supply-api/internal/iam/service" "lijiaoqiao/supply-api/internal/middleware" @@ -832,7 +833,7 @@ func TestExtractRoleCode(t *testing.T) { // func TestExtractRoleCodeFromUserPath(t *testing.T) { ... } func TestSplitPath(t *testing.T) { - result := splitPath("/api/v1/iam/roles/developer") + result := pathutil.SplitPath("/api/v1/iam/roles/developer") assert.Equal(t, []string{"api", "v1", "iam", "roles", "developer"}, result) } @@ -1267,4 +1268,3 @@ func TestToRoleResponse(t *testing.T) { assert.Equal(t, 20, response.Level) assert.True(t, response.IsActive) } - diff --git a/supply-api/internal/pkg/pathutil/path.go b/supply-api/internal/pkg/pathutil/path.go index 130434c5..7f7d6bac 100644 --- a/supply-api/internal/pkg/pathutil/path.go +++ b/supply-api/internal/pkg/pathutil/path.go @@ -1,8 +1,6 @@ // Package pathutil provides path manipulation utilities. package pathutil -import "strings" - // SplitPath splits a URL or file path by '/' and returns non-empty segments. // Unlike strings.Split, this skips empty segments from leading/trailing/consecutive slashes. func SplitPath(path string) []string { diff --git a/supply-api/internal/security/kms_service.go b/supply-api/internal/security/kms_service.go index 751dafd3..3fe0ed31 100644 --- a/supply-api/internal/security/kms_service.go +++ b/supply-api/internal/security/kms_service.go @@ -4,13 +4,14 @@ import ( "context" "crypto/aes" "crypto/cipher" - "crypto/hkdf" "crypto/rand" "crypto/sha256" "encoding/binary" "errors" "fmt" "io" + + "golang.org/x/crypto/hkdf" ) // ==================== P0-02 KMS加密方案 ==================== @@ -207,7 +208,9 @@ func deriveDEK(keyID string, version int) []byte { ikm := append([]byte(keyID), byte(version&0xff)) hkdfReader := hkdf.New(sha256.New, ikm, nil, []byte("supply-api-dek-v1")) - hkdfReader.Read(masterKey) + if _, err := io.ReadFull(hkdfReader, masterKey); err != nil { + panic(fmt.Sprintf("failed to derive DEK: %v", err)) + } return masterKey }