Sync DNS2 production changes - removed obsolete test suite and refactored structure
This commit is contained in:
@@ -1,249 +0,0 @@
|
||||
/**
|
||||
* State Manager Tests
|
||||
*
|
||||
* Tests the thread-safe state management with file locking
|
||||
*/
|
||||
|
||||
const StateManager = require('../state-manager');
|
||||
const fs = require('fs').promises;
|
||||
const path = require('path');
|
||||
const os = require('os');
|
||||
|
||||
// Dedicated temp subdirectory avoids cross-test file collisions
|
||||
const testDir = path.join(os.tmpdir(), `state-manager-test-${Date.now()}`);
|
||||
const testFile = path.join(testDir, 'test-state.json');
|
||||
|
||||
describe('StateManager', () => {
|
||||
let stateManager;
|
||||
|
||||
beforeAll(async () => {
|
||||
await fs.mkdir(testDir, { recursive: true });
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
// Clean up test file + stale lockfiles
|
||||
for (const f of [testFile, `${testFile}.lock`]) {
|
||||
try { await fs.unlink(f); } catch (e) { /* ignore */ }
|
||||
}
|
||||
|
||||
stateManager = new StateManager(testFile, {
|
||||
lockRetries: 20,
|
||||
lockRetryInterval: 50,
|
||||
lockTimeout: 15000,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
for (const f of [testFile, `${testFile}.lock`]) {
|
||||
try { await fs.unlink(f); } catch (e) { /* ignore */ }
|
||||
}
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
try { await fs.rm(testDir, { recursive: true }); } catch (e) { /* ignore */ }
|
||||
});
|
||||
|
||||
describe('Basic Operations', () => {
|
||||
test('creates file with empty array if not exists', async () => {
|
||||
const data = await stateManager.read();
|
||||
expect(Array.isArray(data)).toBe(true);
|
||||
expect(data.length).toBe(0);
|
||||
});
|
||||
|
||||
test('write and read roundtrip', async () => {
|
||||
const testData = [
|
||||
{ id: '1', name: 'Test Service 1' },
|
||||
{ id: '2', name: 'Test Service 2' },
|
||||
];
|
||||
|
||||
await stateManager.write(testData);
|
||||
const data = await stateManager.read();
|
||||
|
||||
expect(data).toEqual(testData);
|
||||
});
|
||||
|
||||
test('update with callback function', async () => {
|
||||
await stateManager.write([{ id: '1', name: 'Service 1' }]);
|
||||
|
||||
const updated = await stateManager.update(items => {
|
||||
items.push({ id: '2', name: 'Service 2' });
|
||||
return items;
|
||||
});
|
||||
|
||||
expect(updated.length).toBe(2);
|
||||
expect(updated[1].name).toBe('Service 2');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Convenience Methods', () => {
|
||||
test('addItem adds to array', async () => {
|
||||
await stateManager.addItem({ id: '1', name: 'Service 1' });
|
||||
await stateManager.addItem({ id: '2', name: 'Service 2' });
|
||||
|
||||
const items = await stateManager.read();
|
||||
expect(items.length).toBe(2);
|
||||
});
|
||||
|
||||
test('removeItem removes by ID', async () => {
|
||||
await stateManager.write([
|
||||
{ id: '1', name: 'Service 1' },
|
||||
{ id: '2', name: 'Service 2' },
|
||||
{ id: '3', name: 'Service 3' },
|
||||
]);
|
||||
|
||||
await stateManager.removeItem('2');
|
||||
|
||||
const items = await stateManager.read();
|
||||
expect(items.length).toBe(2);
|
||||
expect(items.find(i => i.id === '2')).toBeUndefined();
|
||||
});
|
||||
|
||||
test('updateItem updates by ID', async () => {
|
||||
await stateManager.write([
|
||||
{ id: '1', name: 'Service 1', status: 'offline' },
|
||||
]);
|
||||
|
||||
await stateManager.updateItem('1', { status: 'online' });
|
||||
|
||||
const item = await stateManager.findItem('1');
|
||||
expect(item.status).toBe('online');
|
||||
expect(item.name).toBe('Service 1'); // Unchanged
|
||||
});
|
||||
|
||||
test('findItem returns null for non-existent ID', async () => {
|
||||
await stateManager.write([{ id: '1', name: 'Service 1' }]);
|
||||
|
||||
const item = await stateManager.findItem('999');
|
||||
expect(item).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Concurrent Access', () => {
|
||||
test('concurrent writes do not corrupt data', async () => {
|
||||
// Start with empty array
|
||||
await stateManager.write([]);
|
||||
|
||||
// Simulate 10 concurrent writes
|
||||
const promises = [];
|
||||
for (let i = 0; i < 10; i++) {
|
||||
promises.push(
|
||||
stateManager.update(items => {
|
||||
items.push({ id: `service-${i}`, name: `Service ${i}` });
|
||||
return items;
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
await Promise.all(promises);
|
||||
|
||||
// Verify all items were added
|
||||
const items = await stateManager.read();
|
||||
expect(items.length).toBe(10);
|
||||
|
||||
// Verify JSON is valid (not corrupted)
|
||||
const fileContent = await fs.readFile(testFile, 'utf8');
|
||||
expect(() => JSON.parse(fileContent)).not.toThrow();
|
||||
});
|
||||
|
||||
test('concurrent reads while writing', async () => {
|
||||
await stateManager.write([{ id: '1', name: 'Initial' }]);
|
||||
|
||||
const writePromise = stateManager.update(async items => {
|
||||
// Simulate slow operation
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
items.push({ id: '2', name: 'New' });
|
||||
return items;
|
||||
});
|
||||
|
||||
const readPromises = [];
|
||||
for (let i = 0; i < 5; i++) {
|
||||
readPromises.push(stateManager.read());
|
||||
}
|
||||
|
||||
await Promise.all([writePromise, ...readPromises]);
|
||||
|
||||
// Should complete without errors
|
||||
const final = await stateManager.read();
|
||||
expect(final.length).toBe(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Error Handling', () => {
|
||||
test('throws error on invalid JSON', async () => {
|
||||
// Write invalid JSON directly
|
||||
await fs.writeFile(testFile, '{invalid json', 'utf8');
|
||||
|
||||
await expect(stateManager.read()).rejects.toThrow();
|
||||
});
|
||||
|
||||
test('handles missing file gracefully', async () => {
|
||||
await fs.unlink(testFile);
|
||||
|
||||
const data = await stateManager.read();
|
||||
expect(Array.isArray(data)).toBe(true);
|
||||
});
|
||||
|
||||
test('update callback errors are caught', async () => {
|
||||
await expect(
|
||||
stateManager.update(() => {
|
||||
throw new Error('Test error');
|
||||
}),
|
||||
).rejects.toThrow('Test error');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Lock Management', () => {
|
||||
test('isLocked detects locked state', async () => {
|
||||
const lockfile = require('proper-lockfile');
|
||||
|
||||
// Manually lock the file
|
||||
const release = await lockfile.lock(testFile);
|
||||
|
||||
const locked = await stateManager.isLocked();
|
||||
expect(locked).toBe(true);
|
||||
|
||||
await release();
|
||||
|
||||
const unlocked = await stateManager.isLocked();
|
||||
expect(unlocked).toBe(false);
|
||||
});
|
||||
|
||||
test('forceUnlock removes stuck lock', async () => {
|
||||
const lockfile = require('proper-lockfile');
|
||||
|
||||
// Create a stuck lock
|
||||
await lockfile.lock(testFile);
|
||||
|
||||
await stateManager.forceUnlock();
|
||||
|
||||
// Should be able to write now
|
||||
await expect(stateManager.write([])).resolves.not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Performance', () => {
|
||||
test('handles large datasets efficiently', async () => {
|
||||
const largeDataset = [];
|
||||
for (let i = 0; i < 1000; i++) {
|
||||
largeDataset.push({
|
||||
id: `service-${i}`,
|
||||
name: `Service ${i}`,
|
||||
url: `https://service-${i}.example.com`,
|
||||
status: 'online',
|
||||
});
|
||||
}
|
||||
|
||||
const startTime = Date.now();
|
||||
await stateManager.write(largeDataset);
|
||||
const writeTime = Date.now() - startTime;
|
||||
|
||||
const readStart = Date.now();
|
||||
const data = await stateManager.read();
|
||||
const readTime = Date.now() - readStart;
|
||||
|
||||
expect(data.length).toBe(1000);
|
||||
expect(writeTime).toBeLessThan(1000); // Should write in <1s
|
||||
expect(readTime).toBeLessThan(100); // Should read in <100ms
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user