Skip to content

Commit 1c3ba4e

Browse files
authored
test: add config and rate limit coverage
Adds focused coverage for env interpolation, merge utilities, and rate-limit handling. Validated with targeted Jest, full Jest, lint, typecheck, GitHub Actions, Vercel, and CodeRabbit.
1 parent 11c130d commit 1c3ba4e

3 files changed

Lines changed: 730 additions & 0 deletions

File tree

Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
/**
2+
* Testes unitários para env-interpolator
3+
*
4+
* Cobre interpolateString, interpolateEnvVars e lintEnvPatterns.
5+
*
6+
* @see .aiox-core/core/config/env-interpolator.js
7+
* @issue #52
8+
*/
9+
10+
'use strict';
11+
12+
const path = require('path');
13+
14+
const PROJECT_ROOT = path.resolve(__dirname, '..', '..', '..');
15+
16+
const {
17+
interpolateString,
18+
interpolateEnvVars,
19+
lintEnvPatterns,
20+
ENV_VAR_PATTERN,
21+
} = require(path.join(PROJECT_ROOT, '.aiox-core', 'core', 'config', 'env-interpolator'));
22+
23+
// ============================================================================
24+
// interpolateString
25+
// ============================================================================
26+
27+
describe('interpolateString', () => {
28+
const ORIGINAL_ENV = process.env;
29+
30+
beforeEach(() => {
31+
process.env = { ...ORIGINAL_ENV };
32+
});
33+
34+
afterAll(() => {
35+
process.env = ORIGINAL_ENV;
36+
});
37+
38+
it('deve resolver ${VAR} existente', () => {
39+
process.env.MY_VAR = 'hello';
40+
expect(interpolateString('${MY_VAR}')).toBe('hello');
41+
});
42+
43+
it('deve resolver ${VAR:-default} quando VAR existe', () => {
44+
process.env.MY_VAR = 'real';
45+
expect(interpolateString('${MY_VAR:-fallback}')).toBe('real');
46+
});
47+
48+
it('deve usar default quando VAR não existe', () => {
49+
delete process.env.MISSING_VAR;
50+
expect(interpolateString('${MISSING_VAR:-fallback}')).toBe('fallback');
51+
});
52+
53+
it('deve retornar string vazia e gerar warning quando VAR não existe sem default', () => {
54+
delete process.env.MISSING_VAR;
55+
const warnings = [];
56+
const result = interpolateString('${MISSING_VAR}', { warnings });
57+
58+
expect(result).toBe('');
59+
expect(warnings).toHaveLength(1);
60+
expect(warnings[0]).toContain('MISSING_VAR');
61+
});
62+
63+
it('deve resolver múltiplas variáveis na mesma string', () => {
64+
process.env.HOST = 'localhost';
65+
process.env.PORT = '3000';
66+
expect(interpolateString('${HOST}:${PORT}')).toBe('localhost:3000');
67+
});
68+
69+
it('deve preservar texto sem padrão ${...}', () => {
70+
expect(interpolateString('no variables here')).toBe('no variables here');
71+
});
72+
73+
it('deve resolver default vazio ${VAR:-}', () => {
74+
delete process.env.EMPTY_DEFAULT;
75+
expect(interpolateString('${EMPTY_DEFAULT:-}')).toBe('');
76+
});
77+
});
78+
79+
// ============================================================================
80+
// interpolateEnvVars
81+
// ============================================================================
82+
83+
describe('interpolateEnvVars', () => {
84+
const ORIGINAL_ENV = process.env;
85+
86+
beforeEach(() => {
87+
process.env = { ...ORIGINAL_ENV };
88+
});
89+
90+
afterAll(() => {
91+
process.env = ORIGINAL_ENV;
92+
});
93+
94+
it('deve interpolar strings em objetos aninhados', () => {
95+
process.env.DB_HOST = 'pg.example.com';
96+
const config = {
97+
database: {
98+
host: '${DB_HOST}',
99+
port: 5432,
100+
},
101+
};
102+
103+
const result = interpolateEnvVars(config);
104+
expect(result.database.host).toBe('pg.example.com');
105+
expect(result.database.port).toBe(5432);
106+
});
107+
108+
it('deve interpolar strings em arrays', () => {
109+
process.env.ITEM = 'resolved';
110+
const config = ['${ITEM}', 'static'];
111+
112+
const result = interpolateEnvVars(config);
113+
expect(result).toEqual(['resolved', 'static']);
114+
});
115+
116+
it('deve preservar números, booleanos e null', () => {
117+
expect(interpolateEnvVars(42)).toBe(42);
118+
expect(interpolateEnvVars(true)).toBe(true);
119+
expect(interpolateEnvVars(null)).toBeNull();
120+
});
121+
122+
it('deve processar objetos profundamente aninhados', () => {
123+
process.env.SECRET = 'top-secret';
124+
const config = {
125+
l1: { l2: { l3: { key: '${SECRET}' } } },
126+
};
127+
128+
const result = interpolateEnvVars(config);
129+
expect(result.l1.l2.l3.key).toBe('top-secret');
130+
});
131+
132+
it('deve coletar warnings de variáveis ausentes', () => {
133+
delete process.env.UNKNOWN;
134+
const warnings = [];
135+
interpolateEnvVars({ key: '${UNKNOWN}' }, { warnings });
136+
137+
expect(warnings).toHaveLength(1);
138+
expect(warnings[0]).toContain('UNKNOWN');
139+
});
140+
141+
it('não deve mutar o config original', () => {
142+
process.env.VAL = 'new';
143+
const config = { a: '${VAL}' };
144+
const configCopy = JSON.parse(JSON.stringify(config));
145+
146+
interpolateEnvVars(config);
147+
expect(config).toEqual(configCopy);
148+
});
149+
});
150+
151+
// ============================================================================
152+
// lintEnvPatterns
153+
// ============================================================================
154+
155+
describe('lintEnvPatterns', () => {
156+
it('deve detectar padrões ${...} em strings', () => {
157+
const config = { api: { key: '${API_KEY}' } };
158+
const findings = lintEnvPatterns(config, 'config.yaml');
159+
160+
expect(findings).toHaveLength(1);
161+
expect(findings[0]).toContain('config.yaml');
162+
expect(findings[0]).toContain('api.key');
163+
expect(findings[0]).toContain('${API_KEY}');
164+
});
165+
166+
it('deve detectar padrões em arrays', () => {
167+
const config = { hosts: ['static', '${DYNAMIC_HOST}'] };
168+
const findings = lintEnvPatterns(config, 'test.yaml');
169+
170+
expect(findings).toHaveLength(1);
171+
expect(findings[0]).toContain('hosts[1]');
172+
});
173+
174+
it('deve retornar array vazio quando não há padrões', () => {
175+
const config = { name: 'static', port: 3000 };
176+
const findings = lintEnvPatterns(config, 'clean.yaml');
177+
178+
expect(findings).toEqual([]);
179+
});
180+
181+
it('deve detectar múltiplos padrões', () => {
182+
const config = {
183+
db: { host: '${DB_HOST}', pass: '${DB_PASS}' },
184+
api: '${API_URL}',
185+
};
186+
const findings = lintEnvPatterns(config, 'app.yaml');
187+
188+
expect(findings).toHaveLength(3);
189+
});
190+
191+
it('deve funcionar com objetos profundamente aninhados', () => {
192+
const config = { a: { b: { c: { d: '${DEEP}' } } } };
193+
const findings = lintEnvPatterns(config, 'deep.yaml');
194+
195+
expect(findings).toHaveLength(1);
196+
expect(findings[0]).toContain('a.b.c.d');
197+
});
198+
});
199+
200+
// ============================================================================
201+
// ENV_VAR_PATTERN
202+
// ============================================================================
203+
204+
describe('ENV_VAR_PATTERN', () => {
205+
it('deve ser uma regex global', () => {
206+
expect(ENV_VAR_PATTERN).toBeInstanceOf(RegExp);
207+
expect(ENV_VAR_PATTERN.global).toBe(true);
208+
});
209+
210+
it('deve capturar nome da variável', () => {
211+
const match = '${MY_VAR}'.match(new RegExp(ENV_VAR_PATTERN.source));
212+
expect(match[1]).toBe('MY_VAR');
213+
});
214+
215+
it('deve capturar valor default', () => {
216+
const match = '${MY_VAR:-default}'.match(new RegExp(ENV_VAR_PATTERN.source));
217+
expect(match[1]).toBe('MY_VAR');
218+
expect(match[2]).toBe('default');
219+
});
220+
});

0 commit comments

Comments
 (0)