added cli-covarage tool and fixed more tests

This commit is contained in:
Boki 2025-06-26 14:23:01 -04:00
parent b63e58784c
commit b845a8eade
57 changed files with 11917 additions and 295 deletions

View file

@ -213,28 +213,47 @@ export class ConfigManager<T = Record<string, unknown>> {
}
private deepMerge(...objects: Record<string, unknown>[]): Record<string, unknown> {
const result: Record<string, unknown> = {};
const seen = new WeakSet();
const merge = (...objs: Record<string, unknown>[]): Record<string, unknown> => {
const result: Record<string, unknown> = {};
for (const obj of objects) {
for (const [key, value] of Object.entries(obj)) {
if (value === null || value === undefined) {
result[key] = value;
} else if (
typeof value === 'object' &&
!Array.isArray(value) &&
!(value instanceof Date) &&
!(value instanceof RegExp)
) {
result[key] = this.deepMerge(
(result[key] as Record<string, unknown>) || ({} as Record<string, unknown>),
value as Record<string, unknown>
);
} else {
result[key] = value;
for (const obj of objs) {
if (seen.has(obj)) {
// Skip circular reference instead of throwing
return result;
}
seen.add(obj);
for (const [key, value] of Object.entries(obj)) {
if (value === null || value === undefined) {
result[key] = value;
} else if (
typeof value === 'object' &&
!Array.isArray(value) &&
!(value instanceof Date) &&
!(value instanceof RegExp)
) {
if (seen.has(value)) {
// Skip circular reference - don't merge this value
continue;
}
result[key] = merge(
(result[key] as Record<string, unknown>) || ({} as Record<string, unknown>),
value as Record<string, unknown>
);
} else {
result[key] = value;
}
}
seen.delete(obj);
}
}
return result;
return result;
};
return merge(...objects);
}
}

View file

@ -59,18 +59,21 @@ export class EnvLoader implements ConfigLoader {
}
private setConfigValue(config: Record<string, unknown>, key: string, value: string): void {
const parsedValue = this.parseValue(value);
try {
// Handle provider-specific environment variables (only for application usage, not tests)
if (!this.prefix && !this.options.convertCase) {
const providerMapping = this.getProviderMapping(key);
if (providerMapping) {
// For certain fields, we need to preserve the string value
const shouldPreserveString = this.shouldPreserveStringForKey(key);
const parsedValue = shouldPreserveString ? value : this.parseValue(value);
this.setNestedValue(config, providerMapping.path, parsedValue);
return;
}
}
const parsedValue = this.parseValue(value);
if (this.options.convertCase) {
// Convert to camelCase
const camelKey = this.toCamelCase(key);
@ -128,6 +131,15 @@ export class EnvLoader implements ConfigLoader {
return str.toLowerCase().replace(/_([a-z])/g, (_, char) => char.toUpperCase());
}
private shouldPreserveStringForKey(key: string): boolean {
// Keys that should preserve string values even if they look like numbers
const preserveStringKeys = [
'QM_WEBMASTER_ID',
'IB_MARKET_DATA_TYPE'
];
return preserveStringKeys.includes(key);
}
private getProviderMapping(envKey: string): { path: string[] } | null {
// Provider-specific and special environment variable mappings
const providerMappings: Record<string, string[]> = {
@ -213,10 +225,12 @@ export class EnvLoader implements ConfigLoader {
return false;
}
// Handle numbers
const num = Number(value);
if (!isNaN(num) && value !== '') {
return num;
// Handle numbers (but preserve strings with leading zeros or plus signs)
if (!/^[+-]/.test(value) && !/^0\d/.test(value)) {
const num = Number(value);
if (!isNaN(num) && value !== '') {
return num;
}
}
// Handle null/undefined

View file

@ -28,9 +28,19 @@ export const ibProviderConfigSchema = baseProviderConfigSchema.extend({
host: z.string().default('localhost'),
port: z.number().default(5000),
clientId: z.number().default(1),
}).default({
host: 'localhost',
port: 5000,
clientId: 1,
}),
account: z.string().optional(),
marketDataType: z.enum(['live', 'delayed', 'frozen']).default('delayed'),
marketDataType: z.union([
z.enum(['live', 'delayed', 'frozen']),
z.enum(['1', '2', '3']).transform((val) => {
const mapping = { '1': 'live', '2': 'frozen', '3': 'delayed' } as const;
return mapping[val];
}),
]).default('delayed'),
});
// QuoteMedia provider