Skip to content

Commit 230fbe1

Browse files
authored
Merge pull request #33 from Azure/enewman/fix-empty-override-file
Enewman/fix empty override file
2 parents 405d23a + 093a589 commit 230fbe1

4 files changed

Lines changed: 89 additions & 8 deletions

File tree

src/lib/config-loader.ts

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ function assertStringArray(value: unknown, fieldName: string): string[] {
3232
export async function loadFilterConfig(filePath: string): Promise<FilterConfig | undefined> {
3333
try {
3434
const content = await fs.readFile(filePath, 'utf-8');
35-
const parsed = yaml.load(content) as Record<string, unknown>;
35+
const parsed = (yaml.load(content) ?? {}) as Record<string, unknown>;
3636

3737
// Validate structure — each field must be an array of strings
3838
const config: FilterConfig = {};
@@ -104,13 +104,10 @@ export async function loadFilterConfig(filePath: string): Promise<FilterConfig |
104104
export async function loadOverrideConfig(filePath: string): Promise<OverrideConfig | undefined> {
105105
try {
106106
const content = await fs.readFile(filePath, 'utf-8');
107-
const parsed = yaml.load(content) as Record<string, unknown>;
108-
109-
// Return as-is; validation will happen during publish when applied
110-
const config = parsed as OverrideConfig;
107+
const parsed = (yaml.load(content) ?? {}) as OverrideConfig;
111108

112109
logger.debug(`Loaded override config from ${filePath}`);
113-
return config;
110+
return parsed;
114111
} catch (error) {
115112
if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
116113
logger.debug(`Override config file not found: ${filePath}`);
@@ -127,7 +124,7 @@ export async function loadOverrideConfig(filePath: string): Promise<OverrideConf
127124
export async function loadOTelConfig(filePath: string): Promise<Record<string, unknown> | undefined> {
128125
try {
129126
const content = await fs.readFile(filePath, 'utf-8');
130-
const parsed = yaml.load(content) as Record<string, unknown>;
127+
const parsed = (yaml.load(content) ?? {}) as Record<string, unknown>;
131128

132129
logger.debug(`Loaded OTel config from ${filePath}`);
133130
return parsed;

src/lib/logger.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,13 +56,22 @@ function isSensitiveKey(key: string): boolean {
5656

5757
/**
5858
* Recursively sanitize a value, replacing sensitive fields with '***'.
59-
* Handles objects, arrays, and inline bearer tokens in strings.
59+
* Handles objects, arrays, inline bearer tokens in strings, and Error objects.
6060
*/
6161
function sanitize(value: unknown): unknown {
6262
if (typeof value === 'string') {
6363
// Redact inline bearer tokens (e.g. "Bearer eyJ...")
6464
return value.replace(/Bearer\s+[A-Za-z0-9\-._~+/]+=*/gi, 'Bearer ***');
6565
}
66+
if (value instanceof Error) {
67+
// Convert Error objects to a serializable form
68+
return {
69+
name: sanitize(value.name),
70+
message: sanitize(value.message),
71+
stack: value.stack === undefined ? undefined : sanitize(value.stack),
72+
...(value.cause ? { cause: sanitize(value.cause) } : {}),
73+
};
74+
}
6675
if (Array.isArray(value)) {
6776
return value.map((item) => sanitize(item));
6877
}

tests/unit/lib/config-loader.test.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,24 @@ tagNames:
4949
expect(config).toBeDefined();
5050
});
5151

52+
it('should handle completely empty file', async () => {
53+
const filePath = path.join(tmpDir, 'blank.yaml');
54+
await fs.writeFile(filePath, '', 'utf-8');
55+
56+
const config = await loadFilterConfig(filePath);
57+
expect(config).toBeDefined();
58+
expect(config).toEqual({});
59+
});
60+
61+
it('should handle file with only comments', async () => {
62+
const filePath = path.join(tmpDir, 'comments.yaml');
63+
await fs.writeFile(filePath, '# This is a comment\n# Another comment', 'utf-8');
64+
65+
const config = await loadFilterConfig(filePath);
66+
expect(config).toBeDefined();
67+
expect(config).toEqual({});
68+
});
69+
5270
it('should throw for invalid type (non-array field)', async () => {
5371
const content = `
5472
apiNames: "not-an-array"
@@ -123,6 +141,24 @@ backends:
123141
const config = await loadOverrideConfig(path.join(tmpDir, 'nonexistent.yaml'));
124142
expect(config).toBeUndefined();
125143
});
144+
145+
it('should handle completely empty file', async () => {
146+
const filePath = path.join(tmpDir, 'blank.yaml');
147+
await fs.writeFile(filePath, '', 'utf-8');
148+
149+
const config = await loadOverrideConfig(filePath);
150+
expect(config).toBeDefined();
151+
expect(config).toEqual({});
152+
});
153+
154+
it('should handle file with only comments', async () => {
155+
const filePath = path.join(tmpDir, 'comments.yaml');
156+
await fs.writeFile(filePath, '# Override config\n# TODO: add overrides', 'utf-8');
157+
158+
const config = await loadOverrideConfig(filePath);
159+
expect(config).toBeDefined();
160+
expect(config).toEqual({});
161+
});
126162
});
127163

128164
describe('loadOTelConfig', () => {
@@ -148,5 +184,23 @@ service:
148184
const config = await loadOTelConfig(path.join(tmpDir, 'nonexistent.yaml'));
149185
expect(config).toBeUndefined();
150186
});
187+
188+
it('should handle completely empty file', async () => {
189+
const filePath = path.join(tmpDir, 'blank.yaml');
190+
await fs.writeFile(filePath, '', 'utf-8');
191+
192+
const config = await loadOTelConfig(filePath);
193+
expect(config).toBeDefined();
194+
expect(config).toEqual({});
195+
});
196+
197+
it('should handle file with only whitespace', async () => {
198+
const filePath = path.join(tmpDir, 'whitespace.yaml');
199+
await fs.writeFile(filePath, ' \n \n ', 'utf-8');
200+
201+
const config = await loadOTelConfig(filePath);
202+
expect(config).toBeDefined();
203+
expect(config).toEqual({});
204+
});
151205
});
152206
});

tests/unit/lib/logger.test.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,27 @@ describe('Logger', () => {
146146
expect(output).toContain('test');
147147
expect(output).toContain('42');
148148
});
149+
150+
it('should serialize Error objects with message and stack', () => {
151+
const error = new Error('Something went wrong');
152+
loggerInstance.error('Error occurred:', error);
153+
const output = stderrSpy.mock.calls[0]![0] as string;
154+
expect(output).toContain('Something went wrong');
155+
expect(output).toContain('"name":"Error"');
156+
expect(output).toContain('"message":"Something went wrong"');
157+
// Stack trace should be present (not testing exact format)
158+
expect(output).toContain('"stack"');
159+
});
160+
161+
it('should serialize Error objects with cause', () => {
162+
const innerError = new Error('Inner error');
163+
const outerError = new Error('Outer error', { cause: innerError });
164+
loggerInstance.error('Nested error:', outerError);
165+
const output = stderrSpy.mock.calls[0]![0] as string;
166+
expect(output).toContain('Outer error');
167+
expect(output).toContain('Inner error');
168+
expect(output).toContain('"cause"');
169+
});
149170
});
150171

151172
describe('non-ASCII punctuation normalization', () => {

0 commit comments

Comments
 (0)