This commit is contained in:
136
packages/core/tests/vault-concurrency.test.ts
Normal file
136
packages/core/tests/vault-concurrency.test.ts
Normal file
@@ -0,0 +1,136 @@
|
||||
import { describe, it, expect, afterEach } from 'vitest';
|
||||
import fs from 'node:fs';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
import { VaultStore } from '../src/vault.js';
|
||||
|
||||
describe('Vault Concurrency & Atomic Writes (11B-8)', () => {
|
||||
const tempDirs: string[] = [];
|
||||
|
||||
function makeTempDir(): string {
|
||||
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'waggle-vault-conc-'));
|
||||
tempDirs.push(dir);
|
||||
return dir;
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
for (const dir of tempDirs) {
|
||||
fs.rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
tempDirs.length = 0;
|
||||
});
|
||||
|
||||
it('5 concurrent setAsync calls — all 5 keys exist after', async () => {
|
||||
const dir = makeTempDir();
|
||||
const vault = new VaultStore(dir);
|
||||
|
||||
// Fire 5 concurrent writes
|
||||
await Promise.all([
|
||||
vault.setAsync('key-1', 'value-1'),
|
||||
vault.setAsync('key-2', 'value-2'),
|
||||
vault.setAsync('key-3', 'value-3'),
|
||||
vault.setAsync('key-4', 'value-4'),
|
||||
vault.setAsync('key-5', 'value-5'),
|
||||
]);
|
||||
|
||||
// Verify all 5 keys exist and have correct values
|
||||
for (let i = 1; i <= 5; i++) {
|
||||
const entry = vault.get(`key-${i}`);
|
||||
expect(entry).not.toBeNull();
|
||||
expect(entry!.value).toBe(`value-${i}`);
|
||||
}
|
||||
|
||||
// Verify via list that all 5 are present
|
||||
const list = vault.list();
|
||||
expect(list).toHaveLength(5);
|
||||
});
|
||||
|
||||
it('concurrent setAsync and deleteAsync do not corrupt vault', async () => {
|
||||
const dir = makeTempDir();
|
||||
const vault = new VaultStore(dir);
|
||||
|
||||
// Set some initial keys
|
||||
vault.set('keep-1', 'v1');
|
||||
vault.set('remove-1', 'v2');
|
||||
vault.set('keep-2', 'v3');
|
||||
|
||||
// Concurrently set new keys and delete existing ones
|
||||
await Promise.all([
|
||||
vault.setAsync('new-1', 'new-v1'),
|
||||
vault.deleteAsync('remove-1'),
|
||||
vault.setAsync('new-2', 'new-v2'),
|
||||
]);
|
||||
|
||||
expect(vault.get('keep-1')?.value).toBe('v1');
|
||||
expect(vault.get('keep-2')?.value).toBe('v3');
|
||||
expect(vault.get('new-1')?.value).toBe('new-v1');
|
||||
expect(vault.get('new-2')?.value).toBe('new-v2');
|
||||
expect(vault.get('remove-1')).toBeNull();
|
||||
});
|
||||
|
||||
it('atomic write — no vault.json.tmp left behind after write', () => {
|
||||
const dir = makeTempDir();
|
||||
const vault = new VaultStore(dir);
|
||||
|
||||
vault.set('test', 'value');
|
||||
|
||||
const tmpPath = path.join(dir, 'vault.json.tmp');
|
||||
expect(fs.existsSync(tmpPath)).toBe(false);
|
||||
expect(fs.existsSync(path.join(dir, 'vault.json'))).toBe(true);
|
||||
});
|
||||
|
||||
it('deleteAsync returns correct existed flag', async () => {
|
||||
const dir = makeTempDir();
|
||||
const vault = new VaultStore(dir);
|
||||
|
||||
vault.set('exists', 'val');
|
||||
|
||||
const existed = await vault.deleteAsync('exists');
|
||||
expect(existed).toBe(true);
|
||||
|
||||
const notExisted = await vault.deleteAsync('never-was');
|
||||
expect(notExisted).toBe(false);
|
||||
});
|
||||
|
||||
it('setAsync overwrites existing key', async () => {
|
||||
const dir = makeTempDir();
|
||||
const vault = new VaultStore(dir);
|
||||
|
||||
vault.set('overwrite-me', 'old-value');
|
||||
await vault.setAsync('overwrite-me', 'new-value');
|
||||
|
||||
const entry = vault.get('overwrite-me');
|
||||
expect(entry).not.toBeNull();
|
||||
expect(entry!.value).toBe('new-value');
|
||||
});
|
||||
|
||||
it('10 concurrent setAsync calls on same key — last one wins', async () => {
|
||||
const dir = makeTempDir();
|
||||
const vault = new VaultStore(dir);
|
||||
|
||||
// Chain them — since writeLock serializes, the last .then() should win
|
||||
const promises: Promise<void>[] = [];
|
||||
for (let i = 0; i < 10; i++) {
|
||||
promises.push(vault.setAsync('contested', `value-${i}`));
|
||||
}
|
||||
await Promise.all(promises);
|
||||
|
||||
const entry = vault.get('contested');
|
||||
expect(entry).not.toBeNull();
|
||||
// The value should be the last one written (value-9)
|
||||
// because the write lock serializes them in order
|
||||
expect(entry!.value).toBe('value-9');
|
||||
});
|
||||
|
||||
it('sync set still works correctly (backward compatibility)', () => {
|
||||
const dir = makeTempDir();
|
||||
const vault = new VaultStore(dir);
|
||||
|
||||
vault.set('sync-key', 'sync-value', { tag: 'test' });
|
||||
const entry = vault.get('sync-key');
|
||||
|
||||
expect(entry).not.toBeNull();
|
||||
expect(entry!.value).toBe('sync-value');
|
||||
expect(entry!.metadata).toEqual({ tag: 'test' });
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user