78 lines
3.8 KiB
JavaScript
78 lines
3.8 KiB
JavaScript
'use strict';
|
|
|
|
const test = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const fs = require('node:fs');
|
|
const os = require('node:os');
|
|
const path = require('node:path');
|
|
const { Store } = require('../lib/store');
|
|
|
|
test('store persists transactions atomically', async (t) => {
|
|
const dir = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'nexusgate-store-'));
|
|
t.after(() => fs.promises.rm(dir, { recursive: true, force: true }));
|
|
const file = path.join(dir, 'data.json');
|
|
const store = await new Store(file).init();
|
|
await store.transaction((data) => data.customers.push({ id: 'cus_1', name: '测试客户' }));
|
|
const reloaded = await new Store(file).init();
|
|
assert.equal(reloaded.data.customers.length, 1);
|
|
assert.equal(reloaded.data.customers[0].name, '测试客户');
|
|
assert.equal((await fs.promises.stat(file)).mode & 0o777, 0o600);
|
|
});
|
|
|
|
test('store rejects an invalid replacement', async (t) => {
|
|
const dir = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'nexusgate-store-'));
|
|
t.after(() => fs.promises.rm(dir, { recursive: true, force: true }));
|
|
const store = await new Store(path.join(dir, 'data.json')).init();
|
|
await assert.rejects(() => store.replace({ schemaVersion: 99 }), /Unsupported or corrupt/);
|
|
assert.equal(store.data.schemaVersion, 1);
|
|
});
|
|
|
|
test('older backups without subscription logs remain restorable', async (t) => {
|
|
const dir = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'nexusgate-store-'));
|
|
t.after(() => fs.promises.rm(dir, { recursive:true, force:true }));
|
|
const file = path.join(dir, 'data.json');
|
|
const store = await new Store(file).init();
|
|
const oldBackup = store.snapshot();
|
|
delete oldBackup.subscriptionAccess;
|
|
delete oldBackup.subscriptionClients;
|
|
await store.replace(oldBackup);
|
|
assert.deepEqual(store.data.subscriptionAccess, []);
|
|
assert.deepEqual(store.data.subscriptionClients, []);
|
|
await fs.promises.writeFile(file, JSON.stringify(oldBackup));
|
|
const loaded = await new Store(file).init();
|
|
assert.deepEqual(loaded.data.subscriptionAccess, []);
|
|
assert.deepEqual(loaded.data.subscriptionClients, []);
|
|
});
|
|
|
|
test('idle status updates and skipped transactions do not rewrite the database', async (t) => {
|
|
const dir = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'nexusgate-store-'));
|
|
t.after(() => fs.promises.rm(dir, { recursive: true, force: true }));
|
|
const file = path.join(dir, 'data.json');
|
|
const store = await new Store(file).init();
|
|
const initial = await fs.promises.readFile(file, 'utf8');
|
|
await store.transient((data) => { data.settings.lastHeartbeat = 'seen'; });
|
|
assert.equal(store.data.settings.lastHeartbeat, 'seen');
|
|
assert.equal(await store.transaction(() => Store.SKIP), null);
|
|
assert.equal(await fs.promises.readFile(file, 'utf8'), initial);
|
|
await store.transaction((data) => { data.customers.push({ id: 'cus_1' }); });
|
|
assert.equal((await new Store(file).init()).data.settings.lastHeartbeat, 'seen');
|
|
});
|
|
|
|
test('transient updates serialize with concurrent durable transactions', async (t) => {
|
|
const dir = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'nexusgate-store-'));
|
|
t.after(() => fs.promises.rm(dir, { recursive: true, force: true }));
|
|
const file = path.join(dir, 'data.json');
|
|
const store = await new Store(file).init();
|
|
const durable = store.transaction(async (data) => {
|
|
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
data.settings.completedJobRetentionDays = 3;
|
|
});
|
|
const transient = store.transient((data) => { data.settings.lastHeartbeat = 'new'; });
|
|
await Promise.all([durable, transient]);
|
|
assert.equal(store.data.settings.lastHeartbeat, 'new');
|
|
await store.transaction((data) => { data.settings.activityRetentionDays = 14; });
|
|
const loaded = await new Store(file).init();
|
|
assert.equal(loaded.data.settings.lastHeartbeat, 'new');
|
|
assert.equal(loaded.data.settings.completedJobRetentionDays, 3);
|
|
});
|