mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-12 12:28:42 +00:00
feat(server): make server storage adapters (#7902)
This commit is contained in:
282
packages/backend/server/tests/doc/history.spec.ts
Normal file
282
packages/backend/server/tests/doc/history.spec.ts
Normal file
@@ -0,0 +1,282 @@
|
||||
import { TestingModule } from '@nestjs/testing';
|
||||
import type { Snapshot } from '@prisma/client';
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import test from 'ava';
|
||||
import * as Sinon from 'sinon';
|
||||
|
||||
import {
|
||||
DocStorageModule,
|
||||
PgWorkspaceDocStorageAdapter,
|
||||
} from '../../src/core/doc';
|
||||
import { DocStorageOptions } from '../../src/core/doc/options';
|
||||
import { DocRecord } from '../../src/core/doc/storage';
|
||||
import { createTestingModule, initTestingDB } from '../utils';
|
||||
|
||||
let m: TestingModule;
|
||||
let adapter: PgWorkspaceDocStorageAdapter;
|
||||
let db: PrismaClient;
|
||||
|
||||
// cleanup database before each test
|
||||
test.before(async () => {
|
||||
m = await createTestingModule({
|
||||
imports: [DocStorageModule],
|
||||
});
|
||||
|
||||
adapter = m.get(PgWorkspaceDocStorageAdapter);
|
||||
db = m.get(PrismaClient);
|
||||
});
|
||||
|
||||
test.beforeEach(async () => {
|
||||
await initTestingDB(db);
|
||||
const options = m.get(DocStorageOptions);
|
||||
Sinon.stub(options, 'historyMaxAge').resolves(1000);
|
||||
});
|
||||
|
||||
test.afterEach(async () => {
|
||||
Sinon.restore();
|
||||
});
|
||||
|
||||
test.after.always(async () => {
|
||||
await m.close();
|
||||
});
|
||||
|
||||
const snapshot: Snapshot = {
|
||||
workspaceId: '1',
|
||||
id: 'doc1',
|
||||
blob: Buffer.from([1, 0]),
|
||||
state: Buffer.from([0]),
|
||||
seq: 0,
|
||||
updatedAt: new Date(),
|
||||
createdAt: new Date(),
|
||||
};
|
||||
|
||||
function getSnapshot(timestamp: number = Date.now()): DocRecord {
|
||||
return {
|
||||
spaceId: snapshot.workspaceId,
|
||||
docId: snapshot.id,
|
||||
bin: snapshot.blob,
|
||||
timestamp,
|
||||
};
|
||||
}
|
||||
|
||||
test('should create doc history if never created before', async t => {
|
||||
// @ts-expect-error private method
|
||||
Sinon.stub(adapter, 'lastDocHistory').resolves(null);
|
||||
|
||||
const timestamp = Date.now();
|
||||
// @ts-expect-error private method
|
||||
await adapter.createDocHistory(getSnapshot(timestamp));
|
||||
|
||||
const history = await db.snapshotHistory.findFirst({
|
||||
where: {
|
||||
workspaceId: '1',
|
||||
id: 'doc1',
|
||||
},
|
||||
});
|
||||
|
||||
t.truthy(history);
|
||||
t.is(history?.timestamp.getTime(), timestamp);
|
||||
});
|
||||
|
||||
test('should not create history if timestamp equals to last record', async t => {
|
||||
const timestamp = new Date();
|
||||
|
||||
// @ts-expect-error private method
|
||||
Sinon.stub(adapter, 'lastDocHistory').resolves({ timestamp, state: null });
|
||||
|
||||
// @ts-expect-error private method
|
||||
await adapter.createDocHistory(getSnapshot(timestamp));
|
||||
|
||||
const history = await db.snapshotHistory.findFirst({
|
||||
where: {
|
||||
workspaceId: '1',
|
||||
id: 'doc1',
|
||||
},
|
||||
});
|
||||
|
||||
t.falsy(history);
|
||||
});
|
||||
|
||||
test('should not create history if time diff is less than interval config', async t => {
|
||||
const timestamp = new Date();
|
||||
|
||||
// @ts-expect-error private method
|
||||
Sinon.stub(adapter, 'lastDocHistory').resolves({
|
||||
timestamp: new Date(timestamp.getTime() - 1000),
|
||||
state: Buffer.from([0, 1]),
|
||||
});
|
||||
|
||||
// @ts-expect-error private method
|
||||
await adapter.createDocHistory(getSnapshot(timestamp));
|
||||
|
||||
const history = await db.snapshotHistory.findFirst({
|
||||
where: {
|
||||
workspaceId: '1',
|
||||
id: 'doc1',
|
||||
},
|
||||
});
|
||||
|
||||
t.falsy(history);
|
||||
});
|
||||
|
||||
test('should create history if time diff is larger than interval config and state diff', async t => {
|
||||
const timestamp = new Date();
|
||||
|
||||
// @ts-expect-error private method
|
||||
Sinon.stub(adapter, 'lastDocHistory').resolves({
|
||||
timestamp: new Date(timestamp.getTime() - 1000 * 60 * 20),
|
||||
state: Buffer.from([0, 1]),
|
||||
});
|
||||
|
||||
// @ts-expect-error private method
|
||||
await adapter.createDocHistory(getSnapshot(timestamp));
|
||||
|
||||
const history = await db.snapshotHistory.findFirst({
|
||||
where: {
|
||||
workspaceId: '1',
|
||||
id: 'doc1',
|
||||
},
|
||||
});
|
||||
|
||||
t.truthy(history);
|
||||
});
|
||||
|
||||
test('should create history with force flag even if time diff in small', async t => {
|
||||
const timestamp = new Date();
|
||||
|
||||
// @ts-expect-error private method
|
||||
Sinon.stub(adapter, 'lastDocHistory').resolves({
|
||||
timestamp: new Date(timestamp.getTime() - 1),
|
||||
state: Buffer.from([0, 1]),
|
||||
});
|
||||
|
||||
// @ts-expect-error private method
|
||||
await adapter.createDocHistory(getSnapshot(timestamp), true);
|
||||
|
||||
const history = await db.snapshotHistory.findFirst({
|
||||
where: {
|
||||
workspaceId: '1',
|
||||
id: 'doc1',
|
||||
},
|
||||
});
|
||||
|
||||
t.truthy(history);
|
||||
});
|
||||
|
||||
test('should correctly list all history records', async t => {
|
||||
const timestamp = Date.now();
|
||||
|
||||
// insert expired data
|
||||
await db.snapshotHistory.createMany({
|
||||
data: Array.from({ length: 10 })
|
||||
.fill(0)
|
||||
.map((_, i) => ({
|
||||
workspaceId: snapshot.workspaceId,
|
||||
id: snapshot.id,
|
||||
blob: snapshot.blob,
|
||||
state: snapshot.state,
|
||||
timestamp: new Date(timestamp - 10 - i),
|
||||
expiredAt: new Date(timestamp - 1),
|
||||
})),
|
||||
});
|
||||
|
||||
// insert available data
|
||||
await db.snapshotHistory.createMany({
|
||||
data: Array.from({ length: 10 })
|
||||
.fill(0)
|
||||
.map((_, i) => ({
|
||||
workspaceId: snapshot.workspaceId,
|
||||
id: snapshot.id,
|
||||
blob: snapshot.blob,
|
||||
state: snapshot.state,
|
||||
timestamp: new Date(timestamp + i),
|
||||
expiredAt: new Date(timestamp + 1000),
|
||||
})),
|
||||
});
|
||||
|
||||
const list = await adapter.listDocHistories(
|
||||
snapshot.workspaceId,
|
||||
snapshot.id,
|
||||
{ before: timestamp + 20, limit: 8 }
|
||||
);
|
||||
const count = await db.snapshotHistory.count();
|
||||
|
||||
t.is(list.length, 8);
|
||||
t.is(count, 20);
|
||||
});
|
||||
|
||||
test('should be able to get history data', async t => {
|
||||
const timestamp = Date.now();
|
||||
|
||||
// @ts-expect-error private method
|
||||
await adapter.createDocHistory(getSnapshot(timestamp), true);
|
||||
|
||||
const history = await adapter.getDocHistory(
|
||||
snapshot.workspaceId,
|
||||
snapshot.id,
|
||||
timestamp
|
||||
);
|
||||
|
||||
t.truthy(history);
|
||||
t.deepEqual(history?.bin, snapshot.blob);
|
||||
});
|
||||
|
||||
test('should be able to get last history record', async t => {
|
||||
const timestamp = Date.now();
|
||||
|
||||
// insert available data
|
||||
await db.snapshotHistory.createMany({
|
||||
data: Array.from({ length: 10 })
|
||||
.fill(0)
|
||||
.map((_, i) => ({
|
||||
workspaceId: snapshot.workspaceId,
|
||||
id: snapshot.id,
|
||||
blob: snapshot.blob,
|
||||
state: snapshot.state,
|
||||
timestamp: new Date(timestamp + i),
|
||||
expiredAt: new Date(timestamp + 1000),
|
||||
})),
|
||||
});
|
||||
|
||||
// @ts-expect-error private method
|
||||
const history = await adapter.lastDocHistory(
|
||||
snapshot.workspaceId,
|
||||
snapshot.id
|
||||
);
|
||||
|
||||
t.truthy(history);
|
||||
t.is(history?.timestamp.getTime(), timestamp + 9);
|
||||
});
|
||||
|
||||
test('should be able to recover from history', async t => {
|
||||
await db.snapshot.create({
|
||||
data: {
|
||||
...snapshot,
|
||||
blob: Buffer.from([1, 1]),
|
||||
state: Buffer.from([1, 1]),
|
||||
},
|
||||
});
|
||||
const history1Timestamp = snapshot.updatedAt.getTime() - 10;
|
||||
|
||||
// @ts-expect-error private method
|
||||
await adapter.createDocHistory(getSnapshot(history1Timestamp));
|
||||
|
||||
await adapter.rollbackDoc(
|
||||
snapshot.workspaceId,
|
||||
snapshot.id,
|
||||
history1Timestamp
|
||||
);
|
||||
|
||||
const [history1, history2] = await db.snapshotHistory.findMany({
|
||||
where: {
|
||||
workspaceId: snapshot.workspaceId,
|
||||
id: snapshot.id,
|
||||
},
|
||||
});
|
||||
|
||||
t.is(history1.timestamp.getTime(), history1Timestamp);
|
||||
t.is(history2.timestamp.getTime(), snapshot.updatedAt.getTime());
|
||||
|
||||
// new history data force created with snapshot state before recovered
|
||||
t.deepEqual(history2.blob, Buffer.from([1, 1]));
|
||||
});
|
||||
Reference in New Issue
Block a user