2023-08-29 13:07:05 +03:00
|
|
|
import { deepEqual, equal, ok } from 'node:assert';
|
2023-09-01 22:41:29 +03:00
|
|
|
import { mock } from 'node:test';
|
2023-08-29 13:07:05 +03:00
|
|
|
|
|
|
|
import { INestApplication } from '@nestjs/common';
|
|
|
|
import { Test, TestingModule } from '@nestjs/testing';
|
2023-09-01 22:41:29 +03:00
|
|
|
import test from 'ava';
|
2023-08-29 13:07:05 +03:00
|
|
|
import { register } from 'prom-client';
|
|
|
|
import * as Sinon from 'sinon';
|
|
|
|
import { Doc as YDoc, encodeStateAsUpdate } from 'yjs';
|
|
|
|
|
|
|
|
import { Config, ConfigModule } from '../config';
|
|
|
|
import { MetricsModule } from '../metrics';
|
|
|
|
import { DocManager, DocModule } from '../modules/doc';
|
|
|
|
import { PrismaModule, PrismaService } from '../prisma';
|
|
|
|
import { flushDB } from './utils';
|
|
|
|
|
|
|
|
const createModule = () => {
|
|
|
|
return Test.createTestingModule({
|
|
|
|
imports: [
|
|
|
|
PrismaModule,
|
|
|
|
MetricsModule,
|
|
|
|
ConfigModule.forRoot(),
|
|
|
|
DocModule.forRoot(),
|
|
|
|
],
|
|
|
|
}).compile();
|
|
|
|
};
|
|
|
|
|
2023-09-01 22:41:29 +03:00
|
|
|
let app: INestApplication;
|
|
|
|
let m: TestingModule;
|
|
|
|
let timer: Sinon.SinonFakeTimers;
|
2023-08-29 13:07:05 +03:00
|
|
|
|
2023-09-01 22:41:29 +03:00
|
|
|
// cleanup database before each test
|
|
|
|
test.beforeEach(async () => {
|
|
|
|
timer = Sinon.useFakeTimers({
|
|
|
|
toFake: ['setInterval'],
|
2023-08-29 13:07:05 +03:00
|
|
|
});
|
2023-09-01 22:41:29 +03:00
|
|
|
await flushDB();
|
|
|
|
m = await createModule();
|
|
|
|
app = m.createNestApplication();
|
|
|
|
app.enableShutdownHooks();
|
|
|
|
await app.init();
|
|
|
|
});
|
2023-08-29 13:07:05 +03:00
|
|
|
|
2023-09-01 22:41:29 +03:00
|
|
|
test.afterEach(async () => {
|
|
|
|
await app.close();
|
|
|
|
timer.restore();
|
|
|
|
});
|
2023-08-29 13:07:05 +03:00
|
|
|
|
2023-09-01 22:41:29 +03:00
|
|
|
test('should setup update poll interval', async t => {
|
|
|
|
register.clear();
|
|
|
|
const m = await createModule();
|
|
|
|
const manager = m.get(DocManager);
|
|
|
|
const fake = mock.method(manager, 'setup');
|
2023-08-29 13:07:05 +03:00
|
|
|
|
2023-09-01 22:41:29 +03:00
|
|
|
await m.createNestApplication().init();
|
2023-08-29 13:07:05 +03:00
|
|
|
|
2023-09-01 22:41:29 +03:00
|
|
|
equal(fake.mock.callCount(), 1);
|
|
|
|
// @ts-expect-error private member
|
|
|
|
ok(manager.job);
|
|
|
|
t.pass();
|
|
|
|
});
|
2023-08-29 13:07:05 +03:00
|
|
|
|
2023-09-01 22:41:29 +03:00
|
|
|
test('should be able to stop poll', async t => {
|
|
|
|
const manager = m.get(DocManager);
|
|
|
|
const fake = mock.method(manager, 'destroy');
|
2023-08-29 13:07:05 +03:00
|
|
|
|
2023-09-01 22:41:29 +03:00
|
|
|
await app.close();
|
2023-08-29 13:07:05 +03:00
|
|
|
|
2023-09-01 22:41:29 +03:00
|
|
|
equal(fake.mock.callCount(), 1);
|
|
|
|
// @ts-expect-error private member
|
|
|
|
equal(manager.job, null);
|
|
|
|
t.pass();
|
|
|
|
});
|
2023-08-29 13:07:05 +03:00
|
|
|
|
2023-09-01 22:41:29 +03:00
|
|
|
test('should poll when intervel due', async t => {
|
|
|
|
const manager = m.get(DocManager);
|
|
|
|
const interval = m.get(Config).doc.manager.updatePollInterval;
|
2023-08-29 13:07:05 +03:00
|
|
|
|
2023-09-01 22:41:29 +03:00
|
|
|
let resolve: any;
|
|
|
|
const fake = mock.method(manager, 'apply', () => {
|
|
|
|
return new Promise(_resolve => {
|
|
|
|
resolve = _resolve;
|
2023-08-29 13:07:05 +03:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2023-09-01 22:41:29 +03:00
|
|
|
timer.tick(interval);
|
|
|
|
equal(fake.mock.callCount(), 1);
|
2023-08-29 13:07:05 +03:00
|
|
|
|
2023-09-01 22:41:29 +03:00
|
|
|
// busy
|
|
|
|
timer.tick(interval);
|
|
|
|
// @ts-expect-error private member
|
|
|
|
equal(manager.busy, true);
|
|
|
|
equal(fake.mock.callCount(), 1);
|
2023-08-29 13:07:05 +03:00
|
|
|
|
2023-09-01 22:41:29 +03:00
|
|
|
resolve();
|
|
|
|
await timer.tickAsync(1);
|
2023-08-29 13:07:05 +03:00
|
|
|
|
2023-09-01 22:41:29 +03:00
|
|
|
// @ts-expect-error private member
|
|
|
|
equal(manager.busy, false);
|
|
|
|
timer.tick(interval);
|
|
|
|
equal(fake.mock.callCount(), 2);
|
|
|
|
t.pass();
|
|
|
|
});
|
2023-08-29 13:07:05 +03:00
|
|
|
|
2023-09-01 22:41:29 +03:00
|
|
|
test('should merge update when intervel due', async t => {
|
|
|
|
const db = m.get(PrismaService);
|
|
|
|
const manager = m.get(DocManager);
|
2023-08-29 13:07:05 +03:00
|
|
|
|
2023-09-01 22:41:29 +03:00
|
|
|
const doc = new YDoc();
|
|
|
|
const text = doc.getText('content');
|
|
|
|
text.insert(0, 'hello');
|
|
|
|
const update = encodeStateAsUpdate(doc);
|
2023-08-29 13:07:05 +03:00
|
|
|
|
2023-09-01 22:41:29 +03:00
|
|
|
const ws = await db.workspace.create({
|
|
|
|
data: {
|
|
|
|
id: '1',
|
|
|
|
public: false,
|
|
|
|
},
|
|
|
|
});
|
2023-08-29 13:07:05 +03:00
|
|
|
|
2023-09-01 22:41:29 +03:00
|
|
|
await db.update.createMany({
|
|
|
|
data: [
|
|
|
|
{
|
2023-08-29 13:07:05 +03:00
|
|
|
id: '1',
|
2023-09-01 22:41:29 +03:00
|
|
|
workspaceId: '1',
|
|
|
|
blob: Buffer.from([0, 0]),
|
2023-08-29 13:07:05 +03:00
|
|
|
},
|
2023-09-01 22:41:29 +03:00
|
|
|
{
|
|
|
|
id: '1',
|
|
|
|
workspaceId: '1',
|
|
|
|
blob: Buffer.from(update),
|
|
|
|
},
|
|
|
|
],
|
|
|
|
});
|
|
|
|
|
|
|
|
await manager.apply();
|
2023-08-29 13:07:05 +03:00
|
|
|
|
2023-09-01 22:41:29 +03:00
|
|
|
deepEqual(await manager.getLatestUpdate(ws.id, '1'), update);
|
2023-08-29 13:07:05 +03:00
|
|
|
|
2023-09-01 22:41:29 +03:00
|
|
|
let appendUpdate = Buffer.from([]);
|
|
|
|
doc.on('update', update => {
|
|
|
|
appendUpdate = Buffer.from(update);
|
2023-08-29 13:07:05 +03:00
|
|
|
});
|
2023-09-01 22:41:29 +03:00
|
|
|
text.insert(5, 'world');
|
|
|
|
|
|
|
|
await db.update.create({
|
|
|
|
data: {
|
|
|
|
workspaceId: ws.id,
|
|
|
|
id: '1',
|
|
|
|
blob: appendUpdate,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
await manager.apply();
|
|
|
|
|
|
|
|
deepEqual(
|
|
|
|
await manager.getLatestUpdate(ws.id, '1'),
|
|
|
|
encodeStateAsUpdate(doc)
|
|
|
|
);
|
|
|
|
t.pass();
|
2023-08-29 13:07:05 +03:00
|
|
|
});
|