一些检查失败了
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
153 行
4.7 KiB
TypeScript
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);
|
|
});
|
|
|
|
});
|