@sailboat-computer/data-storage
Version:
Shared data storage library for sailboat computer v3
369 lines (313 loc) • 11.2 kB
text/typescript
/**
* Tests for the StorageConfigManager
*/
import { StorageConfigManager, DEFAULT_STORAGE_CONFIG, DEFAULT_UNIFIED_STORAGE_MANAGER_OPTIONS } from '../StorageConfigManager';
import { EventBus } from '../../event-bus';
// Mock the config-client
jest.mock('@sailboat-computer/config-client', () => {
const mockConfigClient = {
getConfig: jest.fn(),
watchConfig: jest.fn().mockReturnValue(jest.fn()),
saveConfig: jest.fn(),
};
return {
createConfigClient: jest.fn().mockReturnValue(mockConfigClient),
};
});
// Mock the config-loader
jest.mock('@sailboat-computer/config-loader', () => {
return {
loadConfig: jest.fn(),
saveConfig: jest.fn(),
};
});
// Mock fs/promises
jest.mock('fs/promises', () => {
return {
mkdir: jest.fn().mockResolvedValue(undefined),
};
});
// Import mocks after mocking
const createConfigClient = jest.requireMock('@sailboat-computer/config-client').createConfigClient;
const loadConfig = jest.requireMock('@sailboat-computer/config-loader').loadConfig;
const saveConfig = jest.requireMock('@sailboat-computer/config-loader').saveConfig;
const mkdir = jest.requireMock('fs/promises').mkdir;
describe('StorageConfigManager', () => {
let configManager: StorageConfigManager;
let mockConfigClient: any;
beforeEach(() => {
jest.clearAllMocks();
// Create config manager
configManager = new StorageConfigManager();
// Get mock config client
mockConfigClient = createConfigClient.mock.results[0].value;
});
it('should use default options when none are provided', () => {
// Verify config client was created with default options
expect(createConfigClient).toHaveBeenCalledWith(expect.objectContaining({
serviceUrl: 'http://localhost:3000',
serviceName: 'data-manager',
fallbackDir: './config',
environment: 'production',
}));
});
it('should use provided options', () => {
// Create config manager with custom options
configManager = new StorageConfigManager({
serviceUrl: 'http://test:3000',
localDir: './test-config',
environment: 'test',
serviceName: 'test-service',
configId: 'test-config',
});
// Verify config client was created with custom options
expect(createConfigClient).toHaveBeenCalledWith(expect.objectContaining({
serviceUrl: 'http://test:3000',
serviceName: 'test-service',
fallbackDir: './test-config',
environment: 'test',
}));
});
describe('initialize', () => {
it('should load configuration from service and cache locally', async () => {
// Set up mock config client
const mockConfig = {
providers: {
hot: {
type: 'redis',
config: {
host: 'test-host',
port: 6379,
},
},
},
localStorage: {
directory: './test-data',
},
};
mockConfigClient.getConfig.mockResolvedValue(mockConfig);
// Initialize config manager
await configManager.initialize();
// Verify config client was called
expect(mockConfigClient.getConfig).toHaveBeenCalledWith('config');
// Verify watchConfig was called
expect(mockConfigClient.watchConfig).toHaveBeenCalledWith(
'config',
expect.any(Function)
);
// Verify config was cached locally
expect(mkdir).toHaveBeenCalledWith(expect.stringContaining('data-manager'), { recursive: true });
expect(saveConfig).toHaveBeenCalledWith(
'data-manager/config.json',
mockConfig,
'Cached from configuration service',
undefined,
expect.objectContaining({
baseDir: './config',
})
);
// Verify storage config was extracted correctly
expect(configManager.getStorageConfig()).toEqual({
providers: mockConfig.providers,
});
// Verify unified storage manager options were extracted correctly
expect(configManager.getUnifiedStorageManagerOptions().localStorage).toEqual(mockConfig.localStorage);
});
it('should fall back to local file when service is unavailable', async () => {
// Set up mock config client to throw error
mockConfigClient.getConfig.mockRejectedValue(new Error('Service unavailable'));
// Set up mock config loader
const mockConfig = {
providers: {
hot: {
type: 'redis',
config: {
host: 'test-host',
port: 6379,
},
},
},
localStorage: {
directory: './test-data',
},
};
loadConfig.mockResolvedValue(mockConfig);
// Initialize config manager
await configManager.initialize();
// Verify config client was called
expect(mockConfigClient.getConfig).toHaveBeenCalledWith('config');
// Verify loadConfig was called
expect(loadConfig).toHaveBeenCalledWith(
'data-manager/config.json',
expect.anything(),
expect.objectContaining({
baseDir: './config',
})
);
// Verify watchConfig was called
expect(mockConfigClient.watchConfig).toHaveBeenCalledWith(
'config',
expect.any(Function)
);
// Verify storage config was extracted correctly
expect(configManager.getStorageConfig()).toEqual({
providers: mockConfig.providers,
});
// Verify unified storage manager options were extracted correctly
expect(configManager.getUnifiedStorageManagerOptions().localStorage).toEqual(mockConfig.localStorage);
});
it('should use default config when both service and local file are unavailable', async () => {
// Set up mock config client to throw error
mockConfigClient.getConfig.mockRejectedValue(new Error('Service unavailable'));
// Set up mock config loader to throw error
loadConfig.mockRejectedValue(new Error('File not found'));
// Initialize config manager
await configManager.initialize();
// Verify config client was called
expect(mockConfigClient.getConfig).toHaveBeenCalledWith('config');
// Verify loadConfig was called
expect(loadConfig).toHaveBeenCalledWith(
'data-manager/config.json',
expect.anything(),
expect.objectContaining({
baseDir: './config',
})
);
// Verify default config was cached locally
expect(saveConfig).toHaveBeenCalledWith(
'data-manager/config.json',
expect.anything(),
'Cached from configuration service',
undefined,
expect.objectContaining({
baseDir: './config',
})
);
// Verify storage config is default
expect(configManager.getStorageConfig()).toEqual(DEFAULT_STORAGE_CONFIG);
// Verify unified storage manager options are default
expect(configManager.getUnifiedStorageManagerOptions()).toEqual(DEFAULT_UNIFIED_STORAGE_MANAGER_OPTIONS);
});
});
describe('saveConfig', () => {
it('should save configuration to service and locally', async () => {
// Set up mock config client
mockConfigClient.saveConfig.mockResolvedValue(undefined);
// Create test config
const testConfig = {
providers: {
hot: {
type: 'redis',
config: {
host: 'test-host',
port: 6379,
},
},
},
};
// Save config
await configManager.saveConfig(testConfig, 'Test description');
// Verify saveConfig was called on config client
expect(mockConfigClient.saveConfig).toHaveBeenCalledWith(
'config',
expect.objectContaining({
providers: testConfig.providers,
}),
'Test description'
);
// Verify saveConfig was called on config loader
expect(saveConfig).toHaveBeenCalledWith(
'data-manager/config.json',
expect.objectContaining({
providers: testConfig.providers,
}),
'Test description',
undefined,
expect.objectContaining({
baseDir: './config',
})
);
// Verify storage config was updated
expect(configManager.getStorageConfig()).toEqual(testConfig);
});
it('should fall back to local file when service is unavailable', async () => {
// Set up mock config client to throw error
mockConfigClient.saveConfig.mockRejectedValue(new Error('Service unavailable'));
// Create test config
const testConfig = {
providers: {
hot: {
type: 'redis',
config: {
host: 'test-host',
port: 6379,
},
},
},
};
// Save config
await configManager.saveConfig(testConfig, 'Test description');
// Verify saveConfig was called on config client
expect(mockConfigClient.saveConfig).toHaveBeenCalledWith(
'config',
expect.objectContaining({
providers: testConfig.providers,
}),
'Test description'
);
// Verify saveConfig was called on config loader
expect(saveConfig).toHaveBeenCalledWith(
'data-manager/config.json',
expect.objectContaining({
providers: testConfig.providers,
}),
'Test description',
undefined,
expect.objectContaining({
baseDir: './config',
})
);
// Verify storage config was updated
expect(configManager.getStorageConfig()).toEqual(testConfig);
});
});
describe('event bus integration', () => {
it('should subscribe to configuration change events', () => {
// Create mock event bus
const mockEventBus: EventBus = {
publish: jest.fn(),
subscribe: jest.fn(),
unsubscribe: jest.fn(),
};
// Set event bus
configManager.setEventBus(mockEventBus);
// Verify subscribe was called
expect(mockEventBus.subscribe).toHaveBeenCalledWith(
'system.config_changed.v1',
expect.any(Function)
);
});
it('should publish configuration change events', async () => {
// Create mock event bus
const mockEventBus: EventBus = {
publish: jest.fn().mockResolvedValue(undefined),
subscribe: jest.fn(),
unsubscribe: jest.fn(),
};
// Set event bus
configManager.setEventBus(mockEventBus);
// Publish config changed
await configManager.publishConfigChanged();
// Verify publish was called
expect(mockEventBus.publish).toHaveBeenCalledWith(
'system.config_changed.v1',
expect.objectContaining({
service: 'data-manager',
timestamp: expect.any(Date),
config: expect.anything(),
}),
expect.anything()
);
});
});
});