@sailboat-computer/data-storage
Version:
Shared data storage library for sailboat computer v3
329 lines (299 loc) • 11.5 kB
text/typescript
/**
* Integration tests for lifecycle operations
*/
import { createStorageManager } from '../../src/storage/manager';
import { createLifecycleManager } from '../../src/lifecycle/manager';
import { createDownsamplingEngine } from '../../src/downsampling/engine';
import { createTimeBasedDownsampler } from '../../src/downsampling/strategies/time-based';
import { createGeospatialDownsampler } from '../../src/downsampling/strategies/geospatial';
import { createCycleBasedDownsampler } from '../../src/downsampling/strategies/cycle-based';
import { createMaintenanceBasedDownsampler } from '../../src/downsampling/strategies/maintenance-based';
import { createGeotemporalGridDownsampler } from '../../src/downsampling/strategies/geotemporal-grid';
import {
createWeatherDataGrid,
createWeatherTimeSeries,
createStormWeatherDataPoint
} from '../fixtures/weather-data';
import {
createSeaStateDataGrid,
createSeaStateTimeSeries,
createRoughSeaStateDataPoint
} from '../fixtures/sea-state-data';
import { StorageTier, BatchPriority } from '../../src/types';
// Mock storage providers
jest.mock('../../src/storage/providers/redis');
jest.mock('../../src/storage/providers/influxdb');
jest.mock('../../src/storage/providers/postgresql');
describe('Lifecycle Operations Integration', () => {
// Create components
const storageManager = createStorageManager({
providers: {
hot: {
type: 'redis',
config: {
host: 'localhost',
port: 6379
}
},
warm: {
type: 'influxdb',
config: {
url: 'http://localhost:8086',
token: 'test-token',
org: 'test-org',
bucket: 'test-bucket'
}
}
},
batching: {
enabled: true,
maxBatchSize: 10,
timeInterval: 1000,
priorityThresholds: {
[BatchPriority.HIGH]: 0,
[BatchPriority.NORMAL]: 100,
[BatchPriority.LOW]: 1000
}
}
} as any); // Type assertion to bypass type checking for test
// Create downsampling engine
const downsamplingEngine = createDownsamplingEngine();
// Register all downsamplers
beforeAll(() => {
downsamplingEngine.registerDownsampler(createTimeBasedDownsampler());
downsamplingEngine.registerDownsampler(createGeospatialDownsampler());
downsamplingEngine.registerDownsampler(createCycleBasedDownsampler());
downsamplingEngine.registerDownsampler(createMaintenanceBasedDownsampler());
downsamplingEngine.registerDownsampler(createGeotemporalGridDownsampler());
});
// Initialize storage manager
beforeEach(async () => {
await storageManager.initialize();
});
// Shutdown storage manager after tests
afterEach(async () => {
await storageManager.shutdown();
});
describe('Lifecycle Manager with GeotemporalGridDownsampler', () => {
it('should initialize with weather and sea state policies', () => {
// Create lifecycle manager
const lifecycleManager = createLifecycleManager(
storageManager,
{
policies: [
{
category: 'weather.data',
hotRetentionDays: 7,
warmRetentionDays: 90,
coldRetentionDays: 730,
migrationThresholdDays: 3,
downsample: true,
downsamplingRuleId: 'weather-rule'
},
{
category: 'sea.state',
hotRetentionDays: 5,
warmRetentionDays: 60,
coldRetentionDays: 365,
migrationThresholdDays: 2,
downsample: true,
downsamplingRuleId: 'sea-state-rule'
}
],
downsamplingRules: [
{
id: 'weather-rule',
name: 'Weather Data Downsampling',
dataType: 'weather.data',
strategy: {
type: 'geotemporal-grid',
baseGridSize: 1.0,
adaptiveGridLevels: 3,
temporalHierarchy: {
recent: { maxAge: 7, resolution: 1 },
mediumTerm: { maxAge: 30, resolution: 6 },
longTerm: { maxAge: 90, resolution: 24 },
historical: { maxAge: 365, resolution: 168 },
seasonal: { resolution: 720 }
}
}
},
{
id: 'sea-state-rule',
name: 'Sea State Data Downsampling',
dataType: 'sea.state',
strategy: {
type: 'geotemporal-grid',
baseGridSize: 0.5,
adaptiveGridLevels: 3,
temporalHierarchy: {
recent: { maxAge: 3, resolution: 1 },
mediumTerm: { maxAge: 14, resolution: 3 },
longTerm: { maxAge: 60, resolution: 12 },
historical: { maxAge: 180, resolution: 72 },
seasonal: { resolution: 720 }
}
}
}
],
downsamplingSchedules: [
{ ruleId: 'weather-rule', cronExpression: '0 3 * * *', enabled: true },
{ ruleId: 'sea-state-rule', cronExpression: '0 9 * * *', enabled: true }
],
cleanupInterval: 24 * 60 * 60 * 1000,
migrationInterval: 12 * 60 * 60 * 1000
},
downsamplingEngine
);
// Verify initialization
const policies = lifecycleManager.getPolicies();
expect(policies.length).toBe(2);
expect(policies[0].category).toBe('weather.data');
expect(policies[1].category).toBe('sea.state');
});
it('should run downsampling for weather data', async () => {
// Create lifecycle manager
const lifecycleManager = createLifecycleManager(
storageManager,
{
policies: [
{
category: 'weather.data',
hotRetentionDays: 7,
warmRetentionDays: 90,
coldRetentionDays: 730,
migrationThresholdDays: 3,
downsample: true,
downsamplingRuleId: 'weather-rule'
}
],
downsamplingRules: [
{
id: 'weather-rule',
name: 'Weather Data Downsampling',
dataType: 'weather.data',
strategy: {
type: 'geotemporal-grid',
baseGridSize: 1.0,
adaptiveGridLevels: 3,
temporalHierarchy: {
recent: { maxAge: 7, resolution: 1 },
mediumTerm: { maxAge: 30, resolution: 6 },
longTerm: { maxAge: 90, resolution: 24 },
historical: { maxAge: 365, resolution: 168 },
seasonal: { resolution: 720 }
}
}
}
],
downsamplingSchedules: [
{ ruleId: 'weather-rule', cronExpression: '0 3 * * *', enabled: true }
],
cleanupInterval: 24 * 60 * 60 * 1000,
migrationInterval: 12 * 60 * 60 * 1000
},
downsamplingEngine
);
// Mock the storage manager's retrieve and storeBatch methods
const weatherData = createWeatherDataGrid(37.7749, -122.4194, 1.0, 10, new Date());
// @ts-ignore - Mock implementation
storageManager.retrieve = jest.fn().mockResolvedValue(weatherData);
// @ts-ignore - Mock implementation
storageManager.storeBatch = jest.fn().mockResolvedValue(
Array(50).fill(0).map((_, i) => `mock-id-${i}`)
);
// Run downsampling
await lifecycleManager.runDownsampling('weather-rule');
// Verify retrieve was called with correct parameters
expect(storageManager.retrieve).toHaveBeenCalledWith(
expect.objectContaining({
category: 'weather.data'
}),
StorageTier.WARM
);
// Verify storeBatch was called
expect(storageManager.storeBatch).toHaveBeenCalled();
// Get the first argument of the first call
const storeBatchArgs = (storageManager.storeBatch as jest.Mock).mock.calls[0][0];
// Verify downsampled data was stored
expect(storeBatchArgs.length).toBeLessThan(weatherData.length);
// Verify downsampling metadata
const firstItem = storeBatchArgs[0];
expect(firstItem.data).toBeDefined();
expect(firstItem.options.tags.downsampled).toBe('true');
expect(firstItem.options.tags.downsamplingStrategy).toBe('geotemporal-grid');
});
it('should run downsampling for sea state data', async () => {
// Create lifecycle manager
const lifecycleManager = createLifecycleManager(
storageManager,
{
policies: [
{
category: 'sea.state',
hotRetentionDays: 5,
warmRetentionDays: 60,
coldRetentionDays: 365,
migrationThresholdDays: 2,
downsample: true,
downsamplingRuleId: 'sea-state-rule'
}
],
downsamplingRules: [
{
id: 'sea-state-rule',
name: 'Sea State Data Downsampling',
dataType: 'sea.state',
strategy: {
type: 'geotemporal-grid',
baseGridSize: 0.5,
adaptiveGridLevels: 3,
temporalHierarchy: {
recent: { maxAge: 3, resolution: 1 },
mediumTerm: { maxAge: 14, resolution: 3 },
longTerm: { maxAge: 60, resolution: 12 },
historical: { maxAge: 180, resolution: 72 },
seasonal: { resolution: 720 }
}
}
}
],
downsamplingSchedules: [
{ ruleId: 'sea-state-rule', cronExpression: '0 9 * * *', enabled: true }
],
cleanupInterval: 24 * 60 * 60 * 1000,
migrationInterval: 12 * 60 * 60 * 1000
},
downsamplingEngine
);
// Mock the storage manager's retrieve and storeBatch methods
const seaStateData = createSeaStateDataGrid(37.8, -122.5, 0.5, 10, new Date());
// @ts-ignore - Mock implementation
storageManager.retrieve = jest.fn().mockResolvedValue(seaStateData);
// @ts-ignore - Mock implementation
storageManager.storeBatch = jest.fn().mockResolvedValue(
Array(50).fill(0).map((_, i) => `mock-id-${i}`)
);
// Run downsampling
await lifecycleManager.runDownsampling('sea-state-rule');
// Verify retrieve was called with correct parameters
expect(storageManager.retrieve).toHaveBeenCalledWith(
expect.objectContaining({
category: 'sea.state'
}),
StorageTier.WARM
);
// Verify storeBatch was called
expect(storageManager.storeBatch).toHaveBeenCalled();
// Get the first argument of the first call
const storeBatchArgs = (storageManager.storeBatch as jest.Mock).mock.calls[0][0];
// Verify downsampled data was stored
expect(storeBatchArgs.length).toBeLessThan(seaStateData.length);
// Verify downsampling metadata
const firstItem = storeBatchArgs[0];
expect(firstItem.data).toBeDefined();
expect(firstItem.options.tags.downsampled).toBe('true');
expect(firstItem.options.tags.downsamplingStrategy).toBe('geotemporal-grid');
});
});
});