Skip to content

Commit 1f241d2

Browse files
test(worker): drive non-jobs packages to >=95% coverage
Adds greenfield suites for the three previously-zero-test packages (config, metrics, provisioner) plus fills coverage gaps in db, email, obs, handlers, and telemetry. A small test-injection seam in telemetry/tracer.go makes the exporter/resource build-error branches reachable without a broken collector. Coverage: db 97.1%, email 95.2%, obs 100%, metrics 100%, provisioner 95.8%, handlers 100%, telemetry 100%, config 100%. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 793486e commit 1f241d2

9 files changed

Lines changed: 1520 additions & 2 deletions

File tree

internal/config/config_test.go

Lines changed: 308 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,308 @@
1+
package config
2+
3+
import (
4+
"strings"
5+
"testing"
6+
)
7+
8+
// clearEnv unsets every env var Load reads so each test starts from a known
9+
// blank slate. t.Setenv restores values on cleanup, but it does not unset
10+
// pre-existing process env, so we explicitly clear first.
11+
func clearEnv(t *testing.T) {
12+
t.Helper()
13+
for _, k := range []string{
14+
"DATABASE_URL", "REDIS_URL", "PROVISIONER_ADDR", "PROVISIONER_SECRET",
15+
"EMAIL_PROVIDER", "BREVO_API_KEY", "BREVO_TEMPLATE_IDS", "BREVO_SENDER_EMAIL",
16+
"BREVO_SENDER_NAME", "SES_AWS_REGION", "SES_AWS_ACCESS_KEY_ID",
17+
"SES_AWS_SECRET_ACCESS_KEY", "SES_FROM_EMAIL", "SES_TEMPLATE_NAMES",
18+
"ENVIRONMENT", "MAXMIND_LICENSE_KEY", "GEOLITE2_DB_PATH", "PLANS_PATH",
19+
"OBJECT_STORE_ENDPOINT", "OBJECT_STORE_ACCESS_KEY", "OBJECT_STORE_SECRET_KEY",
20+
"OBJECT_STORE_BUCKET", "OBJECT_STORE_REGION", "OBJECT_STORE_SECURE",
21+
"MINIO_ENDPOINT", "MINIO_ROOT_USER", "MINIO_ROOT_PASSWORD", "MINIO_BUCKET_NAME",
22+
"KUBE_NAMESPACE_APPS", "INSTANT_API_INTERNAL_URL", "WORKER_INTERNAL_JWT_SECRET",
23+
"AES_KEY", "OBJECT_STORE_BACKEND", "BACKUP_S3_BUCKET", "BACKUP_S3_PATH_PREFIX",
24+
"PLATFORM_BACKUP_S3_PREFIX", "CUSTOMER_DATABASE_URL", "MONGO_ADMIN_URI",
25+
"CUSTOMER_REDIS_URL",
26+
} {
27+
// Setenv("") then unset semantics: t.Setenv records the original and
28+
// restores it; setting to "" is enough since Load treats "" as unset.
29+
t.Setenv(k, "")
30+
}
31+
}
32+
33+
func TestErrMissingConfig_Error(t *testing.T) {
34+
err := &ErrMissingConfig{Key: "DATABASE_URL"}
35+
got := err.Error()
36+
if !strings.Contains(got, "DATABASE_URL") {
37+
t.Fatalf("error message missing key: %q", got)
38+
}
39+
if !strings.Contains(got, "not set") {
40+
t.Fatalf("unexpected error message: %q", got)
41+
}
42+
}
43+
44+
func TestGetenv(t *testing.T) {
45+
t.Setenv("CFG_TEST_KEY", "value")
46+
if got := getenv("CFG_TEST_KEY", "fb"); got != "value" {
47+
t.Fatalf("getenv set: got %q want value", got)
48+
}
49+
t.Setenv("CFG_TEST_KEY", "")
50+
if got := getenv("CFG_TEST_KEY", "fallback"); got != "fallback" {
51+
t.Fatalf("getenv empty: got %q want fallback", got)
52+
}
53+
}
54+
55+
func TestRequire_Panics(t *testing.T) {
56+
t.Setenv("CFG_REQ_KEY", "")
57+
defer func() {
58+
r := recover()
59+
if r == nil {
60+
t.Fatal("require did not panic on missing key")
61+
}
62+
e, ok := r.(*ErrMissingConfig)
63+
if !ok {
64+
t.Fatalf("panic value type = %T, want *ErrMissingConfig", r)
65+
}
66+
if e.Key != "CFG_REQ_KEY" {
67+
t.Fatalf("panic key = %q", e.Key)
68+
}
69+
}()
70+
require("CFG_REQ_KEY")
71+
}
72+
73+
func TestRequire_Present(t *testing.T) {
74+
t.Setenv("CFG_REQ_KEY", "x")
75+
if got := require("CFG_REQ_KEY"); got != "x" {
76+
t.Fatalf("require present: got %q", got)
77+
}
78+
}
79+
80+
func TestLoad_Defaults(t *testing.T) {
81+
clearEnv(t)
82+
t.Setenv("DATABASE_URL", "postgres://localhost/db")
83+
84+
cfg := Load()
85+
86+
if cfg.DatabaseURL != "postgres://localhost/db" {
87+
t.Errorf("DatabaseURL = %q", cfg.DatabaseURL)
88+
}
89+
if cfg.RedisURL != "redis://localhost:6379" {
90+
t.Errorf("RedisURL default = %q", cfg.RedisURL)
91+
}
92+
if cfg.Environment != "development" {
93+
t.Errorf("Environment default = %q", cfg.Environment)
94+
}
95+
if cfg.GeoLite2DBPath != "./GeoLite2-City.mmdb" {
96+
t.Errorf("GeoLite2DBPath default = %q", cfg.GeoLite2DBPath)
97+
}
98+
if cfg.ObjectStoreBucket != "instant-shared" {
99+
t.Errorf("ObjectStoreBucket default = %q", cfg.ObjectStoreBucket)
100+
}
101+
if cfg.KubeNamespaceApps != "instant-apps" {
102+
t.Errorf("KubeNamespaceApps default = %q", cfg.KubeNamespaceApps)
103+
}
104+
if cfg.ObjectStoreBackend != "minio" {
105+
t.Errorf("ObjectStoreBackend default = %q", cfg.ObjectStoreBackend)
106+
}
107+
if cfg.BackupS3PathPrefix != "backups/" {
108+
t.Errorf("BackupS3PathPrefix default = %q", cfg.BackupS3PathPrefix)
109+
}
110+
if cfg.PlatformBackupS3Prefix != "platform-backups/" {
111+
t.Errorf("PlatformBackupS3Prefix default = %q", cfg.PlatformBackupS3Prefix)
112+
}
113+
// BackupS3Bucket falls back to ObjectStoreBucket.
114+
if cfg.BackupS3Bucket != "instant-shared" {
115+
t.Errorf("BackupS3Bucket fallback = %q", cfg.BackupS3Bucket)
116+
}
117+
if cfg.ObjectStoreSecure {
118+
t.Error("ObjectStoreSecure should default false")
119+
}
120+
// Empty maps, not nil.
121+
if cfg.BrevoTemplateIDs == nil || len(cfg.BrevoTemplateIDs) != 0 {
122+
t.Errorf("BrevoTemplateIDs = %v", cfg.BrevoTemplateIDs)
123+
}
124+
if cfg.SESTemplateNames == nil || len(cfg.SESTemplateNames) != 0 {
125+
t.Errorf("SESTemplateNames = %v", cfg.SESTemplateNames)
126+
}
127+
}
128+
129+
func TestLoad_PanicsWithoutDatabaseURL(t *testing.T) {
130+
clearEnv(t)
131+
defer func() {
132+
if recover() == nil {
133+
t.Fatal("Load did not panic without DATABASE_URL")
134+
}
135+
}()
136+
Load()
137+
}
138+
139+
func TestLoad_AllOverrides(t *testing.T) {
140+
clearEnv(t)
141+
t.Setenv("DATABASE_URL", "postgres://db")
142+
t.Setenv("REDIS_URL", "redis://r:6379")
143+
t.Setenv("PROVISIONER_ADDR", "prov:50051")
144+
t.Setenv("PROVISIONER_SECRET", "psecret")
145+
t.Setenv("EMAIL_PROVIDER", "brevo")
146+
t.Setenv("BREVO_API_KEY", "bkey")
147+
t.Setenv("BREVO_TEMPLATE_IDS", `{"a.kind":12,"b.kind":7}`)
148+
t.Setenv("BREVO_SENDER_EMAIL", "no@x.dev")
149+
t.Setenv("BREVO_SENDER_NAME", "X")
150+
t.Setenv("SES_AWS_REGION", "us-east-1")
151+
t.Setenv("SES_AWS_ACCESS_KEY_ID", "AK")
152+
t.Setenv("SES_AWS_SECRET_ACCESS_KEY", "SK")
153+
t.Setenv("SES_FROM_EMAIL", "from@x.dev")
154+
t.Setenv("SES_TEMPLATE_NAMES", `{"a.kind":"tmpl-v1"}`)
155+
t.Setenv("ENVIRONMENT", "production")
156+
t.Setenv("MAXMIND_LICENSE_KEY", "mm")
157+
t.Setenv("GEOLITE2_DB_PATH", "/data/geo.mmdb")
158+
t.Setenv("PLANS_PATH", "/etc/plans.yaml")
159+
t.Setenv("OBJECT_STORE_ENDPOINT", "nyc3.do.com")
160+
t.Setenv("OBJECT_STORE_ACCESS_KEY", "oak")
161+
t.Setenv("OBJECT_STORE_SECRET_KEY", "osk")
162+
t.Setenv("OBJECT_STORE_BUCKET", "mybucket")
163+
t.Setenv("OBJECT_STORE_REGION", "nyc3")
164+
t.Setenv("OBJECT_STORE_SECURE", "true")
165+
t.Setenv("KUBE_NAMESPACE_APPS", "myapps")
166+
t.Setenv("INSTANT_API_INTERNAL_URL", "http://api")
167+
t.Setenv("WORKER_INTERNAL_JWT_SECRET", "wjwt")
168+
t.Setenv("AES_KEY", "deadbeef")
169+
t.Setenv("OBJECT_STORE_BACKEND", "do-spaces")
170+
t.Setenv("BACKUP_S3_BUCKET", "backups-bkt")
171+
t.Setenv("BACKUP_S3_PATH_PREFIX", "bk/")
172+
t.Setenv("PLATFORM_BACKUP_S3_PREFIX", "plat/")
173+
t.Setenv("CUSTOMER_DATABASE_URL", "postgres://cust")
174+
t.Setenv("MONGO_ADMIN_URI", "mongodb://admin")
175+
t.Setenv("CUSTOMER_REDIS_URL", "redis://cust")
176+
177+
cfg := Load()
178+
179+
if cfg.RedisURL != "redis://r:6379" {
180+
t.Errorf("RedisURL = %q", cfg.RedisURL)
181+
}
182+
if cfg.ProvisionerAddr != "prov:50051" || cfg.ProvisionerSecret != "psecret" {
183+
t.Errorf("provisioner = %q / %q", cfg.ProvisionerAddr, cfg.ProvisionerSecret)
184+
}
185+
if cfg.EmailProvider != "brevo" || cfg.BrevoAPIKey != "bkey" {
186+
t.Errorf("brevo = %q / %q", cfg.EmailProvider, cfg.BrevoAPIKey)
187+
}
188+
if cfg.BrevoTemplateIDs["a.kind"] != 12 || cfg.BrevoTemplateIDs["b.kind"] != 7 {
189+
t.Errorf("BrevoTemplateIDs = %v", cfg.BrevoTemplateIDs)
190+
}
191+
if cfg.SESTemplateNames["a.kind"] != "tmpl-v1" {
192+
t.Errorf("SESTemplateNames = %v", cfg.SESTemplateNames)
193+
}
194+
if cfg.Environment != "production" {
195+
t.Errorf("Environment = %q", cfg.Environment)
196+
}
197+
if cfg.GeoLite2DBPath != "/data/geo.mmdb" || cfg.PlansPath != "/etc/plans.yaml" {
198+
t.Errorf("geo/plans = %q / %q", cfg.GeoLite2DBPath, cfg.PlansPath)
199+
}
200+
if !cfg.ObjectStoreSecure {
201+
t.Error("ObjectStoreSecure should be true")
202+
}
203+
if cfg.ObjectStoreBucket != "mybucket" {
204+
t.Errorf("ObjectStoreBucket = %q", cfg.ObjectStoreBucket)
205+
}
206+
if cfg.ObjectStoreBackend != "do-spaces" {
207+
t.Errorf("ObjectStoreBackend = %q", cfg.ObjectStoreBackend)
208+
}
209+
if cfg.BackupS3Bucket != "backups-bkt" {
210+
t.Errorf("BackupS3Bucket = %q", cfg.BackupS3Bucket)
211+
}
212+
if cfg.BackupS3PathPrefix != "bk/" || cfg.PlatformBackupS3Prefix != "plat/" {
213+
t.Errorf("backup prefixes = %q / %q", cfg.BackupS3PathPrefix, cfg.PlatformBackupS3Prefix)
214+
}
215+
if cfg.AESKey != "deadbeef" {
216+
t.Errorf("AESKey = %q", cfg.AESKey)
217+
}
218+
if cfg.CustomerDatabaseURL != "postgres://cust" ||
219+
cfg.MongoAdminURI != "mongodb://admin" ||
220+
cfg.CustomerRedisURL != "redis://cust" {
221+
t.Errorf("customer infra = %q / %q / %q",
222+
cfg.CustomerDatabaseURL, cfg.MongoAdminURI, cfg.CustomerRedisURL)
223+
}
224+
if cfg.InstantAPIInternalURL != "http://api" || cfg.WorkerInternalJWTSecret != "wjwt" {
225+
t.Errorf("internal api = %q / %q", cfg.InstantAPIInternalURL, cfg.WorkerInternalJWTSecret)
226+
}
227+
}
228+
229+
// TestLoad_LegacyMinioFallback exercises every MINIO_* fallback branch when
230+
// the OBJECT_STORE_* equivalents are unset.
231+
func TestLoad_LegacyMinioFallback(t *testing.T) {
232+
clearEnv(t)
233+
t.Setenv("DATABASE_URL", "postgres://db")
234+
t.Setenv("MINIO_ENDPOINT", "minio:9000")
235+
t.Setenv("MINIO_ROOT_USER", "minioadmin")
236+
t.Setenv("MINIO_ROOT_PASSWORD", "miniopass")
237+
t.Setenv("MINIO_BUCKET_NAME", "legacy-bucket")
238+
239+
cfg := Load()
240+
241+
if cfg.ObjectStoreEndpoint != "minio:9000" {
242+
t.Errorf("endpoint fallback = %q", cfg.ObjectStoreEndpoint)
243+
}
244+
if cfg.ObjectStoreAccessKey != "minioadmin" {
245+
t.Errorf("access key fallback = %q", cfg.ObjectStoreAccessKey)
246+
}
247+
if cfg.ObjectStoreSecretKey != "miniopass" {
248+
t.Errorf("secret key fallback = %q", cfg.ObjectStoreSecretKey)
249+
}
250+
// ObjectStoreBucket defaults to "instant-shared" then MINIO_BUCKET_NAME wins.
251+
if cfg.ObjectStoreBucket != "legacy-bucket" {
252+
t.Errorf("bucket fallback = %q", cfg.ObjectStoreBucket)
253+
}
254+
}
255+
256+
// TestLoad_ObjectStoreWinsOverMinio confirms the OBJECT_STORE_* values are NOT
257+
// overwritten by MINIO_* when both present (the fallback branches are skipped).
258+
func TestLoad_ObjectStoreWinsOverMinio(t *testing.T) {
259+
clearEnv(t)
260+
t.Setenv("DATABASE_URL", "postgres://db")
261+
t.Setenv("OBJECT_STORE_ENDPOINT", "primary:9000")
262+
t.Setenv("OBJECT_STORE_ACCESS_KEY", "pak")
263+
t.Setenv("OBJECT_STORE_SECRET_KEY", "psk")
264+
t.Setenv("OBJECT_STORE_BUCKET", "primary-bucket")
265+
t.Setenv("MINIO_ENDPOINT", "minio:9000")
266+
t.Setenv("MINIO_ROOT_USER", "minioadmin")
267+
t.Setenv("MINIO_ROOT_PASSWORD", "miniopass")
268+
t.Setenv("MINIO_BUCKET_NAME", "legacy-bucket")
269+
270+
cfg := Load()
271+
272+
if cfg.ObjectStoreEndpoint != "primary:9000" {
273+
t.Errorf("endpoint = %q", cfg.ObjectStoreEndpoint)
274+
}
275+
if cfg.ObjectStoreAccessKey != "pak" || cfg.ObjectStoreSecretKey != "psk" {
276+
t.Errorf("keys = %q / %q", cfg.ObjectStoreAccessKey, cfg.ObjectStoreSecretKey)
277+
}
278+
// Bucket != "instant-shared" so the MINIO_BUCKET_NAME branch is skipped.
279+
if cfg.ObjectStoreBucket != "primary-bucket" {
280+
t.Errorf("bucket = %q", cfg.ObjectStoreBucket)
281+
}
282+
}
283+
284+
func TestParseBrevoTemplateIDs(t *testing.T) {
285+
if m := parseBrevoTemplateIDs(""); len(m) != 0 || m == nil {
286+
t.Errorf("empty = %v", m)
287+
}
288+
m := parseBrevoTemplateIDs(`{"x":1,"y":2}`)
289+
if m["x"] != 1 || m["y"] != 2 {
290+
t.Errorf("valid = %v", m)
291+
}
292+
if bad := parseBrevoTemplateIDs(`{not json`); len(bad) != 0 || bad == nil {
293+
t.Errorf("malformed should be empty map: %v", bad)
294+
}
295+
}
296+
297+
func TestParseSESTemplateNames(t *testing.T) {
298+
if m := parseSESTemplateNames(""); len(m) != 0 || m == nil {
299+
t.Errorf("empty = %v", m)
300+
}
301+
m := parseSESTemplateNames(`{"x":"tmpl"}`)
302+
if m["x"] != "tmpl" {
303+
t.Errorf("valid = %v", m)
304+
}
305+
if bad := parseSESTemplateNames(`[]invalid`); len(bad) != 0 || bad == nil {
306+
t.Errorf("malformed should be empty map: %v", bad)
307+
}
308+
}

0 commit comments

Comments
 (0)