Warning, /firebird/firebird-ng/src/app/services/config.service.spec.ts is written in an unsupported language. File is not indexed.
0001 import { TestBed } from '@angular/core/testing';
0002 import { ConfigService, ConfigSnapshot } from './config.service';
0003 import { ConfigProperty } from '../utils/config-property';
0004
0005 // Mock storage for testing
0006 class MockStorage {
0007 private storage: Map<string, string> = new Map();
0008
0009 getItem(key: string): string | null {
0010 return this.storage.get(key) || null;
0011 }
0012
0013 setItem(key: string, value: string): void {
0014 this.storage.set(key, value);
0015 }
0016
0017 clear(): void {
0018 this.storage.clear();
0019 }
0020 }
0021
0022 describe('ConfigService', () => {
0023 let service: ConfigService;
0024 let mockStorage: MockStorage;
0025
0026 beforeEach(() => {
0027 TestBed.configureTestingModule({});
0028 service = TestBed.inject(ConfigService);
0029 mockStorage = new MockStorage();
0030 });
0031
0032 it('should be created', () => {
0033 expect(service).toBeTruthy();
0034 });
0035
0036 describe('Config Management', () => {
0037 it('should add and retrieve config properties', () => {
0038 const config = new ConfigProperty('testKey', 'defaultValue', undefined, undefined, mockStorage);
0039
0040 service.addConfig(config);
0041
0042 const retrieved = service.getConfig<string>('testKey');
0043 expect(retrieved).toBe(config);
0044 expect(retrieved?.value).toBe('defaultValue');
0045 });
0046
0047 it('should return undefined for non-existent config', () => {
0048 const retrieved = service.getConfig<string>('nonExistent');
0049 expect(retrieved).toBeUndefined();
0050 });
0051
0052 it('should throw error when getting non-existent config with getConfigOrThrow', () => {
0053 expect(() => service.getConfigOrThrow<string>('nonExistent'))
0054 .toThrowError("Property 'nonExistent' not found");
0055 });
0056
0057 it('should return config with getConfigOrThrow when it exists', () => {
0058 const config = new ConfigProperty('existingKey', 'value', undefined, undefined, mockStorage);
0059 service.addConfig(config);
0060
0061 const retrieved = service.getConfigOrThrow<string>('existingKey');
0062 expect(retrieved).toBe(config);
0063 });
0064 });
0065
0066 describe('loadDefaults', () => {
0067 it('should reset all configs to their default values', () => {
0068 const config1 = new ConfigProperty('key1', 'default1', undefined, undefined, mockStorage);
0069 const config2 = new ConfigProperty('key2', 100, undefined, undefined, mockStorage);
0070
0071 service.addConfig(config1);
0072 service.addConfig(config2);
0073
0074 // Change values
0075 config1.setValue('changed1');
0076 config2.setValue(200);
0077
0078 expect(config1.value).toBe('changed1');
0079 expect(config2.value).toBe(200);
0080
0081 // Load defaults
0082 service.loadDefaults();
0083
0084 expect(config1.value).toBe('default1');
0085 expect(config2.value).toBe(100);
0086 });
0087
0088 it('should handle empty config map', () => {
0089 expect(() => service.loadDefaults()).not.toThrow();
0090 });
0091 });
0092
0093 describe('loadDefaultsFor', () => {
0094 it('should reset only configs with matching prefix', () => {
0095 const uiConfig1 = new ConfigProperty('ui.theme', 'light', undefined, undefined, mockStorage);
0096 const uiConfig2 = new ConfigProperty('ui.fontSize', 14, undefined, undefined, mockStorage);
0097 const apiConfig = new ConfigProperty('api.endpoint', 'http://localhost', undefined, undefined, mockStorage);
0098
0099 service.addConfig(uiConfig1);
0100 service.addConfig(uiConfig2);
0101 service.addConfig(apiConfig);
0102
0103 // Change values
0104 uiConfig1.setValue('dark');
0105 uiConfig2.setValue(16);
0106 apiConfig.setValue('http://production');
0107
0108 // Load defaults only for ui configs
0109 service.loadDefaultsFor('ui');
0110
0111 expect(uiConfig1.value).toBe('light');
0112 expect(uiConfig2.value).toBe(14);
0113 expect(apiConfig.value).toBe('http://production'); // Should not be reset
0114 });
0115
0116 it('should handle prefix with no matching configs', () => {
0117 const config = new ConfigProperty('api.endpoint', 'http://localhost', undefined, undefined, mockStorage);
0118 service.addConfig(config);
0119
0120 config.setValue('http://production');
0121
0122 service.loadDefaultsFor('ui');
0123
0124 expect(config.value).toBe('http://production'); // Should remain unchanged
0125 });
0126
0127 it('should handle empty prefix', () => {
0128 const config = new ConfigProperty('key', 'default', undefined, undefined, mockStorage);
0129 service.addConfig(config);
0130
0131 config.setValue('changed');
0132
0133 // Empty prefix should match all
0134 service.loadDefaultsFor('');
0135
0136 expect(config.value).toBe('default');
0137 });
0138 });
0139
0140 describe('saveToJson', () => {
0141 it('should export all configs to JSON snapshot', () => {
0142 const config1 = new ConfigProperty('key1', 'value1', undefined, undefined, mockStorage);
0143 const config2 = new ConfigProperty('key2', 42, undefined, undefined, mockStorage);
0144 const config3 = new ConfigProperty('key3', { nested: 'object' }, undefined, undefined, mockStorage);
0145
0146 service.addConfig(config1);
0147 service.addConfig(config2);
0148 service.addConfig(config3);
0149
0150 const snapshot = service.saveToJson();
0151
0152 expect(snapshot.configs).toBeDefined();
0153 expect(snapshot.configs['key1'].value).toBe('value1');
0154 expect(snapshot.configs['key2'].value).toBe(42);
0155 expect(snapshot.configs['key3'].value).toEqual({ nested: 'object' });
0156 expect(snapshot.version).toBe('1.0');
0157 expect(snapshot.exportedAt).toBeDefined();
0158 });
0159
0160 it('should include metadata in snapshot', () => {
0161 const snapshot = service.saveToJson();
0162
0163 expect(snapshot.version).toBe('1.0');
0164 expect(snapshot.exportedAt).toBeDefined();
0165
0166 // Verify exportedAt is a valid ISO date string
0167 const date = new Date(snapshot.exportedAt!);
0168 expect(date.toISOString()).toBe(snapshot.exportedAt!);
0169 });
0170
0171 it('should handle empty config map', () => {
0172 const snapshot = service.saveToJson();
0173
0174 expect(snapshot.configs).toEqual({});
0175 expect(snapshot.version).toBe('1.0');
0176 expect(snapshot.exportedAt).toBeDefined();
0177 });
0178 });
0179
0180 describe('loadFromJson', () => {
0181 it('should import configs from JSON snapshot', () => {
0182 const config1 = new ConfigProperty('key1', 'default1', undefined, undefined, mockStorage);
0183 const config2 = new ConfigProperty('key2', 0, undefined, undefined, mockStorage);
0184
0185 service.addConfig(config1);
0186 service.addConfig(config2);
0187
0188 const snapshot: ConfigSnapshot = {
0189 configs: {
0190 key1: { value: 'imported1' },
0191 key2: { value: 99 }
0192 },
0193 version: '1.0',
0194 exportedAt: new Date().toISOString()
0195 };
0196
0197 service.loadFromJson(snapshot);
0198
0199 expect(config1.value).toBe('imported1');
0200 expect(config2.value).toBe(99);
0201 });
0202
0203 it('should warn about configs not found in service', () => {
0204 const consoleWarnSpy = vi.spyOn(console, 'warn');
0205 const config = new ConfigProperty('existing', 'value', undefined, undefined, mockStorage);
0206 service.addConfig(config);
0207
0208 const snapshot: ConfigSnapshot = {
0209 configs: {
0210 existing: { value: 'updated' },
0211 nonExisting: { value: 'ignored' }
0212 }
0213 };
0214
0215 service.loadFromJson(snapshot);
0216
0217 expect(config.value).toBe('updated');
0218 expect(consoleWarnSpy).toHaveBeenCalledWith("Config key 'nonExisting' not found in registered configs, skipping...");
0219 });
0220
0221 it('should respect timestamp-based conflict resolution when overwriteNewer is false', () => {
0222 const config = new ConfigProperty('key', 'default', undefined, undefined, mockStorage);
0223 service.addConfig(config);
0224
0225 // Set a value with a recent timestamp
0226 config.setValue('current', Date.now());
0227
0228 // Try to load an older value
0229 const snapshot: ConfigSnapshot = {
0230 configs: {
0231 key: {
0232 value: 'older',
0233 timestamp: Date.now() - 10000 // 10 seconds ago
0234 }
0235 }
0236 };
0237
0238 service.loadFromJson(snapshot, false);
0239
0240 // Value should remain 'current' because its timestamp is newer
0241 expect(config.value).toBe('current');
0242 });
0243
0244 it('should force overwrite when overwriteNewer is true', () => {
0245 const config = new ConfigProperty('key', 'default', undefined, undefined, mockStorage);
0246 service.addConfig(config);
0247
0248 // Set a value with an explicit far-future timestamp to ensure it's "newer"
0249 const futureTime = Date.now() + 100000; // 100 seconds in the future
0250 config.setValue('current', futureTime);
0251
0252 // Verify the value was set
0253 expect(config.value).toBe('current');
0254
0255 // Load value regardless of timestamp - the snapshot has an old timestamp
0256 const snapshot: ConfigSnapshot = {
0257 configs: {
0258 key: {
0259 value: 'forced',
0260 timestamp: Date.now() - 10000 // 10 seconds ago
0261 }
0262 }
0263 };
0264
0265 // With overwriteNewer=true, should overwrite even though stored time is newer
0266 service.loadFromJson(snapshot, true);
0267
0268 // Value should be 'forced' despite the stored value having a newer timestamp
0269 expect(config.value).toBe('forced');
0270 });
0271
0272 it('should throw error for invalid snapshot', () => {
0273 expect(() => service.loadFromJson(null as any))
0274 .toThrowError('Invalid config snapshot: missing configs object');
0275
0276 expect(() => service.loadFromJson({} as any))
0277 .toThrowError('Invalid config snapshot: missing configs object');
0278 });
0279
0280 it('should handle snapshot without timestamps', () => {
0281 const config = new ConfigProperty('key', 'default', undefined, undefined, mockStorage);
0282 service.addConfig(config);
0283
0284 const snapshot: ConfigSnapshot = {
0285 configs: {
0286 key: { value: 'noTimestamp' }
0287 }
0288 };
0289
0290 service.loadFromJson(snapshot);
0291
0292 expect(config.value).toBe('noTimestamp');
0293 });
0294 });
0295
0296 describe('Integration tests', () => {
0297 it('should roundtrip configs through JSON', () => {
0298 // Setup initial configs
0299 const stringConfig = new ConfigProperty('str', 'hello', undefined, undefined, mockStorage);
0300 const numberConfig = new ConfigProperty('num', 42, undefined, undefined, mockStorage);
0301 const boolConfig = new ConfigProperty('bool', true, undefined, undefined, mockStorage);
0302 const objectConfig = new ConfigProperty('obj', { a: 1, b: 'test' }, undefined, undefined, mockStorage);
0303
0304 service.addConfig(stringConfig);
0305 service.addConfig(numberConfig);
0306 service.addConfig(boolConfig);
0307 service.addConfig(objectConfig);
0308
0309 // Export to JSON
0310 const exported = service.saveToJson();
0311
0312 // Change all values
0313 stringConfig.setValue('changed');
0314 numberConfig.setValue(100);
0315 boolConfig.setValue(false);
0316 objectConfig.setValue({ c: 3 } as any);
0317
0318 // Import back from JSON (use overwriteNewer=true to force restore)
0319 service.loadFromJson(exported, true);
0320
0321 // Verify original values restored
0322 expect(stringConfig.value).toBe('hello');
0323 expect(numberConfig.value).toBe(42);
0324 expect(boolConfig.value).toBe(true);
0325 expect(objectConfig.value).toEqual({ a: 1, b: 'test' });
0326 });
0327
0328 it('should handle complex workflow with prefix-based defaults', () => {
0329 // Setup configs with different prefixes
0330 const uiTheme = new ConfigProperty('ui.theme', 'light', undefined, undefined, mockStorage);
0331 const uiFont = new ConfigProperty('ui.font', 'Arial', undefined, undefined, mockStorage);
0332 const apiUrl = new ConfigProperty('api.url', 'http://localhost', undefined, undefined, mockStorage);
0333 const apiTimeout = new ConfigProperty('api.timeout', 5000, undefined, undefined, mockStorage);
0334
0335 console.log("Initial timestamp");
0336 console.log(uiTheme.key);
0337 console.log(uiTheme.value);
0338 console.log(uiTheme.getTimestamp());
0339
0340 service.addConfig(uiTheme);
0341 service.addConfig(uiFont);
0342 service.addConfig(apiUrl);
0343 service.addConfig(apiTimeout);
0344
0345 // Modify values
0346 uiTheme.setValue('dark');
0347 uiFont.setValue('Roboto');
0348 apiUrl.setValue('http://production');
0349 apiTimeout.setValue(10000);
0350
0351 console.log("timestamp after update (before save)");
0352 console.log(uiTheme.key);
0353 console.log(uiTheme.value);
0354 console.log(uiTheme.getTimestamp());
0355
0356 // Export current state
0357 const snapshot = service.saveToJson();
0358
0359 // Reset UI configs only
0360 service.loadDefaultsFor('ui');
0361 expect(uiTheme.value).toBe('light');
0362 expect(uiFont.value).toBe('Arial');
0363 expect(apiUrl.value).toBe('http://production');
0364 expect(apiTimeout.value).toBe(10000);
0365
0366 console.log("timestamp after .loadDefaultsFor('ui');");
0367 console.log(uiTheme.key);
0368 console.log(uiTheme.value);
0369 console.log(uiTheme.getTimestamp());
0370
0371 // Restore from snapshot (use overwriteNewer=true to force restore)
0372 service.loadFromJson(snapshot, true);
0373 expect(uiTheme.value).toBe('dark');
0374 expect(uiFont.value).toBe('Roboto');
0375 expect(apiUrl.value).toBe('http://production');
0376 expect(apiTimeout.value).toBe(10000);
0377
0378
0379 console.log("timestamp after loadFromJson");
0380 console.log(uiTheme.value);
0381 console.log(uiTheme.getTimestamp());
0382
0383 // Reset all to defaults
0384 service.loadDefaults();
0385 expect(uiTheme.value).toBe('light');
0386 expect(uiFont.value).toBe('Arial');
0387 expect(apiUrl.value).toBe('http://localhost');
0388 expect(apiTimeout.value).toBe(5000);
0389 });
0390 });
0391 });