對齊 ADR-016 / conversion.md v0.6.1 §3.1:visionA 端不再需要 FAA 設定(v0.5 T1 加的 FAAAPIKey/FAABaseURL 撤回)。
config 砍除:
- internal/config/config.go: ConversionConfig.FAABaseURL + FAAAPIKey 兩欄位
- internal/config/load.go: VISIONA_FAA_BASE_URL + VISIONA_FAA_API_KEY 兩 env 讀取
- Enabled() 簡化為「ConverterBaseURL + ConverterAPIKey 兩個非空」
- internal/config/load_test.go: TestLoad_ConversionEnabled 從 6 case 簡化為 4 case (all_set / missing_converter_url / missing_converter_key / all_empty)
.env*.example 對齊(3 個檔):
- visionA-backend/.env.example: 砍 2 個 FAA env row + 註解;header 改「2 欄位啟用」
- .env.stage.example: 同上;VISIONA_CONVERTER_API_KEY 保留 CHANGE_ME_OPENSSL_RAND_HEX_32 placeholder
- .env.dev.example: 註解區塊統一對齊
T3 review polish:
- m-2 internal/api/conversion.go: i18n message map 砍 4 個 dead case (download_token_failed / mc_token_unavailable / idp_misconfigured / idp_unavailable) — 對應 v0.5 mc_token_client 撤回時砍的 sentinel;落入 default「內部錯誤」、行為不變
- m-3 internal/conversion/util.go: hashObjectKey godoc 補「設計約束(重要)」段 + 3 條「不應做的事」(不出現在 response body/header / 不組 URL / 不寫進 user-facing 錯誤訊息)— 明示用途限定於 slog 欄位內、避免 misuse vector
- cmd/api-server/main.go: godoc 對齊 T4 完成狀態
驗證:
- B 層 verification 主動跑(T3 reviewer 接受暫緩、backend 主動跑避免 reviewer 二次要求):
* 跨檔 grep: production code 0 functional 命中(殘留全是註解 audit trail / test fixture name)
* 17 packages race -count=3 全綠
* 3 個 .env 環境一致性驗證
- go build ./... exit 0
- go test -race -count=3 ./... 17 packages 全綠
- Reviewer 5 軸(v0.6-t4-review)✅ 通過(0 Critical / 0 Major / 2 Minor / 4 Suggestion)
v0.6 對齊改造事實上完工:
- T1 ConverterClient.GetResult method
- T2 flow.go DownloadStream/PromoteToModels 改用 GetResult + e2e endpoint
- T3 faa_client 整檔砍 + ErrFAA* sentinel 清 + s-3/s-4/s-5 必補 + mockFAA regression-only
- T4 config FAA 欄位砍 + .env 清 + i18n/godoc polish
main.go startup log 已是「converter_api_key_set only」、無 FAA 殘留 / 無 tenant_id(T2-T3 已處理)。e2e regression 防護由 mockFAA negative assertion 守住(T3)。
下一步:
- visionA backend 端 ADR-016 對齊完工,等使用者跨 repo 加 converter GET /api/v1/jobs/{id}/result endpoint
- stage redeploy + e2e 完整測試
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
371 lines
15 KiB
Go
371 lines
15 KiB
Go
package config
|
||
|
||
import (
|
||
"testing"
|
||
"time"
|
||
|
||
"github.com/stretchr/testify/assert"
|
||
"github.com/stretchr/testify/require"
|
||
)
|
||
|
||
func TestLoad_Defaults(t *testing.T) {
|
||
// Arrange:清掉所有相關 env(t.Setenv 自動還原)
|
||
for _, k := range []string{
|
||
"VISIONA_HOST", "VISIONA_API_PORT", "VISIONA_TUNNEL_PORT", "VISIONA_PROXY_INTERNAL_PORT",
|
||
"VISIONA_SESSION_BACKEND", "VISIONA_PROXY_INTERNAL_URL",
|
||
"VISIONA_AUTH_TYPE", "VISIONA_STATIC_USER_ID", "VISIONA_PAIRING_TOKEN",
|
||
"VISIONA_STORAGE_BACKEND", "VISIONA_STORAGE_LOCALFS_ROOT",
|
||
"VISIONA_MODEL_MAX_SIZE_MB",
|
||
"VISIONA_TUNNEL_HEARTBEAT_INTERVAL", "VISIONA_TUNNEL_IDLE_TIMEOUT",
|
||
"VISIONA_LOG_LEVEL",
|
||
} {
|
||
t.Setenv(k, "")
|
||
}
|
||
|
||
// Act
|
||
cfg := Load()
|
||
|
||
// Assert
|
||
assert.Equal(t, "0.0.0.0", cfg.Server.Host)
|
||
// Port 預設改為 3721 — 對齊 local-tool(B4)。
|
||
assert.Equal(t, 3721, cfg.Server.Port)
|
||
assert.Equal(t, 3800, cfg.Server.TunnelPort)
|
||
assert.Equal(t, 3801, cfg.Server.InternalPort)
|
||
assert.False(t, cfg.Server.SeedDemoData, "預設不 seed demo data")
|
||
assert.Equal(t, "inmemory", cfg.Session.Backend)
|
||
assert.Equal(t, "http://localhost:3801", cfg.Session.ProxyInternalURL)
|
||
assert.Equal(t, "demo-user", cfg.Auth.StaticUserID)
|
||
assert.Equal(t, "localfs", cfg.Storage.Backend)
|
||
assert.Equal(t, "./data/storage", cfg.Storage.RootDir)
|
||
assert.Equal(t, 100, cfg.Model.MaxSizeMB)
|
||
assert.Equal(t, 10*time.Second, cfg.Tunnel.HeartbeatInterval)
|
||
assert.Equal(t, 30*time.Second, cfg.Tunnel.IdleTimeout)
|
||
assert.Equal(t, "info", cfg.Logger.Level)
|
||
}
|
||
|
||
func TestLoad_EnvOverrides(t *testing.T) {
|
||
t.Setenv("VISIONA_API_PORT", "8080")
|
||
t.Setenv("VISIONA_STATIC_USER_ID", "custom-user")
|
||
t.Setenv("VISIONA_MODEL_MAX_SIZE_MB", "500")
|
||
t.Setenv("VISIONA_TUNNEL_HEARTBEAT_INTERVAL", "5s")
|
||
t.Setenv("VISIONA_LOG_LEVEL", "debug")
|
||
|
||
cfg := Load()
|
||
|
||
assert.Equal(t, 8080, cfg.Server.Port)
|
||
assert.Equal(t, "custom-user", cfg.Auth.StaticUserID)
|
||
assert.Equal(t, 500, cfg.Model.MaxSizeMB)
|
||
assert.Equal(t, 5*time.Second, cfg.Tunnel.HeartbeatInterval)
|
||
assert.Equal(t, "debug", cfg.Logger.Level)
|
||
}
|
||
|
||
func TestLoad_InvalidIntFallback(t *testing.T) {
|
||
t.Setenv("VISIONA_API_PORT", "not-a-number")
|
||
cfg := Load()
|
||
assert.Equal(t, 3721, cfg.Server.Port, "無法解析時應 fallback 到預設值(B4 改為 3721)")
|
||
}
|
||
|
||
// TestLoad_SeedDemoData 驗證 VISIONA_SEED_DEMO_DATA env 的解析行為。
|
||
func TestLoad_SeedDemoData(t *testing.T) {
|
||
t.Setenv("VISIONA_SEED_DEMO_DATA", "true")
|
||
cfg := Load()
|
||
assert.True(t, cfg.Server.SeedDemoData)
|
||
|
||
t.Setenv("VISIONA_SEED_DEMO_DATA", "false")
|
||
cfg = Load()
|
||
assert.False(t, cfg.Server.SeedDemoData)
|
||
|
||
// 無法解析時 fallback 到預設 false
|
||
t.Setenv("VISIONA_SEED_DEMO_DATA", "not-a-bool")
|
||
cfg = Load()
|
||
assert.False(t, cfg.Server.SeedDemoData, "無法解析時應 fallback 到預設值")
|
||
}
|
||
|
||
// TestLoad_OIDCDefaults 驗證未設定任何 VISIONA_OIDC_* 時,OIDC 欄位為空字串。
|
||
//
|
||
// OB5 起 OIDC.Enabled 已移除(OIDC 是唯一認證路徑);空字串就是「未設定」,
|
||
// 此時 Validate() 會回 MissingEnvError,main.go 啟動時 fatal log 退出。
|
||
func TestLoad_OIDCDefaults(t *testing.T) {
|
||
for _, k := range []string{
|
||
"VISIONA_OIDC_ISSUER_URL", "VISIONA_OIDC_CLIENT_ID",
|
||
"VISIONA_OIDC_CLIENT_SECRET", "VISIONA_OIDC_REDIRECT_URL", "VISIONA_FRONTEND_URL",
|
||
"VISIONA_OIDC_SERVICE_CLIENT_ID", "VISIONA_OIDC_SERVICE_CLIENT_SECRET",
|
||
"VISIONA_SESSION_SECRET", "VISIONA_SESSION_COOKIE_NAME", "VISIONA_SESSION_COOKIE_DOMAIN",
|
||
"VISIONA_SESSION_COOKIE_SECURE", "VISIONA_SESSION_ABSOLUTE_TTL", "VISIONA_SESSION_IDLE_TTL",
|
||
} {
|
||
t.Setenv(k, "")
|
||
}
|
||
|
||
cfg := Load()
|
||
|
||
assert.Empty(t, cfg.OIDC.IssuerURL)
|
||
assert.Empty(t, cfg.OIDC.ClientID)
|
||
assert.Empty(t, cfg.OIDC.ClientSecret)
|
||
assert.Empty(t, cfg.OIDC.RedirectURL)
|
||
assert.Empty(t, cfg.OIDC.PostLoginURL)
|
||
assert.Empty(t, cfg.OIDC.ServiceClientID, "ServiceClientID 預設留空(A1:未啟用)")
|
||
assert.Empty(t, cfg.OIDC.ServiceClientSecret, "ServiceClientSecret 預設留空(A1:未啟用)")
|
||
|
||
assert.Empty(t, cfg.UserSession.Secret, "雛形 dev 預設不附 secret,由 caller 注入或啟動失敗")
|
||
assert.Equal(t, "visiona_session", cfg.UserSession.CookieName)
|
||
assert.Empty(t, cfg.UserSession.CookieDomain)
|
||
assert.False(t, cfg.UserSession.CookieSecure)
|
||
assert.Equal(t, 168*time.Hour, cfg.UserSession.AbsoluteTTL)
|
||
assert.Equal(t, 24*time.Hour, cfg.UserSession.IdleTTL)
|
||
}
|
||
|
||
// TestLoad_OIDC_ClientSecretOptional:A1(2026-05-01)— 缺 ClientSecret 不再回 MissingEnvError。
|
||
//
|
||
// 模擬 Stage 用的 public PKCE-only client(MC 給的 b8093fea... 沒有 client_secret)。
|
||
func TestLoad_OIDC_ClientSecretOptional(t *testing.T) {
|
||
t.Setenv("VISIONA_OIDC_ISSUER_URL", "https://stage-9527.innovedus.com:7850/")
|
||
t.Setenv("VISIONA_OIDC_CLIENT_ID", "b8093fea1a504a5d8f0e04bee9f78f2e")
|
||
t.Setenv("VISIONA_OIDC_CLIENT_SECRET", "") // 故意留空 — public client
|
||
t.Setenv("VISIONA_OIDC_REDIRECT_URL", "https://stage-9527.innovedus.com:9527/api/auth/callback")
|
||
t.Setenv("VISIONA_FRONTEND_URL", "https://stage-9527.innovedus.com:9527")
|
||
t.Setenv("VISIONA_SESSION_SECRET", "32-byte-or-longer-random-secret-aaaa")
|
||
|
||
cfg := Load()
|
||
assert.Empty(t, cfg.OIDC.ClientSecret, "public client mode:ClientSecret 應為空字串")
|
||
assert.NoError(t, cfg.Validate(), "ClientSecret 為空不應觸發 MissingEnvError")
|
||
}
|
||
|
||
// TestLoad_OIDC_ServiceClientFields:A1 預留 client_credentials grant 兩個欄位能正確讀取。
|
||
// 測試固定值故意用顯而易見的 fake — 不要貼任何環境的真實 client_id / secret 進測試。
|
||
func TestLoad_OIDC_ServiceClientFields(t *testing.T) {
|
||
const fakeServiceID = "fake-service-client-id-for-test"
|
||
const fakeServiceSecret = "fake-service-client-secret-for-test"
|
||
|
||
t.Setenv("VISIONA_OIDC_SERVICE_CLIENT_ID", fakeServiceID)
|
||
t.Setenv("VISIONA_OIDC_SERVICE_CLIENT_SECRET", fakeServiceSecret)
|
||
|
||
cfg := Load()
|
||
assert.Equal(t, fakeServiceID, cfg.OIDC.ServiceClientID)
|
||
assert.Equal(t, fakeServiceSecret, cfg.OIDC.ServiceClientSecret)
|
||
}
|
||
|
||
// TestLoad_OIDCAllSet 驗證 OIDC env vars 設定後能正確讀取。
|
||
func TestLoad_OIDCAllSet(t *testing.T) {
|
||
t.Setenv("VISIONA_OIDC_ISSUER_URL", "http://localhost:5050")
|
||
t.Setenv("VISIONA_OIDC_CLIENT_ID", "visionA")
|
||
t.Setenv("VISIONA_OIDC_CLIENT_SECRET", "secret")
|
||
t.Setenv("VISIONA_OIDC_REDIRECT_URL", "http://localhost:3721/api/auth/callback")
|
||
t.Setenv("VISIONA_FRONTEND_URL", "http://localhost:3000")
|
||
t.Setenv("VISIONA_SESSION_SECRET", "32-byte-or-longer-random-secret-aaaa")
|
||
t.Setenv("VISIONA_SESSION_COOKIE_SECURE", "true")
|
||
t.Setenv("VISIONA_SESSION_ABSOLUTE_TTL", "72h")
|
||
t.Setenv("VISIONA_SESSION_IDLE_TTL", "12h")
|
||
|
||
cfg := Load()
|
||
|
||
assert.Equal(t, "http://localhost:5050", cfg.OIDC.IssuerURL)
|
||
assert.Equal(t, "visionA", cfg.OIDC.ClientID)
|
||
assert.Equal(t, "secret", cfg.OIDC.ClientSecret)
|
||
assert.Equal(t, "http://localhost:3721/api/auth/callback", cfg.OIDC.RedirectURL)
|
||
assert.Equal(t, "http://localhost:3000", cfg.OIDC.PostLoginURL)
|
||
assert.Equal(t, "32-byte-or-longer-random-secret-aaaa", cfg.UserSession.Secret)
|
||
assert.True(t, cfg.UserSession.CookieSecure)
|
||
assert.Equal(t, 72*time.Hour, cfg.UserSession.AbsoluteTTL)
|
||
assert.Equal(t, 12*time.Hour, cfg.UserSession.IdleTTL)
|
||
}
|
||
|
||
// TestConfig_Validate_MissingFields 驗證 OIDC 必填欄位缺失時回 MissingEnvError。
|
||
//
|
||
// A1(2026-05-01):ClientSecret 改為選填,已從必填清單移除;剩 5 項必填。
|
||
func TestConfig_Validate_MissingFields(t *testing.T) {
|
||
cfg := &Config{} // 全部欄位 zero value
|
||
err := cfg.Validate()
|
||
require.Error(t, err)
|
||
|
||
var missErr *MissingEnvError
|
||
require.ErrorAs(t, err, &missErr, "錯誤型別應可被 errors.As 解出")
|
||
// 應列出 5 個必填欄位(不含 ClientSecret)
|
||
assert.ElementsMatch(t, []string{
|
||
"VISIONA_OIDC_ISSUER_URL",
|
||
"VISIONA_OIDC_CLIENT_ID",
|
||
"VISIONA_OIDC_REDIRECT_URL",
|
||
"VISIONA_FRONTEND_URL",
|
||
"VISIONA_SESSION_SECRET",
|
||
}, missErr.Vars)
|
||
assert.NotContains(t, missErr.Vars, "VISIONA_OIDC_CLIENT_SECRET",
|
||
"A1:ClientSecret 為選填,不應出現在必填缺失清單")
|
||
}
|
||
|
||
// TestValidate_ConfidentialClient:完整 confidential client(含 ClientSecret)能通過 Validate。
|
||
func TestValidate_ConfidentialClient(t *testing.T) {
|
||
cfg := &Config{
|
||
OIDC: OIDCConfig{
|
||
IssuerURL: "http://localhost:5050",
|
||
ClientID: "visionA",
|
||
ClientSecret: "secret", // 有值 → confidential mode
|
||
RedirectURL: "http://localhost:3721/api/auth/callback",
|
||
PostLoginURL: "http://localhost:3000",
|
||
},
|
||
UserSession: UserSessionConfig{Secret: "session-secret-32-bytes-aaaaaaaaaaaa"},
|
||
}
|
||
assert.NoError(t, cfg.Validate())
|
||
}
|
||
|
||
// TestValidate_PKCEOnlyPublicClient:A1 — 只給 ClientID 沒給 Secret 也能通過 Validate。
|
||
//
|
||
// 對應 Stage 部署的真實情境:MC 配給 visionA 的 client `b8093fea1a504a5d8f0e04bee9f78f2e`
|
||
// 是 public client,沒有 client_secret,靠 PKCE 防 code interception。
|
||
func TestValidate_PKCEOnlyPublicClient(t *testing.T) {
|
||
cfg := &Config{
|
||
OIDC: OIDCConfig{
|
||
IssuerURL: "https://stage-9527.innovedus.com:7850/",
|
||
ClientID: "b8093fea1a504a5d8f0e04bee9f78f2e",
|
||
// ClientSecret 留空 — public PKCE-only client
|
||
RedirectURL: "https://stage-9527.innovedus.com:9527/api/auth/callback",
|
||
PostLoginURL: "https://stage-9527.innovedus.com:9527",
|
||
},
|
||
UserSession: UserSessionConfig{Secret: "session-secret-32-bytes-aaaaaaaaaaaa"},
|
||
}
|
||
assert.NoError(t, cfg.Validate(),
|
||
"A1:public PKCE-only client(ClientSecret 留空)應通過 Validate")
|
||
}
|
||
|
||
// TestValidate_ServiceClientFieldsNotChecked:A1 — ServiceClientID/Secret 留空不影響 Validate。
|
||
//
|
||
// 兩個欄位是 client_credentials grant 預留鉤子,A1 階段不啟用、不檢查。
|
||
func TestValidate_ServiceClientFieldsNotChecked(t *testing.T) {
|
||
cfg := &Config{
|
||
OIDC: OIDCConfig{
|
||
IssuerURL: "http://localhost:5050",
|
||
ClientID: "visionA",
|
||
RedirectURL: "http://localhost:3721/api/auth/callback",
|
||
PostLoginURL: "http://localhost:3000",
|
||
// 兩個 Service* 都留空 — 預期通過
|
||
},
|
||
UserSession: UserSessionConfig{Secret: "session-secret-32-bytes-aaaaaaaaaaaa"},
|
||
}
|
||
assert.NoError(t, cfg.Validate())
|
||
}
|
||
|
||
// TestLoad_CORSAllowedOrigins 驗證 VISIONA_CORS_ALLOWED_ORIGINS 的逗號分隔解析。
|
||
// 空字串 / 純分隔字元 → fallback 到 nil(交由 api.Deps.validate 塞預設)。
|
||
func TestLoad_CORSAllowedOrigins(t *testing.T) {
|
||
// 未設 → nil
|
||
t.Setenv("VISIONA_CORS_ALLOWED_ORIGINS", "")
|
||
cfg := Load()
|
||
assert.Nil(t, cfg.CORS.AllowedOrigins)
|
||
|
||
// 單一 origin
|
||
t.Setenv("VISIONA_CORS_ALLOWED_ORIGINS", "http://localhost:3000")
|
||
cfg = Load()
|
||
assert.Equal(t, []string{"http://localhost:3000"}, cfg.CORS.AllowedOrigins)
|
||
|
||
// 多個 origin + trim space
|
||
t.Setenv("VISIONA_CORS_ALLOWED_ORIGINS", "http://a.com, http://b.com ,http://c.com")
|
||
cfg = Load()
|
||
assert.Equal(t, []string{"http://a.com", "http://b.com", "http://c.com"}, cfg.CORS.AllowedOrigins)
|
||
|
||
// 只有分隔字元 → fallback(過濾後 len == 0)
|
||
t.Setenv("VISIONA_CORS_ALLOWED_ORIGINS", " , ,")
|
||
cfg = Load()
|
||
assert.Nil(t, cfg.CORS.AllowedOrigins)
|
||
}
|
||
|
||
// TestLoad_ConversionDefaults 驗證 Phase 0.8 / 0.8b conversion 欄位的預設行為。
|
||
//
|
||
// 對齊 docs/autoflow/04-architecture/conversion.md §3 + ADR-015 + ADR-016:留空時
|
||
// Enabled() 為 false,5 個 endpoint 不會 wire(main.go 在 wire 階段會跳過)。
|
||
//
|
||
// Phase 0.8b T5:原暫留欄位 TenantID / DelegatedTTLSeconds 已從 ConversionConfig 移除
|
||
// (MC 認證鏈與 delegated download token 機制不存在了);本 test 不再驗這兩欄位。
|
||
//
|
||
// Phase 0.8b v0.6 T4:原 FAA 相關欄位 FAABaseURL / FAAAPIKey 已移除(ADR-016 撤回
|
||
// v0.5 設計缺口,visionA 端不再直接呼叫 FAA);本 test 不再驗這兩欄位,對應的
|
||
// VISIONA_FAA_BASE_URL / VISIONA_FAA_API_KEY env 也不再 clear。
|
||
func TestLoad_ConversionDefaults(t *testing.T) {
|
||
for _, k := range []string{
|
||
"VISIONA_CONVERTER_BASE_URL",
|
||
"VISIONA_CONVERTER_API_KEY",
|
||
"VISIONA_CONVERTER_MAX_MODEL_SIZE_MB",
|
||
} {
|
||
t.Setenv(k, "")
|
||
}
|
||
|
||
cfg := Load()
|
||
assert.Empty(t, cfg.Conversion.ConverterBaseURL)
|
||
assert.Empty(t, cfg.Conversion.ConverterAPIKey, "Phase 0.8b:API key 預設留空")
|
||
assert.Equal(t, 500, cfg.Conversion.MaxModelSizeMB, "預設 500 MB(與 converter 對齊)")
|
||
assert.False(t, cfg.Conversion.Enabled(), "全空 → 不啟用")
|
||
}
|
||
|
||
// TestLoad_ConversionEnabled 驗證 Conversion.Enabled() 的判定邏輯。
|
||
//
|
||
// Phase 0.8b v0.6 T4 簡化(ADR-016):visionA 端撤回對 FAA 的直接呼叫後,
|
||
// 只剩 ConverterBaseURL + ConverterAPIKey 兩個欄位需非空;任一缺即 disable。
|
||
func TestLoad_ConversionEnabled(t *testing.T) {
|
||
cases := []struct {
|
||
name string
|
||
converterURL string
|
||
converterKey string
|
||
wantEnabled bool
|
||
}{
|
||
{"all_set_enables",
|
||
"http://converter:9501",
|
||
"converter-key-32-bytes-hex-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||
true},
|
||
{"missing_converter_url_disabled",
|
||
"",
|
||
"converter-key",
|
||
false},
|
||
{"missing_converter_key_disabled",
|
||
"http://converter:9501",
|
||
"",
|
||
false},
|
||
{"all_empty_disabled",
|
||
"", "",
|
||
false},
|
||
}
|
||
|
||
for _, tc := range cases {
|
||
tc := tc
|
||
t.Run(tc.name, func(t *testing.T) {
|
||
t.Setenv("VISIONA_CONVERTER_BASE_URL", tc.converterURL)
|
||
t.Setenv("VISIONA_CONVERTER_API_KEY", tc.converterKey)
|
||
cfg := Load()
|
||
assert.Equal(t, tc.wantEnabled, cfg.Conversion.Enabled())
|
||
})
|
||
}
|
||
}
|
||
|
||
// TestLoad_ConversionAllSet 驗證 Phase 0.8b 所有欄位設定後正確讀取。
|
||
//
|
||
// Phase 0.8b T5:原暫留欄位 TenantID / DelegatedTTLSeconds 已移除,本 test
|
||
// 不再驗這兩欄位(對應 env 也不再讀取)。
|
||
//
|
||
// Phase 0.8b v0.6 T4:原 FAA 相關欄位 FAABaseURL / FAAAPIKey 已移除(ADR-016),
|
||
// 本 test 不再驗這兩欄位、對應 env 也不再讀取。
|
||
func TestLoad_ConversionAllSet(t *testing.T) {
|
||
const fakeConverterKey = "fake-converter-api-key-for-test-do-not-use-in-prod"
|
||
|
||
t.Setenv("VISIONA_CONVERTER_BASE_URL", "http://192.168.0.130:9501")
|
||
t.Setenv("VISIONA_CONVERTER_API_KEY", fakeConverterKey)
|
||
t.Setenv("VISIONA_CONVERTER_MAX_MODEL_SIZE_MB", "300")
|
||
|
||
cfg := Load()
|
||
assert.Equal(t, "http://192.168.0.130:9501", cfg.Conversion.ConverterBaseURL)
|
||
assert.Equal(t, fakeConverterKey, cfg.Conversion.ConverterAPIKey)
|
||
assert.Equal(t, 300, cfg.Conversion.MaxModelSizeMB)
|
||
assert.True(t, cfg.Conversion.Enabled())
|
||
}
|
||
|
||
// TestLoad_ConversionAPIKeysOnly:Phase 0.8b v0.6 T4 — 2 個必要欄位齊全即 Enabled。
|
||
//
|
||
// 本 test 與 TestLoad_ConversionAllSet 共同覆蓋 Enabled() 的最小啟用條件:
|
||
// 只要 ConverterBaseURL + ConverterAPIKey 兩個欄位都非空、即視為啟用,
|
||
// MaxModelSizeMB 不影響啟用判定。
|
||
func TestLoad_ConversionAPIKeysOnly(t *testing.T) {
|
||
const fakeConverterKey = "fake-converter-api-key-only-test"
|
||
|
||
t.Setenv("VISIONA_CONVERTER_BASE_URL", "http://192.168.0.130:9501")
|
||
t.Setenv("VISIONA_CONVERTER_API_KEY", fakeConverterKey)
|
||
|
||
cfg := Load()
|
||
assert.True(t, cfg.Conversion.Enabled(),
|
||
"Phase 0.8b v0.6 T4:2 個必要欄位齊全即 Enabled")
|
||
}
|