Local branch had diverged from origin/master (sibling commits on the same base). Rewrote local history linearly on top of origin/master, folding in all local content: exa/hot-platform discovery hunters, leaks ledger, nightly sweep orchestrator, updated .gitignore and skill docs, plus the latest hunt output and vault state. Remote-only files (vault keys, channel scripts) were restored rather than dropped, so the resulting tree is a full union of both sides.
1 line
19 KiB
JSON
1 line
19 KiB
JSON
{"repo": "aqww5941-wq/ai-gateway", "files": {"config/config_test.go": {"330ba3dbb25f40e09da3527c0a1ce3ba190436aa": {"t": 1786068287.986764, "n": 17147, "h": "6a61d966c80a7e3d", "c": "package config\n\nimport (\n\t\"errors\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n)\n\nconst minimalConfigYAML = `providers:\n - name: primary\n type: openai\n api_key: not-a-real-key\n base_url: https://example.invalid/v1\n models: [model-a]\nroutes:\n - name: default\n match:\n model: model-a\n strategy: round_robin\n targets:\n - provider: primary\n model: model-a\n`\n\nfunc TestLoadAppliesDeterministicDefaults(t *testing.T) {\n\tcfg, err := loadTestConfig(t, minimalConfigYAML)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif cfg.Server.Port != 8081 || cfg.Server.ReadTimeout != 30*time.Second || cfg.Server.WriteTimeout != 120*time.Second {\n\t\tt.Fatalf(\"server defaults = port %d, read %s, write %s\", cfg.Server.Port, cfg.Server.ReadTimeout, cfg.Server.WriteTimeout)\n\t}\n\tif cfg.Server.DBPath != \"data/gateway.db\" || cfg.Server.MaxConcurrency != 500 || cfg.Server.QueueSize != 200 || cfg.Server.QueueTimeout != 10*time.Second {\n\t\tt.Fatalf(\"server resource defaults = %#v\", cfg.Server)\n\t}\n\tif got := cfg.Server.Transport; got != (TransportConfig{MaxConnsPerHost: 100, MaxIdleConnsPerHost: 50, MaxIdleConns: 200}) {\n\t\tt.Fatalf(\"transport defaults = %#v\", got)\n\t}\n\tif cfg.Providers[0].Timeout != 30*time.Second {\n\t\tt.Fatalf(\"provider timeout = %s, want 30s\", cfg.Providers[0].Timeout)\n\t}\n\tif cfg.RateLimit.PerKey != 60 || cfg.RateLimit.PerModel != 100 {\n\t\tt.Fatalf(\"rate limit defaults = %#v\", cfg.RateLimit)\n\t}\n\tif cfg.Cache.Backend != \"memory\" || cfg.Cache.TTL != \"1h\" || cfg.Cache.Strategy != \"exact\" || cfg.Cache.MaxSize != 1000 || cfg.Cache.Threshold != 0.85 || cfg.Cache.RedisAddr != \"localhost:6379\" {\n\t\tt.Fatalf(\"cache defaults = %#v\", cfg.Cache)\n\t}\n\tif cfg.Tracing.Exporter != \"stdout\" || cfg.Tracing.ServiceName != \"ai-gateway\" || cfg.Tracing.SampleRatio != 1 {\n\t\tt.Fatalf(\"tracing defaults = %#v\", cfg.Tracing)\n\t}\n\tif cfg.Filter.Mode != \"mask\" {\n\t\tt.Fatalf(\"filter mode = %q, want mask\", cfg.Filter.Mode)\n\t}\n}\n\nfunc TestLoadCurrentExample(t *testing.T) {\n\tcfg, err := Load(\"gateway.yaml\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif cfg.Server.ReadTimeout != 30*time.Second || cfg.Server.WriteTimeout != 120*time.Second {\n\t\tt.Fatalf(\"example timeouts = %s/%s\", cfg.Server.ReadTimeout, cfg.Server.WriteTimeout)\n\t}\n\tif cfg.Auth.Enabled || len(cfg.Auth.Keys) != 0 {\n\t\tt.Fatalf(\"example auth = %#v, want disabled without seed keys\", cfg.Auth)\n\t}\n\tif len(cfg.Providers) != 4 {\n\t\tt.Fatalf(\"example providers = %d, want one invalid legacy provider and three native declarations\", len(cfg.Providers))\n\t}\n\n\twantCredentials := map[string]string{\n\t\t\"ark\": \"ARK_API_KEY\",\n\t\t\"deepseek\": \"DEEPSEEK_API_KEY\",\n\t\t\"qwen\": \"DASHSCOPE_API_KEY\",\n\t}\n\tfor _, provider := range cfg.Providers {\n\t\tif provider.Kind == \"\" {\n\t\t\tif provider.BaseURL != \"https://example.invalid/v1\" || provider.APIKey != \"invalid-example-provider-key\" {\n\t\t\t\tt.Fatalf(\"legacy example is not safely invalid: %#v\", provider)\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\t\tif provider.RuntimeEnabled() || provider.Evidence.Status != \"unverified\" {\n\t\t\tt.Fatalf(\"native provider %q must be disabled/unverified: %#v\", provider.Name, provider)\n\t\t}\n\t\tif provider.Credential.Env != wantCredentials[provider.Kind] || provider.APIKey != \"\" {\n\t\t\tt.Fatalf(\"native provider %q credential = %#v, want env reference only\", provider.Name, provider.Credential)\n\t\t}\n\t}\n\tif cfg.Providers[3].Qwen == nil || cfg.Providers[3].Qwen.WorkspaceID != \"invalid-example-workspace-id\" || len(cfg.Providers[3].Models) != 1 || cfg.Providers[3].Models[0] != \"qwen3.7-flash\" {\n\t\tt.Fatalf(\"qwen bootstrap declaration = %#v\", cfg.Providers[3])\n\t}\n\tif len(cfg.Routes) != 1 || cfg.Routes[0].Targets[0].Provider != \"legacy-invalid-example\" {\n\t\tt.Fatalf(\"example routes = %#v, want only the invalid bootstrap route\", cfg.Routes)\n\t}\n}\n\nfunc TestREADMEDeclaresNativeProvidersAsBootstrapOnly(t *testing.T) {\n\tdata, err := os.ReadFile(filepath.Join(\"..\", \"README.md\"))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tcontents := string(data)\n\tfor _, required := range []string{\n\t\t\"kind: ark\",\n\t\t\"kind: deepseek\",\n\t\t\"kind: qwen\",\n\t\t\"env: ARK_API_KEY\",\n\t\t\"env: DEEPSEEK_API_KEY\",\n\t\t\"env: DASHSCOPE_API_KEY\",\n\t\t\"qwen3.7-flash\",\n\t\t\"status: unverified\",\n\t\t\"Native Adapter 尚未实现\",\n\t} {\n\t\tif !strings.Contains(contents, required) {\n\t\t\tt.Errorf(\"README.md missing %q\", required)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\n\t\t\"覆盖 DeepSeek / SiliconFlow / 豆包等所有 OpenAI 兼容端点\",\n\t\t\"SILICONFLOW_API_KEY\",\n\t\t\"DOUBAO_API_KEY\",\n\t\t\"Bearer sk-test\",\n\t} {\n\t\tif strings.Contains(contents, forbidden) {\n\t\t\tt.Errorf(\"README.md still contains out-of-scope or unsafe claim %q\", forbidden)\n\t\t}\n\t}\n}\n\nfunc TestLoadAcceptsIndependentNativeBootstrapSchemas(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tyaml string\n\t}{\n\t\t{name: \"ark\", yaml: `providers:\n - name: ark-bootstrap\n kind: ark\n enabled: false\n credential: {env: ARK_API_KEY}\n evidence: {status: unverified}\n ark:\n base_url: https://ark.cn-beijing.volces.com/api/v3\n region: cn-beijing\n protocol_version: responses-v1\n endpoint_id: invalid-example-endpoint-id\n models: [invalid-example-model]\nroutes: []\n`},\n\t\t{name: \"deepseek\", yaml: `providers:\n - name: deepseek-bootstrap\n kind: deepseek\n enabled: false\n credential: {env: DEEPSEEK_API_KEY}\n evidence: {status: unverified}\n deepseek:\n base_url: https://api.deepseek.com\n region: global\n protocol_version: chat-completions-v1\n endpoint: stable\n models: [deepseek-v4-flash]\nroutes: []\n`},\n\t\t{name: \"qwen\", yaml: nativeQwenBootstrapYAML},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tcfg, err := loadTestConfig(t, tt.yaml)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tif len(cfg.Providers) != 1 || cfg.Providers[0].Kind != tt.name || cfg.Providers[0].RuntimeEnabled() {\n\t\t\t\tt.Fatalf(\"provider = %#v, want disabled %s declaration\", cfg.Providers, tt.name)\n\t\t\t}\n\t\t\tif len(cfg.Routes) != 0 {\n\t\t\t\tt.Fatalf(\"routes = %#v, want no route for disabled provider\", cfg.Routes)\n\t\t\t}\n\t\t})\n\t}\n}\n\nconst nativeQwenBootstrapYAML = `providers:\n - name: qwen-bootstrap\n kind: qwen\n enabled: false\n credential: {env: DASHSCOPE_API_KEY}\n evidence: {status: unverified}\n qwen:\n base_url: https://invalid-example-workspace-id.cn-beijing.maas.aliyuncs.com/compatible-mode/v1\n region: cn-beijing\n protocol_version: chat-completions-v1\n workspace_id: invalid-example-workspace-id\n models: [qwen3.7-flash]\nroutes: []\n`\n\nfunc TestLoadRejectsUnsafeOrUnrunnableNativeBootstrapConfiguration(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tyaml string\n\t\tpath string\n\t\tproblem string\n\t}{\n\t\t{\n\t\t\tname: \"enabled before adapter exists\",\n\t\t\tyaml: strings.Replace(nativeQwenBootstrapYAML, \"enabled: false\", \"enabled: true\", 1),\n\t\t\tpath: \"providers[0].enabled\",\n\t\t\tproblem: `adapter for provider kind \"qwen\" is not implemented; set enabled to false`,\n\t\t},\n\t\t{\n\t\t\tname: \"enabled state omitted\",\n\t\t\tyaml: strings.Replace(nativeQwenBootstrapYAML, \" enabled: false\\n\", \"\", 1),\n\t\t\tpath: \"providers[0].enabled\",\n\t\t\tproblem: \"must be explicitly set to false while the native adapter is unavailable\",\n\t\t},\n\t\t{\n\t\t\tname: \"evidence overclaims support\",\n\t\t\tyaml: strings.Replace(nativeQwenBootstrapYAML, \"status: unverified\", \"status: verified\", 1),\n\t\t\tpath: \"providers[0].evidence.status\",\n\t\t\tproblem: \"must be unverified while the native adapter is unavailable\",\n\t\t},\n\t\t{\n\t\t\tname: \"credential is a value instead of a reference\",\n\t\t\tyaml: strings.Replace(nativeQwenBootstrapYAML, \"DASHSCOPE_API_KEY\", \"literal-secret-value\", 1),\n\t\t\tpath: \"providers[0].credential.env\",\n\t\t\tproblem: \"must be an uppercase environment variable name\",\n\t\t},\n\t\t{\n\t\t\tname: \"workspace host does not match workspace reference\",\n\t\t\tyaml: strings.Replace(nativeQwenBootstrapYAML, \"https://invalid-example-workspace-id.cn-beijing.maas.aliyuncs.com\", \"https://another-workspace.cn-beijing.maas.aliyuncs.com\", 1),\n\t\t\tpath: \"providers[0].qwen.base_url\",\n\t\t\tproblem: \"must use the controlled endpoint https://invalid-example-workspace-id.cn-beijing.maas.aliyuncs.com/compatible-mode/v1\",\n\t\t},\n\t\t{\n\t\t\tname: \"route references disabled provider\",\n\t\t\tyaml: strings.Replace(nativeQwenBootstrapYAML, \"routes: []\", `routes:\n - name: invalid-route\n match: {model: qwen}\n strategy: round_robin\n targets: [{provider: qwen-bootstrap, model: qwen3.7-flash}]`, 1),\n\t\t\tpath: \"routes[0].targets[0].provider\",\n\t\t\tproblem: `references disabled provider \"qwen-bootstrap\"`,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\t_, err := loadTestConfig(t, tt.yaml)\n\t\t\tassertConfigError(t, err, ErrorKindValidation, tt.path, tt.problem)\n\t\t})\n\t}\n}\n\nfunc TestLoadRejectsMissingEnvironmentVariablesInSortedOrder(t *testing.T) {\n\tfor _, name := range []string{\"TASK3_MISSING_A\", \"TASK3_MISSING_B\"} {\n\t\tvalue, existed := os.LookupEnv(name)\n\t\tif err := os.Unsetenv(name); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tt.Cleanup(func() {\n\t\t\tif existed {\n\t\t\t\t_ = os.Setenv(name, value)\n\t\t\t} else {\n\t\t\t\t_ = os.Unsetenv(name)\n\t\t\t}\n\t\t})\n\t}\n\n\t_, err := loadTestConfig(t, strings.ReplaceAll(minimalConfigYAML, \"not-a-real-key\", \"${TASK3_MISSING_B}-${TASK3_MISSING_A}\"))\n\tassertConfigError(t, err, ErrorKindEnvironment, \"environment\", \"missing variables: TASK3_MISSING_A, TASK3_MISSING_B\")\n}\n\nfunc TestLoadRejectsUnknownFields(t *testing.T) {\n\t_, err := loadTestConfig(t, \"server:\\n mystery_timeout: 5s\\n\"+minimalConfigYAML)\n\tif err == nil {\n\t\tt.Fatal(\"Load() error = nil, want unknown field error\")\n\t}\n\tkind, ok := ErrorKindOf(err)\n\tif !ok || kind != ErrorKindParse {\n\t\tt.Fatalf(\"ErrorKindOf() = %q, %v, want %q, true\", kind, ok, ErrorKindParse)\n\t}\n\tif !strings.Contains(err.Error(), \"field mystery_timeout not found in type config.ServerConfig\") {\n\t\tt.Fatalf(\"Load() error = %q, want located unknown field\", err)\n\t}\n}\n\nfunc TestLoadReadErrorRetainsCause(t *testing.T) {\n\tpath := filepath.Join(t.TempDir(), \"missing.yaml\")\n\t_, err := Load(path)\n\tif !errors.Is(err, os.ErrNotExist) {\n\t\tt.Fatalf(\"Load() error = %v, want os.ErrNotExist cause\", err)\n\t}\n\tkind, ok := ErrorKindOf(err)\n\tif !ok || kind != ErrorKindRead {\n\t\tt.Fatalf(\"ErrorKindOf() = %q, %v, want %q, true\", kind, ok, ErrorKindRead)\n\t}\n}\n\nfunc TestLoadRejectsMalformedDuration(t *testing.T) {\n\t_, err := loadTestConfig(t, \"server:\\n read_timeout: immediately\\n\"+minimalConfigYAML)\n\tif err == nil {\n\t\tt.Fatal(\"Load() error = nil, want duration parse error\")\n\t}\n\tkind, ok := ErrorKindOf(err)\n\tif !ok || kind != ErrorKindParse {\n\t\tt.Fatalf(\"ErrorKindOf() = %q, %v, want %q, true\", kind, ok, ErrorKindParse)\n\t}\n\tif !strings.Contains(err.Error(), \"line 2\") || !strings.Contains(err.Error(), \"time.Duration\") {\n\t\tt.Fatalf(\"Load() error = %q, want line and duration type\", err)\n\t}\n}\n\nfunc TestLoadRejectsMultipleYAMLDocuments(t *testing.T) {\n\t_, err := loadTestConfig(t, minimalConfigYAML+\"---\\n{}\\n\")\n\tassertConfigError(t, err, ErrorKindParse, \"document\", \"multiple YAML documents are not allowed\")\n}\n\nfunc TestLoadDoesNotReplaceExplicitZeroWithDefaults(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tprefix string\n\t\tpath string\n\t\tproblem string\n\t}{\n\t\t{name: \"port\", prefix: \"server:\\n port: 0\\n\", path: \"server.port\", problem: \"must be between 1 and 65535\"},\n\t\t{name: \"read timeout\", prefix: \"server:\\n read_timeout: 0s\\n\", path: \"server.read_timeout\", problem: \"must be greater than 0\"},\n\t\t{name: \"sample ratio\", prefix: \"tracing:\\n sample_ratio: 0\\n\", path: \"tracing.sample_ratio\", problem: \"must be greater than 0 and less than or equal to 1\"},\n\t\t{name: \"cache threshold\", prefix: \"cache:\\n threshold: 0\\n\", path: \"cache.threshold\", problem: \"must be greater than 0 and less than or equal to 1\"},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\t_, err := loadTestConfig(t, tt.prefix+minimalConfigYAML)\n\t\t\tassertConfigError(t, err, ErrorKindValidation, tt.path, tt.problem)\n\t\t})\n\t}\n}\n\nfunc TestLoadDistinguishesMissingAndExplicitZeroProviderTimeout(t *testing.T) {\n\tcfg, err := loadTestConfig(t, minimalConfigYAML)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif cfg.Providers[0].Timeout != 30*time.Second {\n\t\tt.Fatalf(\"missing timeout default = %s, want 30s\", cfg.Providers[0].Timeout)\n\t}\n\n\twithZero := strings.Replace(minimalConfigYAML, \" models: [model-a]\", \" models: [model-a]\\n timeout: 0s\", 1)\n\t_, err = loadTestConfig(t, withZero)\n\tassertConfigError(t, err, ErrorKindValidation, \"providers[0].timeout\", \"must be greater than 0\")\n\n\twithNull := strings.Replace(minimalConfigYAML, \" models: [model-a]\", \" models: [model-a]\\n timeout: null\", 1)\n\t_, err = loadTestConfig(t, withNull)\n\tassertConfigError(t, err, ErrorKindValidation, \"providers[0].timeout\", \"must be greater than 0\")\n}\n\nfunc TestValidateRejectsInvalidAndDanglingConfiguration(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tmutate func(*Config)\n\t\tpath string\n\t\tproblem string\n\t}{\n\t\t{\n\t\t\tname: \"port\",\n\t\t\tmutate: func(cfg *Config) { cfg.Server.Port = 65536 },\n\t\t\tpath: \"server.port\", problem: \"must be between 1 and 65535\",\n\t\t},\n\t\t{\n\t\t\tname: \"duration\",\n\t\t\tmutate: func(cfg *Config) { cfg.Server.ReadTimeout = -time.Second },\n\t\t\tpath: \"server.read_timeout\", problem: \"must be greater than 0\",\n\t\t},\n\t\t{\n\t\t\tname: \"ratio\",\n\t\t\tmutate: func(cfg *Config) { cfg.Tracing.SampleRatio = 1.01 },\n\t\t\tpath: \"tracing.sample_ratio\", problem: \"must be greater than 0 and less than or equal to 1\",\n\t\t},\n\t\t{\n\t\t\tname: \"duplicate provider\",\n\t\t\tmutate: func(cfg *Config) { cfg.Providers = append(cfg.Providers, cfg.Providers[0]) },\n\t\t\tpath: \"providers[1].name\", problem: `duplicates provider name \"primary\"`,\n\t\t},\n\t\t{\n\t\t\tname: \"duplicate route name\",\n\t\t\tmutate: func(cfg *Config) { cfg.Routes = append(cfg.Routes, cfg.Routes[0]) },\n\t\t\tpath: \"routes[1].name\", problem: `duplicates route name \"default\"`,\n\t\t},\n\t\t{\n\t\t\tname: \"duplicate route model\",\n\t\t\tmutate: func(cfg *Config) {\n\t\t\t\tduplicate := cfg.Routes[0]\n\t\t\t\tduplicate.Name = \"another\"\n\t\t\t\tcfg.Routes = append(cfg.Routes, duplicate)\n\t\t\t},\n\t\t\tpath: \"routes[1].match.model\", problem: `duplicates route model \"model-a\"`,\n\t\t},\n\t\t{\n\t\t\tname: \"unknown provider reference\",\n\t\t\tmutate: func(cfg *Config) { cfg.Routes[0].Targets[0].Provider = \"missing\" },\n\t\t\tpath: \"routes[0].targets[0].provider\", problem: `references unknown provider \"missing\"`,\n\t\t},\n\t\t{\n\t\t\tname: \"unknown provider model reference\",\n\t\t\tmutate: func(cfg *Config) { cfg.Routes[0].Targets[0].Model = \"missing-model\" },\n\t\t\tpath: \"routes[0].targets[0].model\", problem: `references model \"missing-model\" not declared by provider \"primary\"`,\n\t\t},\n\t\t{\n\t\t\tname: \"unknown route reference\",\n\t\t\tmutate: func(cfg *Config) {\n\t\t\t\tcfg.Auth.Keys = []KeyConfig{{Token: \"not-a-real-token\", Name: \"user\", Role: \"user\", Models: \"missing-route\"}}\n\t\t\t},\n\t\t\tpath: \"auth.keys[0].models\", problem: `references unknown route model \"missing-route\"`,\n\t\t},\n\t\t{\n\t\t\tname: \"invalid route strategy\",\n\t\t\tmutate: func(cfg *Config) { cfg.Routes[0].Strategy = \"random\" },\n\t\t\tpath: \"routes[0].strategy\", problem: \"must be one of: fallback, latency, round_robin, semantic, weighted\",\n\t\t},\n\t\t{\n\t\t\tname: \"incomplete semantic route\",\n\t\t\tmutate: func(cfg *Config) {\n\t\t\t\tcfg.Routes[0].Strategy = \"semantic\"\n\t\t\t\tcfg.Routes[0].Targets = nil\n\t\t\t\tcfg.Routes[0].SemanticRules = []SemanticRuleConfig{{Complexity: \"simple\", Target: RouteTarget{Provider: \"primary\", Model: \"model-a\"}}}\n\t\t\t},\n\t\t\tpath: \"routes[0].semantic_rules\", problem: `missing \"complex\" complexity rule`,\n\t\t},\n\t\t{\n\t\t\tname: \"invalid cache duration\",\n\t\t\tmutate: func(cfg *Config) { cfg.Cache.TTL = \"0s\" },\n\t\t\tpath: \"cache.ttl\", problem: \"must be greater than 0\",\n\t\t},\n\t\t{\n\t\t\tname: \"invalid filter rule\",\n\t\t\tmutate: func(cfg *Config) { cfg.Filter.Rules = []string{\"unknown\"} },\n\t\t\tpath: \"filter.rules[0]\", problem: `unknown filter rule \"unknown\"`,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tcfg := validTestConfig(t)\n\t\t\ttt.mutate(cfg)\n\t\t\terr := Validate(cfg)\n\t\t\tassertConfigError(t, err, ErrorKindValidation, tt.path, tt.problem)\n\t\t})\n\t}\n}\n\nfunc TestErrorKindOfRejectsNonConfigError(t *testing.T) {\n\tif kind, ok := ErrorKindOf(errors.New(\"other\")); ok || kind != \"\" {\n\t\tt.Fatalf(\"ErrorKindOf() = %q, %v, want empty, false\", kind, ok)\n\t}\n}\n\nfunc validTestConfig(t *testing.T) *Config {\n\tt.Helper()\n\tcfg, err := decodeStrict(minimalConfigYAML)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif err := Validate(cfg); err != nil {\n\t\tt.Fatal(err)\n\t}\n\treturn cfg\n}\n\nfunc loadTestConfig(t *testing.T, contents string) (*Config, error) {\n\tt.Helper()\n\tpath := filepath.Join(t.TempDir(), \"gateway.yaml\")\n\tif err := os.WriteFile(path, []byte(contents), 0o600); err != nil {\n\t\tt.Fatal(err)\n\t}\n\treturn Load(path)\n}\n\nfunc assertConfigError(t *testing.T, err error, kind ErrorKind, path, problem string) {\n\tt.Helper()\n\tif err == nil {\n\t\tt.Fatalf(\"error = nil, want %s error\", kind)\n\t}\n\tvar configErr *ConfigError\n\tif !errors.As(err, &configErr) {\n\t\tt.Fatalf(\"error = %T %v, want *ConfigError\", err, err)\n\t}\n\tif configErr.Kind != kind || configErr.Path != path || configErr.Problem != problem {\n\t\tt.Fatalf(\"ConfigError = %#v, want kind=%q path=%q problem=%q\", configErr, kind, path, problem)\n\t}\n}\n"}}}} |