文件
mynode/packages/server/src/db.replication.test.ts
chen 301194963b
一些检查失败了
automerge / automerge (push) Has been cancelled
react-native-android-build-apk / AssembleRelease (push) Has been cancelled
Build macOS M1 / Main (push) Has been cancelled
Joplin Continuous Integration / Main (macos-15-intel) (push) Has been cancelled
Joplin Continuous Integration / Main (ubuntu-22.04) (push) Has been cancelled
Joplin Continuous Integration / Main (ubuntu-22.04-arm) (push) Has been cancelled
Joplin Continuous Integration / Main (windows-11-vs2026-arm) (push) Has been cancelled
Joplin Continuous Integration / Main (windows-2025) (push) Has been cancelled
Joplin Continuous Integration / ServerDockerImage (ubuntu-22.04) (push) Has been cancelled
Joplin Continuous Integration / ServerDockerImage (ubuntu-22.04-arm) (push) Has been cancelled
Joplin UI tests / Main (ubuntu-22.04) (push) Has been cancelled
Joplin UI tests / Main (windows-2025) (push) Has been cancelled
Joplin UI tests / Mobile (push) Has been cancelled
Close stale issues / ProcessStaleIssues (push) Has been cancelled
feat: Initial clean release of CHM
2026-11-04 11:29:39 +08:00

153 行
4.7 KiB
TypeScript

import { afterAllTests, beforeAllDb, beforeEachDb, createFolder, createUserAndSession, db, dbSlave, expectThrow, getDatabaseClientType, models, packageRootDir, updateFolder } from './utils/testing/testUtils';
import { connectDb, disconnectDb, reconnectDb, sqliteSyncSlave } from './db';
import { ChangeType, Event } from './services/database/types';
import { DatabaseConfig, DatabaseConfigClient } from './utils/types';
import { createDb } from './tools/dbTools';
import { msleep } from './utils/time';
import { FolderEntity } from '@joplin/lib/services/database/types';
const eventId1 = '4f405391-bd72-4a4f-809f-344fc6cd4b31';
const eventId2 = '4f405391-bd72-4a4f-809f-344fc6cd4b32';
const event1: Event = {
id: eventId1,
type: 1,
name: 'test',
created_time: Date.now(),
};
const event2 = {
...event1,
id: eventId2,
};
let testIndex = 0;
const beforeTest = async (envValues: Record<string, string> = null) => {
// Use `beforeAllDb` in `beforeEach` to ensure each test has its own database.
// To work around file locking issues on Windows, each test needs its own database instance:
const databaseKey = `db.replication.${testIndex ++}`;
await beforeAllDb(databaseKey, envValues ? { envValues } : null);
await beforeEachDb();
};
const afterTest = async () => {
await afterAllTests();
};
describe('db.replication', () => {
afterEach(async () => {
await afterTest();
});
it('should reconnect a database', async () => {
if (getDatabaseClientType() === DatabaseConfigClient.PostgreSQL) return;
await beforeTest();
await disconnectDb(db());
await expectThrow(async () => db().insert(event1).into('events'));
await reconnectDb(db());
await db().insert(event1).into('events');
{
const results = await db().select('*').from('events');
expect(results.length).toBe(1);
expect(results[0].id).toBe(eventId1);
}
await reconnectDb(db());
await db().insert(event2).into('events');
{
const results = await db().select('*').from('events');
expect(results.length).toBe(2);
expect([results[0].id, results[1].id].sort()).toEqual([eventId1, eventId2]);
}
});
it('should manually sync an SQLite slave instance', async () => {
if (getDatabaseClientType() === DatabaseConfigClient.PostgreSQL) return;
await beforeTest();
const masterConfig: DatabaseConfig = {
client: DatabaseConfigClient.SQLite,
name: `${packageRootDir}/db-master-test.sqlite`,
};
const slaveConfig: DatabaseConfig = {
client: DatabaseConfigClient.SQLite,
name: `${packageRootDir}/db-slave-test.sqlite`,
};
await createDb(masterConfig, { dropIfExists: true });
await createDb(slaveConfig, { dropIfExists: true });
const master = await connectDb(masterConfig);
const slave = await connectDb(slaveConfig);
await master.insert(event1).into('events');
expect((await master.select('*').from('events')).length).toBe(1);
expect((await slave.select('*').from('events')).length).toBe(0);
await sqliteSyncSlave(master, slave);
expect((await master.select('*').from('events')).length).toBe(1);
expect((await slave.select('*').from('events')).length).toBe(1);
await disconnectDb(master);
await disconnectDb(slave);
});
test('should track changes - using replication', async () => {
if (getDatabaseClientType() === DatabaseConfigClient.PostgreSQL) return;
await beforeTest({ DB_USE_SLAVE: '1' });
const { session, user } = await createUserAndSession(1, true);
const changeModel = models().change();
const folder = {
id: '000000000000000000000000000000F1',
title: 'title 1',
};
const folderItem = await createFolder(session.id, folder);
await msleep(1);
let result = await changeModel.delta(user.id);
// We get nothing because the slave has not been synced yet
expect(result.items.length).toBe(0);
// But we still get the item because it doesn't use the slave database
expect((await models().item().loadAsJoplinItem<FolderEntity>(folderItem.id)).title).toBe('title 1');
// After sync, we should get the change
await sqliteSyncSlave(db(), dbSlave());
result = await changeModel.delta(user.id);
expect(result.items.length).toBe(1);
expect(result.items[0].type).toBe(ChangeType.Create);
await updateFolder(session.id, { ...folder, title: 'title 2' });
result = await changeModel.delta(user.id, { cursor: result.cursor });
// Nothing because it hasn't been synced yet
expect(result.items.length).toBe(0);
// But we get the latest item if requesting it directly
expect((await models().item().loadAsJoplinItem<FolderEntity>(folderItem.id)).title).toBe('title 2');
// After sync, we should get the change
await sqliteSyncSlave(db(), dbSlave());
result = await changeModel.delta(user.id, { cursor: result.cursor });
expect(result.items.length).toBe(1);
expect(result.items[0].type).toBe(ChangeType.Update);
});
});