mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-11 20:08:37 +00:00
refactor(server): use ava (#4120)
This commit is contained in:
@@ -11,11 +11,8 @@
|
||||
"build": "tsc",
|
||||
"start": "node --loader ts-node/esm.mjs --es-module-specifier-resolution node ./src/index.ts",
|
||||
"dev": "nodemon ./src/index.ts",
|
||||
"test": "yarn exec ts-node-esm ./scripts/run-test.ts all",
|
||||
"test:select": "yarn exec ts-node-esm ./scripts/run-test.ts",
|
||||
"test:watch": "yarn exec ts-node-esm ./scripts/run-test.ts all --watch",
|
||||
"test:select:watch": "yarn exec ts-node-esm ./scripts/run-test.ts --watch",
|
||||
"test:coverage": "c8 yarn exec ts-node-esm ./scripts/run-test.ts all",
|
||||
"test": "ava --concurrency 1 --serial",
|
||||
"test:coverage": "c8 ava --concurrency 1 --serial",
|
||||
"postinstall": "prisma generate"
|
||||
},
|
||||
"dependencies": {
|
||||
@@ -90,6 +87,7 @@
|
||||
"@types/sinon": "^10.0.16",
|
||||
"@types/supertest": "^2.0.12",
|
||||
"@types/ws": "^8.5.5",
|
||||
"ava": "^5.3.1",
|
||||
"c8": "^8.0.1",
|
||||
"nodemon": "^3.0.1",
|
||||
"sinon": "^15.2.0",
|
||||
@@ -97,6 +95,27 @@
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^5.2.2"
|
||||
},
|
||||
"ava": {
|
||||
"extensions": {
|
||||
"ts": "module"
|
||||
},
|
||||
"nodeArguments": [
|
||||
"--loader",
|
||||
"ts-node/esm.mjs",
|
||||
"--es-module-specifier-resolution",
|
||||
"node"
|
||||
],
|
||||
"files": [
|
||||
"src/**/*.spec.ts"
|
||||
],
|
||||
"require": [
|
||||
"./src/prelude.ts"
|
||||
],
|
||||
"environmentVariables": {
|
||||
"TS_NODE_PROJECT": "./tsconfig.json",
|
||||
"NODE_ENV": "test"
|
||||
}
|
||||
},
|
||||
"nodemonConfig": {
|
||||
"exec": "node",
|
||||
"script": "./src/index.ts",
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
#!/usr/bin/env ts-node-esm
|
||||
import { resolve } from 'node:path';
|
||||
|
||||
import * as p from '@clack/prompts';
|
||||
import { spawn, spawnSync } from 'child_process';
|
||||
import { readdir } from 'fs/promises';
|
||||
import * as process from 'process';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
import pkg from '../package.json' assert { type: 'json' };
|
||||
|
||||
const root = fileURLToPath(new URL('..', import.meta.url));
|
||||
const testDir = resolve(root, 'src', 'tests');
|
||||
const files = await readdir(testDir);
|
||||
|
||||
const watchMode = process.argv.includes('--watch');
|
||||
|
||||
const sharedArgs = [
|
||||
...pkg.nodemonConfig.nodeArgs,
|
||||
'--test',
|
||||
watchMode ? '--watch' : '',
|
||||
];
|
||||
|
||||
const env = {
|
||||
PATH: process.env.PATH,
|
||||
NODE_ENV: 'test',
|
||||
DATABASE_URL: process.env.DATABASE_URL,
|
||||
NODE_NO_WARNINGS: '1',
|
||||
};
|
||||
|
||||
if (process.argv[2] === 'all') {
|
||||
files.forEach(file => {
|
||||
const path = resolve(testDir, file);
|
||||
spawnSync('node', [...sharedArgs, path], {
|
||||
cwd: root,
|
||||
env,
|
||||
stdio: 'inherit',
|
||||
shell: true,
|
||||
});
|
||||
});
|
||||
} else {
|
||||
const result = await p.group({
|
||||
file: () =>
|
||||
p.select({
|
||||
message: 'Select a file to run',
|
||||
options: files.map(file => ({
|
||||
label: file,
|
||||
value: file as any,
|
||||
})),
|
||||
}),
|
||||
});
|
||||
|
||||
const target = resolve(testDir, result.file);
|
||||
|
||||
const cp = spawn(
|
||||
'node',
|
||||
[
|
||||
...sharedArgs,
|
||||
'--test-reporter=spec',
|
||||
'--test-reporter-destination=stdout',
|
||||
target,
|
||||
],
|
||||
{
|
||||
cwd: root,
|
||||
env,
|
||||
stdio: 'inherit',
|
||||
shell: true,
|
||||
}
|
||||
);
|
||||
cp.on('exit', code => {
|
||||
process.exit(code ?? 0);
|
||||
});
|
||||
}
|
||||
@@ -3,7 +3,7 @@ import { Module } from '@nestjs/common';
|
||||
import { AppController } from './app.controller';
|
||||
import { ConfigModule } from './config';
|
||||
import { MetricsModule } from './metrics';
|
||||
import { BusinessModules, Providers } from './modules';
|
||||
import { BusinessModules } from './modules';
|
||||
import { PrismaModule } from './prisma';
|
||||
import { SessionModule } from './session';
|
||||
import { StorageModule } from './storage';
|
||||
@@ -19,7 +19,6 @@ import { RateLimiterModule } from './throttler';
|
||||
RateLimiterModule,
|
||||
...BusinessModules,
|
||||
],
|
||||
providers: Providers,
|
||||
controllers: [AppController],
|
||||
})
|
||||
export class AppModule {}
|
||||
|
||||
@@ -24,6 +24,7 @@ import graphqlUploadExpress from 'graphql-upload/graphqlUploadExpress.mjs';
|
||||
|
||||
import { AppModule } from './app';
|
||||
import { Config } from './config';
|
||||
import { ExceptionLogger } from './middleware/exception-logger';
|
||||
import { serverTimingAndCache } from './middleware/timing';
|
||||
import { RedisIoAdapter } from './modules/sync/redis-adapter';
|
||||
|
||||
@@ -75,6 +76,7 @@ app.use(
|
||||
})
|
||||
);
|
||||
|
||||
app.useGlobalFilters(new ExceptionLogger());
|
||||
app.use(cookieParser());
|
||||
|
||||
const config = app.get(Config);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Global, Module } from '@nestjs/common';
|
||||
|
||||
import { SessionService } from '../../session';
|
||||
import { MAILER, MailService } from './mailer';
|
||||
import { NextAuthController } from './next-auth.controller';
|
||||
import { NextAuthOptionsProvider } from './next-auth-options';
|
||||
@@ -10,6 +11,7 @@ import { AuthService } from './service';
|
||||
@Module({
|
||||
providers: [
|
||||
AuthService,
|
||||
SessionService,
|
||||
AuthResolver,
|
||||
NextAuthOptionsProvider,
|
||||
MAILER,
|
||||
|
||||
@@ -43,7 +43,7 @@ export class TokenType {
|
||||
export class AuthResolver {
|
||||
constructor(
|
||||
private readonly config: Config,
|
||||
private auth: AuthService,
|
||||
private readonly auth: AuthService,
|
||||
private readonly session: SessionService
|
||||
) {}
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { DynamicModule, Provider, Type } from '@nestjs/common';
|
||||
import { APP_FILTER } from '@nestjs/core';
|
||||
import { DynamicModule, Type } from '@nestjs/common';
|
||||
|
||||
import { GqlModule } from '../graphql.module';
|
||||
import { ExceptionLogger } from '../middleware/exception-logger';
|
||||
import { AuthModule } from './auth';
|
||||
import { DocModule } from './doc';
|
||||
import { SyncModule } from './sync';
|
||||
@@ -39,11 +37,4 @@ switch (SERVER_FLAVOR) {
|
||||
break;
|
||||
}
|
||||
|
||||
const Providers: Provider[] = [
|
||||
{
|
||||
provide: APP_FILTER,
|
||||
useClass: ExceptionLogger,
|
||||
},
|
||||
];
|
||||
|
||||
export { BusinessModules, Providers };
|
||||
export { BusinessModules };
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { equal, ok } from 'node:assert';
|
||||
import { afterEach, beforeEach, describe, test } from 'node:test';
|
||||
|
||||
import { Transformer } from '@napi-rs/image';
|
||||
import type { INestApplication } from '@nestjs/common';
|
||||
import { Test } from '@nestjs/testing';
|
||||
import { hash } from '@node-rs/argon2';
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import test from 'ava';
|
||||
import { Express } from 'express';
|
||||
// @ts-expect-error graphql-upload is not typed
|
||||
import graphqlUploadExpress from 'graphql-upload/graphqlUploadExpress.mjs';
|
||||
@@ -15,83 +15,82 @@ import { AppModule } from '../app';
|
||||
|
||||
const gql = '/graphql';
|
||||
|
||||
describe('AppModule', async () => {
|
||||
let app: INestApplication;
|
||||
let app: INestApplication;
|
||||
|
||||
// cleanup database before each test
|
||||
beforeEach(async () => {
|
||||
const client = new PrismaClient();
|
||||
await client.$connect();
|
||||
await client.user.deleteMany({});
|
||||
await client.user.create({
|
||||
data: {
|
||||
name: 'Alex Yang',
|
||||
email: 'alex.yang@example.org',
|
||||
password: await hash('123456'),
|
||||
},
|
||||
});
|
||||
await client.$disconnect();
|
||||
// cleanup database before each test
|
||||
test.beforeEach(async () => {
|
||||
const client = new PrismaClient();
|
||||
await client.$connect();
|
||||
await client.user.deleteMany({});
|
||||
await client.user.create({
|
||||
data: {
|
||||
name: 'Alex Yang',
|
||||
email: 'alex.yang@example.org',
|
||||
password: await hash('123456'),
|
||||
},
|
||||
});
|
||||
await client.$disconnect();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
const module = await Test.createTestingModule({
|
||||
imports: [AppModule],
|
||||
}).compile();
|
||||
app = module.createNestApplication({
|
||||
cors: true,
|
||||
bodyParser: true,
|
||||
});
|
||||
app.use(
|
||||
graphqlUploadExpress({
|
||||
maxFileSize: 10 * 1024 * 1024,
|
||||
maxFiles: 5,
|
||||
})
|
||||
);
|
||||
await app.init();
|
||||
test.beforeEach(async () => {
|
||||
const module = await Test.createTestingModule({
|
||||
imports: [AppModule],
|
||||
}).compile();
|
||||
app = module.createNestApplication({
|
||||
cors: true,
|
||||
bodyParser: true,
|
||||
});
|
||||
app.use(
|
||||
graphqlUploadExpress({
|
||||
maxFileSize: 10 * 1024 * 1024,
|
||||
maxFiles: 5,
|
||||
})
|
||||
);
|
||||
await app.init();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await app.close();
|
||||
});
|
||||
test.afterEach(async () => {
|
||||
await app.close();
|
||||
});
|
||||
|
||||
await test('should init app', async () => {
|
||||
ok(typeof app === 'object');
|
||||
await request(app.getHttpServer())
|
||||
.post(gql)
|
||||
.send({
|
||||
query: `
|
||||
test('should init app', async () => {
|
||||
ok(typeof app === 'object');
|
||||
await request(app.getHttpServer())
|
||||
.post(gql)
|
||||
.send({
|
||||
query: `
|
||||
query {
|
||||
error
|
||||
}
|
||||
`,
|
||||
})
|
||||
.expect(400);
|
||||
})
|
||||
.expect(400);
|
||||
|
||||
const { token } = await createToken(app);
|
||||
const { token } = await createToken(app);
|
||||
|
||||
await request(app.getHttpServer())
|
||||
.post(gql)
|
||||
.auth(token, { type: 'bearer' })
|
||||
.send({
|
||||
query: `
|
||||
await request(app.getHttpServer())
|
||||
.post(gql)
|
||||
.auth(token, { type: 'bearer' })
|
||||
.send({
|
||||
query: `
|
||||
query {
|
||||
__typename
|
||||
}
|
||||
`,
|
||||
})
|
||||
.expect(200)
|
||||
.expect(res => {
|
||||
ok(res.body.data.__typename === 'Query');
|
||||
});
|
||||
});
|
||||
})
|
||||
.expect(200)
|
||||
.expect(res => {
|
||||
ok(res.body.data.__typename === 'Query');
|
||||
});
|
||||
});
|
||||
|
||||
await test('should find default user', async () => {
|
||||
const { token } = await createToken(app);
|
||||
await request(app.getHttpServer())
|
||||
.post(gql)
|
||||
.auth(token, { type: 'bearer' })
|
||||
.send({
|
||||
query: `
|
||||
test('should find default user', async () => {
|
||||
const { token } = await createToken(app);
|
||||
await request(app.getHttpServer())
|
||||
.post(gql)
|
||||
.auth(token, { type: 'bearer' })
|
||||
.send({
|
||||
query: `
|
||||
query {
|
||||
user(email: "alex.yang@example.org") {
|
||||
email
|
||||
@@ -99,29 +98,29 @@ describe('AppModule', async () => {
|
||||
}
|
||||
}
|
||||
`,
|
||||
})
|
||||
.expect(200)
|
||||
.expect(res => {
|
||||
equal(res.body.data.user.email, 'alex.yang@example.org');
|
||||
});
|
||||
});
|
||||
})
|
||||
.expect(200)
|
||||
.expect(res => {
|
||||
equal(res.body.data.user.email, 'alex.yang@example.org');
|
||||
});
|
||||
});
|
||||
|
||||
await test('should be able to upload avatar', async () => {
|
||||
const { token, id } = await createToken(app);
|
||||
const png = await Transformer.fromRgbaPixels(
|
||||
Buffer.alloc(400 * 400 * 4).fill(255),
|
||||
400,
|
||||
400
|
||||
).png();
|
||||
test('should be able to upload avatar', async () => {
|
||||
const { token, id } = await createToken(app);
|
||||
const png = await Transformer.fromRgbaPixels(
|
||||
Buffer.alloc(400 * 400 * 4).fill(255),
|
||||
400,
|
||||
400
|
||||
).png();
|
||||
|
||||
await request(app.getHttpServer())
|
||||
.post(gql)
|
||||
.auth(token, { type: 'bearer' })
|
||||
.field(
|
||||
'operations',
|
||||
JSON.stringify({
|
||||
name: 'uploadAvatar',
|
||||
query: `mutation uploadAvatar($id: String!, $avatar: Upload!) {
|
||||
await request(app.getHttpServer())
|
||||
.post(gql)
|
||||
.auth(token, { type: 'bearer' })
|
||||
.field(
|
||||
'operations',
|
||||
JSON.stringify({
|
||||
name: 'uploadAvatar',
|
||||
query: `mutation uploadAvatar($id: String!, $avatar: Upload!) {
|
||||
uploadAvatar(id: $id, avatar: $avatar) {
|
||||
id
|
||||
name
|
||||
@@ -130,17 +129,15 @@ describe('AppModule', async () => {
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: { id, avatar: null },
|
||||
})
|
||||
)
|
||||
|
||||
.field('map', JSON.stringify({ '0': ['variables.avatar'] }))
|
||||
.attach('0', png, 'avatar.png')
|
||||
.expect(200)
|
||||
.expect(res => {
|
||||
equal(res.body.data.uploadAvatar.id, id);
|
||||
});
|
||||
});
|
||||
variables: { id, avatar: null },
|
||||
})
|
||||
)
|
||||
.field('map', JSON.stringify({ '0': ['variables.avatar'] }))
|
||||
.attach('0', png, 'avatar.png')
|
||||
.expect(200)
|
||||
.expect(res => {
|
||||
equal(res.body.data.uploadAvatar.id, id);
|
||||
});
|
||||
});
|
||||
|
||||
async function createToken(app: INestApplication<Express>): Promise<{
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
/// <reference types="../global.d.ts" />
|
||||
import { equal } from 'node:assert';
|
||||
import { afterEach, beforeEach, test } from 'node:test';
|
||||
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import test from 'ava';
|
||||
|
||||
import { ConfigModule } from '../config';
|
||||
import { GqlModule } from '../graphql.module';
|
||||
@@ -17,13 +17,13 @@ let auth: AuthService;
|
||||
let module: TestingModule;
|
||||
|
||||
// cleanup database before each test
|
||||
beforeEach(async () => {
|
||||
test.beforeEach(async () => {
|
||||
const client = new PrismaClient();
|
||||
await client.$connect();
|
||||
await client.user.deleteMany({});
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
test.beforeEach(async () => {
|
||||
module = await Test.createTestingModule({
|
||||
imports: [
|
||||
ConfigModule.forRoot({
|
||||
@@ -43,16 +43,17 @@ beforeEach(async () => {
|
||||
auth = module.get(AuthService);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
test.afterEach(async () => {
|
||||
await module.close();
|
||||
});
|
||||
|
||||
test('should be able to register and signIn', async () => {
|
||||
test('should be able to register and signIn', async t => {
|
||||
await auth.signUp('Alex Yang', 'alexyang@example.org', '123456');
|
||||
await auth.signIn('alexyang@example.org', '123456');
|
||||
t.pass();
|
||||
});
|
||||
|
||||
test('should be able to verify', async () => {
|
||||
test('should be able to verify', async t => {
|
||||
await auth.signUp('Alex Yang', 'alexyang@example.org', '123456');
|
||||
await auth.signIn('alexyang@example.org', '123456');
|
||||
const date = new Date();
|
||||
@@ -83,4 +84,5 @@ test('should be able to verify', async () => {
|
||||
equal(claim.emailVerified?.toISOString(), date.toISOString());
|
||||
equal(claim.createdAt.toISOString(), date.toISOString());
|
||||
}
|
||||
t.pass();
|
||||
});
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
import { equal, ok } from 'node:assert';
|
||||
import { beforeEach, test } from 'node:test';
|
||||
import { ok } from 'node:assert';
|
||||
|
||||
import { Test } from '@nestjs/testing';
|
||||
import test from 'ava';
|
||||
|
||||
import { Config, ConfigModule } from '../config';
|
||||
|
||||
let config: Config;
|
||||
beforeEach(async () => {
|
||||
test.beforeEach(async () => {
|
||||
const module = await Test.createTestingModule({
|
||||
imports: [ConfigModule.forRoot()],
|
||||
}).compile();
|
||||
config = module.get(Config);
|
||||
});
|
||||
|
||||
test('should be able to get config', () => {
|
||||
ok(typeof config.host === 'string');
|
||||
equal(config.env, 'test');
|
||||
test('should be able to get config', t => {
|
||||
t.true(typeof config.host === 'string');
|
||||
t.is(config.env, 'test');
|
||||
});
|
||||
|
||||
test('should be able to override config', async () => {
|
||||
test('should be able to override config', async t => {
|
||||
const module = await Test.createTestingModule({
|
||||
imports: [
|
||||
ConfigModule.forRoot({
|
||||
@@ -29,4 +29,5 @@ test('should be able to override config', async () => {
|
||||
const config = module.get(Config);
|
||||
|
||||
ok(config.host, 'testing');
|
||||
t.pass();
|
||||
});
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { deepEqual, equal, ok } from 'node:assert';
|
||||
import { afterEach, beforeEach, mock, test } from 'node:test';
|
||||
import { mock } from 'node:test';
|
||||
|
||||
import { INestApplication } from '@nestjs/common';
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import test from 'ava';
|
||||
import { register } from 'prom-client';
|
||||
import * as Sinon from 'sinon';
|
||||
import { Doc as YDoc, encodeStateAsUpdate } from 'yjs';
|
||||
@@ -24,135 +25,137 @@ const createModule = () => {
|
||||
}).compile();
|
||||
};
|
||||
|
||||
test('Doc Module', async t => {
|
||||
let app: INestApplication;
|
||||
let m: TestingModule;
|
||||
let timer: Sinon.SinonFakeTimers;
|
||||
let app: INestApplication;
|
||||
let m: TestingModule;
|
||||
let timer: Sinon.SinonFakeTimers;
|
||||
|
||||
// cleanup database before each test
|
||||
beforeEach(async () => {
|
||||
timer = Sinon.useFakeTimers({
|
||||
toFake: ['setInterval'],
|
||||
});
|
||||
await flushDB();
|
||||
m = await createModule();
|
||||
app = m.createNestApplication();
|
||||
app.enableShutdownHooks();
|
||||
await app.init();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await app.close();
|
||||
timer.restore();
|
||||
});
|
||||
|
||||
await t.test('should setup update poll interval', async () => {
|
||||
register.clear();
|
||||
const m = await createModule();
|
||||
const manager = m.get(DocManager);
|
||||
const fake = mock.method(manager, 'setup');
|
||||
|
||||
await m.createNestApplication().init();
|
||||
|
||||
equal(fake.mock.callCount(), 1);
|
||||
// @ts-expect-error private member
|
||||
ok(manager.job);
|
||||
});
|
||||
|
||||
await t.test('should be able to stop poll', async () => {
|
||||
const manager = m.get(DocManager);
|
||||
const fake = mock.method(manager, 'destroy');
|
||||
|
||||
await app.close();
|
||||
|
||||
equal(fake.mock.callCount(), 1);
|
||||
// @ts-expect-error private member
|
||||
equal(manager.job, null);
|
||||
});
|
||||
|
||||
await t.test('should poll when intervel due', async () => {
|
||||
const manager = m.get(DocManager);
|
||||
const interval = m.get(Config).doc.manager.updatePollInterval;
|
||||
|
||||
let resolve: any;
|
||||
const fake = mock.method(manager, 'apply', () => {
|
||||
return new Promise(_resolve => {
|
||||
resolve = _resolve;
|
||||
});
|
||||
});
|
||||
|
||||
timer.tick(interval);
|
||||
equal(fake.mock.callCount(), 1);
|
||||
|
||||
// busy
|
||||
timer.tick(interval);
|
||||
// @ts-expect-error private member
|
||||
equal(manager.busy, true);
|
||||
equal(fake.mock.callCount(), 1);
|
||||
|
||||
resolve();
|
||||
await timer.tickAsync(1);
|
||||
|
||||
// @ts-expect-error private member
|
||||
equal(manager.busy, false);
|
||||
timer.tick(interval);
|
||||
equal(fake.mock.callCount(), 2);
|
||||
});
|
||||
|
||||
await t.test('should merge update when intervel due', async () => {
|
||||
const db = m.get(PrismaService);
|
||||
const manager = m.get(DocManager);
|
||||
|
||||
const doc = new YDoc();
|
||||
const text = doc.getText('content');
|
||||
text.insert(0, 'hello');
|
||||
const update = encodeStateAsUpdate(doc);
|
||||
|
||||
const ws = await db.workspace.create({
|
||||
data: {
|
||||
id: '1',
|
||||
public: false,
|
||||
},
|
||||
});
|
||||
|
||||
await db.update.createMany({
|
||||
data: [
|
||||
{
|
||||
id: '1',
|
||||
workspaceId: '1',
|
||||
blob: Buffer.from([0, 0]),
|
||||
},
|
||||
{
|
||||
id: '1',
|
||||
workspaceId: '1',
|
||||
blob: Buffer.from(update),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
await manager.apply();
|
||||
|
||||
deepEqual(await manager.getLatestUpdate(ws.id, '1'), update);
|
||||
|
||||
let appendUpdate = Buffer.from([]);
|
||||
doc.on('update', update => {
|
||||
appendUpdate = Buffer.from(update);
|
||||
});
|
||||
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)
|
||||
);
|
||||
// cleanup database before each test
|
||||
test.beforeEach(async () => {
|
||||
timer = Sinon.useFakeTimers({
|
||||
toFake: ['setInterval'],
|
||||
});
|
||||
await flushDB();
|
||||
m = await createModule();
|
||||
app = m.createNestApplication();
|
||||
app.enableShutdownHooks();
|
||||
await app.init();
|
||||
});
|
||||
|
||||
test.afterEach(async () => {
|
||||
await app.close();
|
||||
timer.restore();
|
||||
});
|
||||
|
||||
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');
|
||||
|
||||
await m.createNestApplication().init();
|
||||
|
||||
equal(fake.mock.callCount(), 1);
|
||||
// @ts-expect-error private member
|
||||
ok(manager.job);
|
||||
t.pass();
|
||||
});
|
||||
|
||||
test('should be able to stop poll', async t => {
|
||||
const manager = m.get(DocManager);
|
||||
const fake = mock.method(manager, 'destroy');
|
||||
|
||||
await app.close();
|
||||
|
||||
equal(fake.mock.callCount(), 1);
|
||||
// @ts-expect-error private member
|
||||
equal(manager.job, null);
|
||||
t.pass();
|
||||
});
|
||||
|
||||
test('should poll when intervel due', async t => {
|
||||
const manager = m.get(DocManager);
|
||||
const interval = m.get(Config).doc.manager.updatePollInterval;
|
||||
|
||||
let resolve: any;
|
||||
const fake = mock.method(manager, 'apply', () => {
|
||||
return new Promise(_resolve => {
|
||||
resolve = _resolve;
|
||||
});
|
||||
});
|
||||
|
||||
timer.tick(interval);
|
||||
equal(fake.mock.callCount(), 1);
|
||||
|
||||
// busy
|
||||
timer.tick(interval);
|
||||
// @ts-expect-error private member
|
||||
equal(manager.busy, true);
|
||||
equal(fake.mock.callCount(), 1);
|
||||
|
||||
resolve();
|
||||
await timer.tickAsync(1);
|
||||
|
||||
// @ts-expect-error private member
|
||||
equal(manager.busy, false);
|
||||
timer.tick(interval);
|
||||
equal(fake.mock.callCount(), 2);
|
||||
t.pass();
|
||||
});
|
||||
|
||||
test('should merge update when intervel due', async t => {
|
||||
const db = m.get(PrismaService);
|
||||
const manager = m.get(DocManager);
|
||||
|
||||
const doc = new YDoc();
|
||||
const text = doc.getText('content');
|
||||
text.insert(0, 'hello');
|
||||
const update = encodeStateAsUpdate(doc);
|
||||
|
||||
const ws = await db.workspace.create({
|
||||
data: {
|
||||
id: '1',
|
||||
public: false,
|
||||
},
|
||||
});
|
||||
|
||||
await db.update.createMany({
|
||||
data: [
|
||||
{
|
||||
id: '1',
|
||||
workspaceId: '1',
|
||||
blob: Buffer.from([0, 0]),
|
||||
},
|
||||
{
|
||||
id: '1',
|
||||
workspaceId: '1',
|
||||
blob: Buffer.from(update),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
await manager.apply();
|
||||
|
||||
deepEqual(await manager.getLatestUpdate(ws.id, '1'), update);
|
||||
|
||||
let appendUpdate = Buffer.from([]);
|
||||
doc.on('update', update => {
|
||||
appendUpdate = Buffer.from(update);
|
||||
});
|
||||
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();
|
||||
});
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { ok } from 'node:assert';
|
||||
import { afterEach, beforeEach, describe, it } from 'node:test';
|
||||
|
||||
import type { INestApplication } from '@nestjs/common';
|
||||
import { Test } from '@nestjs/testing';
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import test from 'ava';
|
||||
// @ts-expect-error graphql-upload is not typed
|
||||
import graphqlUploadExpress from 'graphql-upload/graphqlUploadExpress.mjs';
|
||||
|
||||
@@ -11,76 +11,75 @@ import { AppModule } from '../app';
|
||||
import { MailService } from '../modules/auth/mailer';
|
||||
import { createWorkspace, getInviteInfo, inviteUser, signUp } from './utils';
|
||||
|
||||
describe('Mail Module', () => {
|
||||
let app: INestApplication;
|
||||
let app: INestApplication;
|
||||
|
||||
const client = new PrismaClient();
|
||||
const client = new PrismaClient();
|
||||
|
||||
let mail: MailService;
|
||||
let mail: MailService;
|
||||
|
||||
// cleanup database before each test
|
||||
beforeEach(async () => {
|
||||
await client.$connect();
|
||||
await client.user.deleteMany({});
|
||||
await client.snapshot.deleteMany({});
|
||||
await client.update.deleteMany({});
|
||||
await client.workspace.deleteMany({});
|
||||
await client.$disconnect();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
const module = await Test.createTestingModule({
|
||||
imports: [AppModule],
|
||||
}).compile();
|
||||
app = module.createNestApplication();
|
||||
app.use(
|
||||
graphqlUploadExpress({
|
||||
maxFileSize: 10 * 1024 * 1024,
|
||||
maxFiles: 5,
|
||||
})
|
||||
);
|
||||
await app.init();
|
||||
|
||||
mail = module.get(MailService);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await app.close();
|
||||
});
|
||||
|
||||
it('should send invite email', async () => {
|
||||
if (mail.hasConfigured()) {
|
||||
const u1 = await signUp(app, 'u1', 'u1@affine.pro', '1');
|
||||
const u2 = await signUp(app, 'u2', 'u2@affine.pro', '1');
|
||||
|
||||
const workspace = await createWorkspace(app, u1.token.token);
|
||||
const inviteId = await inviteUser(
|
||||
app,
|
||||
u1.token.token,
|
||||
workspace.id,
|
||||
u2.email,
|
||||
'Admin'
|
||||
);
|
||||
|
||||
const inviteInfo = await getInviteInfo(app, u1.token.token, inviteId);
|
||||
|
||||
const resp = await mail.sendInviteEmail(
|
||||
'production@toeverything.info',
|
||||
inviteId,
|
||||
{
|
||||
workspace: {
|
||||
id: inviteInfo.workspace.id,
|
||||
name: inviteInfo.workspace.name,
|
||||
avatar: '',
|
||||
},
|
||||
user: {
|
||||
avatar: inviteInfo.user?.avatarUrl || '',
|
||||
name: inviteInfo.user?.name || '',
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
ok(resp.accepted.length === 1, 'failed to send invite email');
|
||||
}
|
||||
});
|
||||
// cleanup database before each test
|
||||
test.beforeEach(async () => {
|
||||
await client.$connect();
|
||||
await client.user.deleteMany({});
|
||||
await client.snapshot.deleteMany({});
|
||||
await client.update.deleteMany({});
|
||||
await client.workspace.deleteMany({});
|
||||
await client.$disconnect();
|
||||
});
|
||||
|
||||
test.beforeEach(async () => {
|
||||
const module = await Test.createTestingModule({
|
||||
imports: [AppModule],
|
||||
}).compile();
|
||||
app = module.createNestApplication();
|
||||
app.use(
|
||||
graphqlUploadExpress({
|
||||
maxFileSize: 10 * 1024 * 1024,
|
||||
maxFiles: 5,
|
||||
})
|
||||
);
|
||||
await app.init();
|
||||
|
||||
mail = module.get(MailService);
|
||||
});
|
||||
|
||||
test.afterEach(async () => {
|
||||
await app.close();
|
||||
});
|
||||
|
||||
test('should send invite email', async t => {
|
||||
if (mail.hasConfigured()) {
|
||||
const u1 = await signUp(app, 'u1', 'u1@affine.pro', '1');
|
||||
const u2 = await signUp(app, 'u2', 'u2@affine.pro', '1');
|
||||
|
||||
const workspace = await createWorkspace(app, u1.token.token);
|
||||
const inviteId = await inviteUser(
|
||||
app,
|
||||
u1.token.token,
|
||||
workspace.id,
|
||||
u2.email,
|
||||
'Admin'
|
||||
);
|
||||
|
||||
const inviteInfo = await getInviteInfo(app, u1.token.token, inviteId);
|
||||
|
||||
const resp = await mail.sendInviteEmail(
|
||||
'production@toeverything.info',
|
||||
inviteId,
|
||||
{
|
||||
workspace: {
|
||||
id: inviteInfo.workspace.id,
|
||||
name: inviteInfo.workspace.name,
|
||||
avatar: '',
|
||||
},
|
||||
user: {
|
||||
avatar: inviteInfo.user?.avatarUrl || '',
|
||||
name: inviteInfo.user?.name || '',
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
ok(resp.accepted.length === 1, 'failed to send invite email');
|
||||
}
|
||||
t.pass();
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ok } from 'node:assert';
|
||||
import { afterEach, beforeEach, test } from 'node:test';
|
||||
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import test from 'ava';
|
||||
import { register } from 'prom-client';
|
||||
|
||||
import { MetricsModule } from '../metrics';
|
||||
@@ -11,7 +11,7 @@ import { PrismaModule } from '../prisma';
|
||||
let metrics: Metrics;
|
||||
let module: TestingModule;
|
||||
|
||||
beforeEach(async () => {
|
||||
test.beforeEach(async () => {
|
||||
module = await Test.createTestingModule({
|
||||
imports: [MetricsModule, PrismaModule],
|
||||
}).compile();
|
||||
@@ -19,11 +19,11 @@ beforeEach(async () => {
|
||||
metrics = module.get(Metrics);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
test.afterEach(async () => {
|
||||
await module.close();
|
||||
});
|
||||
|
||||
test('should be able to increment counter', async () => {
|
||||
test('should be able to increment counter', async t => {
|
||||
metrics.socketIOEventCounter(1, { event: 'client-handshake' });
|
||||
const socketIOCounterMetric =
|
||||
await register.getSingleMetric('socket_io_counter');
|
||||
@@ -33,9 +33,10 @@ test('should be able to increment counter', async () => {
|
||||
JSON.stringify((await socketIOCounterMetric.get()).values) ===
|
||||
'[{"value":1,"labels":{"event":"client-handshake"}}]'
|
||||
);
|
||||
t.pass();
|
||||
});
|
||||
|
||||
test('should be able to timer', async () => {
|
||||
test('should be able to timer', async t => {
|
||||
let minimum: number;
|
||||
{
|
||||
const endTimer = metrics.socketIOEventTimer({ event: 'client-handshake' });
|
||||
@@ -76,4 +77,5 @@ test('should be able to timer', async () => {
|
||||
);
|
||||
}
|
||||
}
|
||||
t.pass();
|
||||
});
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
/// <reference types="../global.d.ts" />
|
||||
import { equal } from 'node:assert';
|
||||
import { afterEach, beforeEach, test } from 'node:test';
|
||||
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import test from 'ava';
|
||||
|
||||
import { ConfigModule } from '../config';
|
||||
import { SessionModule, SessionService } from '../session';
|
||||
@@ -12,31 +12,33 @@ let session: SessionService;
|
||||
let module: TestingModule;
|
||||
|
||||
// cleanup database before each test
|
||||
beforeEach(async () => {
|
||||
test.beforeEach(async () => {
|
||||
const client = new PrismaClient();
|
||||
await client.$connect();
|
||||
await client.user.deleteMany({});
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
test.beforeEach(async () => {
|
||||
module = await Test.createTestingModule({
|
||||
imports: [ConfigModule.forRoot(), SessionModule],
|
||||
}).compile();
|
||||
session = module.get(SessionService);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
test.afterEach(async () => {
|
||||
await module.close();
|
||||
});
|
||||
|
||||
test('should be able to set session', async () => {
|
||||
test('should be able to set session', async t => {
|
||||
await session.set('test', 'value');
|
||||
equal(await session.get('test'), 'value');
|
||||
t.pass();
|
||||
});
|
||||
|
||||
test('should be expired by ttl', async () => {
|
||||
test('should be expired by ttl', async t => {
|
||||
await session.set('test', 'value', 100);
|
||||
equal(await session.get('test'), 'value');
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
equal(await session.get('test'), undefined);
|
||||
t.pass();
|
||||
});
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { ok, rejects } from 'node:assert';
|
||||
import { afterEach, beforeEach, describe, it } from 'node:test';
|
||||
|
||||
import type { INestApplication } from '@nestjs/common';
|
||||
import { Test } from '@nestjs/testing';
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import test from 'ava';
|
||||
// @ts-expect-error graphql-upload is not typed
|
||||
import graphqlUploadExpress from 'graphql-upload/graphqlUploadExpress.mjs';
|
||||
import request from 'supertest';
|
||||
@@ -11,66 +11,67 @@ import request from 'supertest';
|
||||
import { AppModule } from '../app';
|
||||
import { currentUser, signUp } from './utils';
|
||||
|
||||
describe('User Module', () => {
|
||||
let app: INestApplication;
|
||||
let app: INestApplication;
|
||||
|
||||
// cleanup database before each test
|
||||
beforeEach(async () => {
|
||||
const client = new PrismaClient();
|
||||
await client.$connect();
|
||||
await client.user.deleteMany({});
|
||||
await client.$disconnect();
|
||||
});
|
||||
// cleanup database before each test
|
||||
test.beforeEach(async () => {
|
||||
const client = new PrismaClient();
|
||||
await client.$connect();
|
||||
await client.user.deleteMany({});
|
||||
await client.$disconnect();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
const module = await Test.createTestingModule({
|
||||
imports: [AppModule],
|
||||
}).compile();
|
||||
app = module.createNestApplication();
|
||||
app.use(
|
||||
graphqlUploadExpress({
|
||||
maxFileSize: 10 * 1024 * 1024,
|
||||
maxFiles: 5,
|
||||
})
|
||||
);
|
||||
await app.init();
|
||||
});
|
||||
test.beforeEach(async () => {
|
||||
const module = await Test.createTestingModule({
|
||||
imports: [AppModule],
|
||||
}).compile();
|
||||
app = module.createNestApplication();
|
||||
app.use(
|
||||
graphqlUploadExpress({
|
||||
maxFileSize: 10 * 1024 * 1024,
|
||||
maxFiles: 5,
|
||||
})
|
||||
);
|
||||
await app.init();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await app.close();
|
||||
});
|
||||
test.afterEach(async () => {
|
||||
await app.close();
|
||||
});
|
||||
|
||||
it('should register a user', async () => {
|
||||
const user = await signUp(app, 'u1', 'u1@affine.pro', '123456');
|
||||
ok(typeof user.id === 'string', 'user.id is not a string');
|
||||
ok(user.name === 'u1', 'user.name is not valid');
|
||||
ok(user.email === 'u1@affine.pro', 'user.email is not valid');
|
||||
});
|
||||
test('should register a user', async t => {
|
||||
const user = await signUp(app, 'u1', 'u1@affine.pro', '123456');
|
||||
ok(typeof user.id === 'string', 'user.id is not a string');
|
||||
ok(user.name === 'u1', 'user.name is not valid');
|
||||
ok(user.email === 'u1@affine.pro', 'user.email is not valid');
|
||||
t.pass();
|
||||
});
|
||||
|
||||
it('should get current user', async () => {
|
||||
const user = await signUp(app, 'u1', 'u1@affine.pro', '123456');
|
||||
const currUser = await currentUser(app, user.token.token);
|
||||
ok(currUser.id === user.id, 'user.id is not valid');
|
||||
ok(currUser.name === user.name, 'user.name is not valid');
|
||||
ok(currUser.email === user.email, 'user.email is not valid');
|
||||
ok(currUser.hasPassword, 'currUser.hasPassword is not valid');
|
||||
});
|
||||
test('should get current user', async t => {
|
||||
const user = await signUp(app, 'u1', 'u1@affine.pro', '123456');
|
||||
const currUser = await currentUser(app, user.token.token);
|
||||
ok(currUser.id === user.id, 'user.id is not valid');
|
||||
ok(currUser.name === user.name, 'user.name is not valid');
|
||||
ok(currUser.email === user.email, 'user.email is not valid');
|
||||
ok(currUser.hasPassword, 'currUser.hasPassword is not valid');
|
||||
t.pass();
|
||||
});
|
||||
|
||||
it('should be able to delete user', async () => {
|
||||
const user = await signUp(app, 'u1', 'u1@affine.pro', '123456');
|
||||
await request(app.getHttpServer())
|
||||
.post('/graphql')
|
||||
.auth(user.token.token, { type: 'bearer' })
|
||||
.send({
|
||||
query: `
|
||||
test('should be able to delete user', async t => {
|
||||
const user = await signUp(app, 'u1', 'u1@affine.pro', '123456');
|
||||
await request(app.getHttpServer())
|
||||
.post('/graphql')
|
||||
.auth(user.token.token, { type: 'bearer' })
|
||||
.send({
|
||||
query: `
|
||||
mutation {
|
||||
deleteAccount {
|
||||
success
|
||||
}
|
||||
}
|
||||
`,
|
||||
})
|
||||
.expect(200);
|
||||
rejects(currentUser(app, user.token.token));
|
||||
});
|
||||
})
|
||||
.expect(200);
|
||||
await rejects(currentUser(app, user.token.token));
|
||||
t.pass();
|
||||
});
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { deepEqual, ok } from 'node:assert';
|
||||
import { afterEach, beforeEach, describe, it } from 'node:test';
|
||||
|
||||
import type { INestApplication } from '@nestjs/common';
|
||||
import { Test } from '@nestjs/testing';
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import test from 'ava';
|
||||
// @ts-expect-error graphql-upload is not typed
|
||||
import graphqlUploadExpress from 'graphql-upload/graphqlUploadExpress.mjs';
|
||||
import request from 'supertest';
|
||||
@@ -18,116 +18,118 @@ import {
|
||||
signUp,
|
||||
} from './utils';
|
||||
|
||||
describe('Workspace Module - Blobs', () => {
|
||||
let app: INestApplication;
|
||||
let app: INestApplication;
|
||||
|
||||
const client = new PrismaClient();
|
||||
const client = new PrismaClient();
|
||||
|
||||
// cleanup database before each test
|
||||
beforeEach(async () => {
|
||||
await client.$connect();
|
||||
await client.user.deleteMany({});
|
||||
await client.snapshot.deleteMany({});
|
||||
await client.update.deleteMany({});
|
||||
await client.workspace.deleteMany({});
|
||||
await client.$disconnect();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
const module = await Test.createTestingModule({
|
||||
imports: [AppModule],
|
||||
}).compile();
|
||||
app = module.createNestApplication();
|
||||
app.use(
|
||||
graphqlUploadExpress({
|
||||
maxFileSize: 10 * 1024 * 1024,
|
||||
maxFiles: 5,
|
||||
})
|
||||
);
|
||||
await app.init();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await app.close();
|
||||
});
|
||||
|
||||
it('should set blobs', async () => {
|
||||
const u1 = await signUp(app, 'u1', 'u1@affine.pro', '1');
|
||||
|
||||
const workspace = await createWorkspace(app, u1.token.token);
|
||||
|
||||
const buffer1 = Buffer.from([0, 0]);
|
||||
const hash1 = await setBlob(app, u1.token.token, workspace.id, buffer1);
|
||||
const buffer2 = Buffer.from([0, 1]);
|
||||
const hash2 = await setBlob(app, u1.token.token, workspace.id, buffer2);
|
||||
|
||||
const server = app.getHttpServer();
|
||||
|
||||
const response1 = await request(server)
|
||||
.get(`/api/workspaces/${workspace.id}/blobs/${hash1}`)
|
||||
.auth(u1.token.token, { type: 'bearer' })
|
||||
.buffer();
|
||||
|
||||
deepEqual(response1.body, buffer1, 'failed to get blob');
|
||||
|
||||
const response2 = await request(server)
|
||||
.get(`/api/workspaces/${workspace.id}/blobs/${hash2}`)
|
||||
.auth(u1.token.token, { type: 'bearer' })
|
||||
.buffer();
|
||||
|
||||
deepEqual(response2.body, buffer2, 'failed to get blob');
|
||||
});
|
||||
|
||||
it('should list blobs', async () => {
|
||||
const u1 = await signUp(app, 'u1', 'u1@affine.pro', '1');
|
||||
|
||||
const workspace = await createWorkspace(app, u1.token.token);
|
||||
const blobs = await listBlobs(app, u1.token.token, workspace.id);
|
||||
ok(blobs.length === 0, 'failed to list blobs');
|
||||
|
||||
const buffer1 = Buffer.from([0, 0]);
|
||||
const hash1 = await setBlob(app, u1.token.token, workspace.id, buffer1);
|
||||
const buffer2 = Buffer.from([0, 1]);
|
||||
const hash2 = await setBlob(app, u1.token.token, workspace.id, buffer2);
|
||||
|
||||
const ret = await listBlobs(app, u1.token.token, workspace.id);
|
||||
ok(ret.length === 2, 'failed to list blobs');
|
||||
ok(ret[0] === hash1, 'failed to list blobs');
|
||||
ok(ret[1] === hash2, 'failed to list blobs');
|
||||
});
|
||||
|
||||
it('should calc blobs size', async () => {
|
||||
const u1 = await signUp(app, 'u1', 'u1@affine.pro', '1');
|
||||
|
||||
const workspace = await createWorkspace(app, u1.token.token);
|
||||
|
||||
const buffer1 = Buffer.from([0, 0]);
|
||||
await setBlob(app, u1.token.token, workspace.id, buffer1);
|
||||
const buffer2 = Buffer.from([0, 1]);
|
||||
await setBlob(app, u1.token.token, workspace.id, buffer2);
|
||||
|
||||
const size = await collectBlobSizes(app, u1.token.token, workspace.id);
|
||||
ok(size === 4, 'failed to collect blob sizes');
|
||||
});
|
||||
|
||||
it('should calc all blobs size', async () => {
|
||||
const u1 = await signUp(app, 'u1', 'u1@affine.pro', '1');
|
||||
|
||||
const workspace1 = await createWorkspace(app, u1.token.token);
|
||||
|
||||
const buffer1 = Buffer.from([0, 0]);
|
||||
await setBlob(app, u1.token.token, workspace1.id, buffer1);
|
||||
const buffer2 = Buffer.from([0, 1]);
|
||||
await setBlob(app, u1.token.token, workspace1.id, buffer2);
|
||||
|
||||
const workspace2 = await createWorkspace(app, u1.token.token);
|
||||
|
||||
const buffer3 = Buffer.from([0, 0]);
|
||||
await setBlob(app, u1.token.token, workspace2.id, buffer3);
|
||||
const buffer4 = Buffer.from([0, 1]);
|
||||
await setBlob(app, u1.token.token, workspace2.id, buffer4);
|
||||
|
||||
const size = await collectAllBlobSizes(app, u1.token.token);
|
||||
ok(size === 8, 'failed to collect all blob sizes');
|
||||
});
|
||||
// cleanup database before each test
|
||||
test.beforeEach(async () => {
|
||||
await client.$connect();
|
||||
await client.user.deleteMany({});
|
||||
await client.snapshot.deleteMany({});
|
||||
await client.update.deleteMany({});
|
||||
await client.workspace.deleteMany({});
|
||||
await client.$disconnect();
|
||||
});
|
||||
|
||||
test.beforeEach(async () => {
|
||||
const module = await Test.createTestingModule({
|
||||
imports: [AppModule],
|
||||
}).compile();
|
||||
app = module.createNestApplication();
|
||||
app.use(
|
||||
graphqlUploadExpress({
|
||||
maxFileSize: 10 * 1024 * 1024,
|
||||
maxFiles: 5,
|
||||
})
|
||||
);
|
||||
await app.init();
|
||||
});
|
||||
|
||||
test.afterEach(async () => {
|
||||
await app.close();
|
||||
});
|
||||
|
||||
test('should set blobs', async t => {
|
||||
const u1 = await signUp(app, 'u1', 'u1@affine.pro', '1');
|
||||
|
||||
const workspace = await createWorkspace(app, u1.token.token);
|
||||
|
||||
const buffer1 = Buffer.from([0, 0]);
|
||||
const hash1 = await setBlob(app, u1.token.token, workspace.id, buffer1);
|
||||
const buffer2 = Buffer.from([0, 1]);
|
||||
const hash2 = await setBlob(app, u1.token.token, workspace.id, buffer2);
|
||||
|
||||
const server = app.getHttpServer();
|
||||
|
||||
const response1 = await request(server)
|
||||
.get(`/api/workspaces/${workspace.id}/blobs/${hash1}`)
|
||||
.auth(u1.token.token, { type: 'bearer' })
|
||||
.buffer();
|
||||
|
||||
deepEqual(response1.body, buffer1, 'failed to get blob');
|
||||
|
||||
const response2 = await request(server)
|
||||
.get(`/api/workspaces/${workspace.id}/blobs/${hash2}`)
|
||||
.auth(u1.token.token, { type: 'bearer' })
|
||||
.buffer();
|
||||
|
||||
deepEqual(response2.body, buffer2, 'failed to get blob');
|
||||
t.pass();
|
||||
});
|
||||
|
||||
test('should list blobs', async t => {
|
||||
const u1 = await signUp(app, 'u1', 'u1@affine.pro', '1');
|
||||
|
||||
const workspace = await createWorkspace(app, u1.token.token);
|
||||
const blobs = await listBlobs(app, u1.token.token, workspace.id);
|
||||
ok(blobs.length === 0, 'failed to list blobs');
|
||||
|
||||
const buffer1 = Buffer.from([0, 0]);
|
||||
const hash1 = await setBlob(app, u1.token.token, workspace.id, buffer1);
|
||||
const buffer2 = Buffer.from([0, 1]);
|
||||
const hash2 = await setBlob(app, u1.token.token, workspace.id, buffer2);
|
||||
|
||||
const ret = await listBlobs(app, u1.token.token, workspace.id);
|
||||
ok(ret.length === 2, 'failed to list blobs');
|
||||
ok(ret[0] === hash1, 'failed to list blobs');
|
||||
ok(ret[1] === hash2, 'failed to list blobs');
|
||||
t.pass();
|
||||
});
|
||||
|
||||
test('should calc blobs size', async t => {
|
||||
const u1 = await signUp(app, 'u1', 'u1@affine.pro', '1');
|
||||
|
||||
const workspace = await createWorkspace(app, u1.token.token);
|
||||
|
||||
const buffer1 = Buffer.from([0, 0]);
|
||||
await setBlob(app, u1.token.token, workspace.id, buffer1);
|
||||
const buffer2 = Buffer.from([0, 1]);
|
||||
await setBlob(app, u1.token.token, workspace.id, buffer2);
|
||||
|
||||
const size = await collectBlobSizes(app, u1.token.token, workspace.id);
|
||||
ok(size === 4, 'failed to collect blob sizes');
|
||||
t.pass();
|
||||
});
|
||||
|
||||
test('should calc all blobs size', async t => {
|
||||
const u1 = await signUp(app, 'u1', 'u1@affine.pro', '1');
|
||||
|
||||
const workspace1 = await createWorkspace(app, u1.token.token);
|
||||
|
||||
const buffer1 = Buffer.from([0, 0]);
|
||||
await setBlob(app, u1.token.token, workspace1.id, buffer1);
|
||||
const buffer2 = Buffer.from([0, 1]);
|
||||
await setBlob(app, u1.token.token, workspace1.id, buffer2);
|
||||
|
||||
const workspace2 = await createWorkspace(app, u1.token.token);
|
||||
|
||||
const buffer3 = Buffer.from([0, 0]);
|
||||
await setBlob(app, u1.token.token, workspace2.id, buffer3);
|
||||
const buffer4 = Buffer.from([0, 1]);
|
||||
await setBlob(app, u1.token.token, workspace2.id, buffer4);
|
||||
|
||||
const size = await collectAllBlobSizes(app, u1.token.token);
|
||||
ok(size === 8, 'failed to collect all blob sizes');
|
||||
t.pass();
|
||||
});
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { ok } from 'node:assert';
|
||||
import { afterEach, beforeEach, describe, it } from 'node:test';
|
||||
|
||||
import type { INestApplication } from '@nestjs/common';
|
||||
import { Test } from '@nestjs/testing';
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import test from 'ava';
|
||||
// @ts-expect-error graphql-upload is not typed
|
||||
import graphqlUploadExpress from 'graphql-upload/graphqlUploadExpress.mjs';
|
||||
|
||||
@@ -21,169 +21,168 @@ import {
|
||||
signUp,
|
||||
} from './utils';
|
||||
|
||||
describe('Workspace Module - invite', () => {
|
||||
let app: INestApplication;
|
||||
let app: INestApplication;
|
||||
|
||||
const client = new PrismaClient();
|
||||
const client = new PrismaClient();
|
||||
|
||||
let auth: AuthService;
|
||||
let mail: MailService;
|
||||
let auth: AuthService;
|
||||
let mail: MailService;
|
||||
|
||||
// cleanup database before each test
|
||||
beforeEach(async () => {
|
||||
await client.$connect();
|
||||
await client.user.deleteMany({});
|
||||
await client.snapshot.deleteMany({});
|
||||
await client.update.deleteMany({});
|
||||
await client.workspace.deleteMany({});
|
||||
await client.$disconnect();
|
||||
});
|
||||
// cleanup database before each test
|
||||
test.beforeEach(async () => {
|
||||
await client.$connect();
|
||||
await client.user.deleteMany({});
|
||||
await client.snapshot.deleteMany({});
|
||||
await client.update.deleteMany({});
|
||||
await client.workspace.deleteMany({});
|
||||
await client.$disconnect();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
const module = await Test.createTestingModule({
|
||||
imports: [AppModule],
|
||||
}).compile();
|
||||
app = module.createNestApplication();
|
||||
app.use(
|
||||
graphqlUploadExpress({
|
||||
maxFileSize: 10 * 1024 * 1024,
|
||||
maxFiles: 5,
|
||||
})
|
||||
);
|
||||
await app.init();
|
||||
test.beforeEach(async () => {
|
||||
const module = await Test.createTestingModule({
|
||||
imports: [AppModule],
|
||||
}).compile();
|
||||
app = module.createNestApplication();
|
||||
app.use(
|
||||
graphqlUploadExpress({
|
||||
maxFileSize: 10 * 1024 * 1024,
|
||||
maxFiles: 5,
|
||||
})
|
||||
);
|
||||
await app.init();
|
||||
|
||||
auth = module.get(AuthService);
|
||||
mail = module.get(MailService);
|
||||
});
|
||||
auth = module.get(AuthService);
|
||||
mail = module.get(MailService);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await app.close();
|
||||
});
|
||||
test.afterEach(async () => {
|
||||
await app.close();
|
||||
});
|
||||
|
||||
it('should invite a user', async () => {
|
||||
test('should invite a user', async t => {
|
||||
const u1 = await signUp(app, 'u1', 'u1@affine.pro', '1');
|
||||
const u2 = await signUp(app, 'u2', 'u2@affine.pro', '1');
|
||||
|
||||
const workspace = await createWorkspace(app, u1.token.token);
|
||||
|
||||
const invite = await inviteUser(
|
||||
app,
|
||||
u1.token.token,
|
||||
workspace.id,
|
||||
u2.email,
|
||||
'Admin'
|
||||
);
|
||||
ok(!!invite, 'failed to invite user');
|
||||
t.pass();
|
||||
});
|
||||
|
||||
test('should accept an invite', async t => {
|
||||
const u1 = await signUp(app, 'u1', 'u1@affine.pro', '1');
|
||||
const u2 = await signUp(app, 'u2', 'u2@affine.pro', '1');
|
||||
|
||||
const workspace = await createWorkspace(app, u1.token.token);
|
||||
await inviteUser(app, u1.token.token, workspace.id, u2.email, 'Admin');
|
||||
|
||||
const accept = await acceptInvite(app, u2.token.token, workspace.id);
|
||||
ok(accept === true, 'failed to accept invite');
|
||||
|
||||
const currWorkspace = await getWorkspace(app, u1.token.token, workspace.id);
|
||||
const currMember = currWorkspace.members.find(u => u.email === u2.email);
|
||||
ok(currMember !== undefined, 'failed to invite user');
|
||||
ok(currMember.id === u2.id, 'failed to invite user');
|
||||
ok(!currMember.accepted, 'failed to invite user');
|
||||
t.pass();
|
||||
});
|
||||
|
||||
test('should leave a workspace', async t => {
|
||||
const u1 = await signUp(app, 'u1', 'u1@affine.pro', '1');
|
||||
const u2 = await signUp(app, 'u2', 'u2@affine.pro', '1');
|
||||
|
||||
const workspace = await createWorkspace(app, u1.token.token);
|
||||
await inviteUser(app, u1.token.token, workspace.id, u2.email, 'Admin');
|
||||
await acceptInvite(app, u2.token.token, workspace.id);
|
||||
|
||||
const leave = await leaveWorkspace(app, u2.token.token, workspace.id);
|
||||
ok(leave === true, 'failed to leave workspace');
|
||||
t.pass();
|
||||
});
|
||||
|
||||
test('should revoke a user', async t => {
|
||||
const u1 = await signUp(app, 'u1', 'u1@affine.pro', '1');
|
||||
const u2 = await signUp(app, 'u2', 'u2@affine.pro', '1');
|
||||
|
||||
const workspace = await createWorkspace(app, u1.token.token);
|
||||
await inviteUser(app, u1.token.token, workspace.id, u2.email, 'Admin');
|
||||
|
||||
const currWorkspace = await getWorkspace(app, u1.token.token, workspace.id);
|
||||
ok(currWorkspace.members.length === 2, 'failed to invite user');
|
||||
|
||||
const revoke = await revokeUser(app, u1.token.token, workspace.id, u2.id);
|
||||
ok(revoke === true, 'failed to revoke user');
|
||||
t.pass();
|
||||
});
|
||||
|
||||
test('should create user if not exist', async t => {
|
||||
const u1 = await signUp(app, 'u1', 'u1@affine.pro', '1');
|
||||
|
||||
const workspace = await createWorkspace(app, u1.token.token);
|
||||
|
||||
await inviteUser(app, u1.token.token, workspace.id, 'u2@affine.pro', 'Admin');
|
||||
|
||||
const user = await auth.getUserByEmail('u2@affine.pro');
|
||||
ok(user !== undefined, 'failed to create user');
|
||||
ok(user?.name === 'Unnamed', 'failed to create user');
|
||||
t.pass();
|
||||
});
|
||||
|
||||
test('should invite a user by link', async t => {
|
||||
const u1 = await signUp(app, 'u1', 'u1@affine.pro', '1');
|
||||
const u2 = await signUp(app, 'u2', 'u2@affine.pro', '1');
|
||||
|
||||
const workspace = await createWorkspace(app, u1.token.token);
|
||||
|
||||
const invite = await inviteUser(
|
||||
app,
|
||||
u1.token.token,
|
||||
workspace.id,
|
||||
u2.email,
|
||||
'Admin'
|
||||
);
|
||||
|
||||
const accept = await acceptInviteById(app, workspace.id, invite);
|
||||
ok(accept === true, 'failed to accept invite');
|
||||
|
||||
const invite1 = await inviteUser(
|
||||
app,
|
||||
u1.token.token,
|
||||
workspace.id,
|
||||
u2.email,
|
||||
'Admin'
|
||||
);
|
||||
|
||||
ok(invite === invite1, 'repeat the invitation must return same id');
|
||||
|
||||
const currWorkspace = await getWorkspace(app, u1.token.token, workspace.id);
|
||||
const currMember = currWorkspace.members.find(u => u.email === u2.email);
|
||||
ok(currMember !== undefined, 'failed to invite user');
|
||||
ok(currMember.inviteId === invite, 'failed to check invite id');
|
||||
t.pass();
|
||||
});
|
||||
|
||||
test('should send invite email', async t => {
|
||||
if (mail.hasConfigured()) {
|
||||
const u1 = await signUp(app, 'u1', 'u1@affine.pro', '1');
|
||||
const u2 = await signUp(app, 'u2', 'u2@affine.pro', '1');
|
||||
const u2 = await signUp(app, 'test', 'production@toeverything.info', '1');
|
||||
|
||||
const workspace = await createWorkspace(app, u1.token.token);
|
||||
|
||||
const invite = await inviteUser(
|
||||
app,
|
||||
u1.token.token,
|
||||
workspace.id,
|
||||
u2.email,
|
||||
'Admin'
|
||||
);
|
||||
ok(!!invite, 'failed to invite user');
|
||||
});
|
||||
|
||||
it('should accept an invite', async () => {
|
||||
const u1 = await signUp(app, 'u1', 'u1@affine.pro', '1');
|
||||
const u2 = await signUp(app, 'u2', 'u2@affine.pro', '1');
|
||||
|
||||
const workspace = await createWorkspace(app, u1.token.token);
|
||||
await inviteUser(app, u1.token.token, workspace.id, u2.email, 'Admin');
|
||||
|
||||
const accept = await acceptInvite(app, u2.token.token, workspace.id);
|
||||
ok(accept === true, 'failed to accept invite');
|
||||
|
||||
const currWorkspace = await getWorkspace(app, u1.token.token, workspace.id);
|
||||
const currMember = currWorkspace.members.find(u => u.email === u2.email);
|
||||
ok(currMember !== undefined, 'failed to invite user');
|
||||
ok(currMember.id === u2.id, 'failed to invite user');
|
||||
ok(!currMember.accepted, 'failed to invite user');
|
||||
});
|
||||
|
||||
it('should leave a workspace', async () => {
|
||||
const u1 = await signUp(app, 'u1', 'u1@affine.pro', '1');
|
||||
const u2 = await signUp(app, 'u2', 'u2@affine.pro', '1');
|
||||
|
||||
const workspace = await createWorkspace(app, u1.token.token);
|
||||
await inviteUser(app, u1.token.token, workspace.id, u2.email, 'Admin');
|
||||
await acceptInvite(app, u2.token.token, workspace.id);
|
||||
|
||||
const leave = await leaveWorkspace(app, u2.token.token, workspace.id);
|
||||
ok(leave === true, 'failed to leave workspace');
|
||||
});
|
||||
|
||||
it('should revoke a user', async () => {
|
||||
const u1 = await signUp(app, 'u1', 'u1@affine.pro', '1');
|
||||
const u2 = await signUp(app, 'u2', 'u2@affine.pro', '1');
|
||||
|
||||
const workspace = await createWorkspace(app, u1.token.token);
|
||||
await inviteUser(app, u1.token.token, workspace.id, u2.email, 'Admin');
|
||||
|
||||
const currWorkspace = await getWorkspace(app, u1.token.token, workspace.id);
|
||||
ok(currWorkspace.members.length === 2, 'failed to invite user');
|
||||
|
||||
const revoke = await revokeUser(app, u1.token.token, workspace.id, u2.id);
|
||||
ok(revoke === true, 'failed to revoke user');
|
||||
});
|
||||
|
||||
it('should create user if not exist', async () => {
|
||||
const u1 = await signUp(app, 'u1', 'u1@affine.pro', '1');
|
||||
|
||||
const workspace = await createWorkspace(app, u1.token.token);
|
||||
|
||||
await inviteUser(
|
||||
app,
|
||||
u1.token.token,
|
||||
workspace.id,
|
||||
'u2@affine.pro',
|
||||
'Admin'
|
||||
);
|
||||
|
||||
const user = await auth.getUserByEmail('u2@affine.pro');
|
||||
ok(user !== undefined, 'failed to create user');
|
||||
ok(user?.name === 'Unnamed', 'failed to create user');
|
||||
});
|
||||
|
||||
it('should invite a user by link', async () => {
|
||||
const u1 = await signUp(app, 'u1', 'u1@affine.pro', '1');
|
||||
const u2 = await signUp(app, 'u2', 'u2@affine.pro', '1');
|
||||
|
||||
const workspace = await createWorkspace(app, u1.token.token);
|
||||
|
||||
const invite = await inviteUser(
|
||||
app,
|
||||
u1.token.token,
|
||||
workspace.id,
|
||||
u2.email,
|
||||
'Admin'
|
||||
'Admin',
|
||||
true
|
||||
);
|
||||
|
||||
const accept = await acceptInviteById(app, workspace.id, invite);
|
||||
ok(accept === true, 'failed to accept invite');
|
||||
|
||||
const invite1 = await inviteUser(
|
||||
app,
|
||||
u1.token.token,
|
||||
workspace.id,
|
||||
u2.email,
|
||||
'Admin'
|
||||
);
|
||||
|
||||
ok(invite === invite1, 'repeat the invitation must return same id');
|
||||
|
||||
const currWorkspace = await getWorkspace(app, u1.token.token, workspace.id);
|
||||
const currMember = currWorkspace.members.find(u => u.email === u2.email);
|
||||
ok(currMember !== undefined, 'failed to invite user');
|
||||
ok(currMember.inviteId === invite, 'failed to check invite id');
|
||||
});
|
||||
|
||||
it('should send invite email', async () => {
|
||||
if (mail.hasConfigured()) {
|
||||
const u1 = await signUp(app, 'u1', 'u1@affine.pro', '1');
|
||||
const u2 = await signUp(app, 'test', 'production@toeverything.info', '1');
|
||||
|
||||
const workspace = await createWorkspace(app, u1.token.token);
|
||||
await inviteUser(
|
||||
app,
|
||||
u1.token.token,
|
||||
workspace.id,
|
||||
u2.email,
|
||||
'Admin',
|
||||
true
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
t.pass();
|
||||
});
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { deepEqual, ok, rejects } from 'node:assert';
|
||||
import { afterEach, beforeEach, describe, it } from 'node:test';
|
||||
|
||||
import type { INestApplication } from '@nestjs/common';
|
||||
import { Test } from '@nestjs/testing';
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import test from 'ava';
|
||||
// @ts-expect-error graphql-upload is not typed
|
||||
import graphqlUploadExpress from 'graphql-upload/graphqlUploadExpress.mjs';
|
||||
import request from 'supertest';
|
||||
@@ -22,222 +22,213 @@ import {
|
||||
updateWorkspace,
|
||||
} from './utils';
|
||||
|
||||
describe('Workspace Module', () => {
|
||||
let app: INestApplication;
|
||||
let app: INestApplication;
|
||||
|
||||
const client = new PrismaClient();
|
||||
const client = new PrismaClient();
|
||||
|
||||
// cleanup database before each test
|
||||
beforeEach(async () => {
|
||||
await client.$connect();
|
||||
await client.user.deleteMany({});
|
||||
await client.update.deleteMany({});
|
||||
await client.snapshot.deleteMany({});
|
||||
await client.workspace.deleteMany({});
|
||||
await client.$disconnect();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
const module = await Test.createTestingModule({
|
||||
imports: [AppModule],
|
||||
}).compile();
|
||||
app = module.createNestApplication();
|
||||
app.use(
|
||||
graphqlUploadExpress({
|
||||
maxFileSize: 10 * 1024 * 1024,
|
||||
maxFiles: 5,
|
||||
})
|
||||
);
|
||||
await app.init();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await app.close();
|
||||
});
|
||||
|
||||
it('should register a user', async () => {
|
||||
const user = await signUp(app, 'u1', 'u1@affine.pro', '123456');
|
||||
ok(typeof user.id === 'string', 'user.id is not a string');
|
||||
ok(user.name === 'u1', 'user.name is not valid');
|
||||
ok(user.email === 'u1@affine.pro', 'user.email is not valid');
|
||||
});
|
||||
|
||||
it.skip('should be throttled at call signUp', async () => {
|
||||
let token = '';
|
||||
for (let i = 0; i < 10; i++) {
|
||||
token = (await signUp(app, `u${i}`, `u${i}@affine.pro`, `${i}`)).token
|
||||
.token;
|
||||
// throttles are applied to each endpoint separately
|
||||
await currentUser(app, token);
|
||||
}
|
||||
await rejects(signUp(app, 'u11', 'u11@affine.pro', '11'));
|
||||
await rejects(currentUser(app, token));
|
||||
});
|
||||
|
||||
it('should create a workspace', async () => {
|
||||
const user = await signUp(app, 'u1', 'u1@affine.pro', '1');
|
||||
|
||||
const workspace = await createWorkspace(app, user.token.token);
|
||||
ok(typeof workspace.id === 'string', 'workspace.id is not a string');
|
||||
});
|
||||
|
||||
it('should can publish workspace', async () => {
|
||||
const user = await signUp(app, 'u1', 'u1@affine.pro', '1');
|
||||
const workspace = await createWorkspace(app, user.token.token);
|
||||
|
||||
const isPublic = await updateWorkspace(
|
||||
app,
|
||||
user.token.token,
|
||||
workspace.id,
|
||||
true
|
||||
);
|
||||
ok(isPublic === true, 'failed to publish workspace');
|
||||
|
||||
const isPrivate = await updateWorkspace(
|
||||
app,
|
||||
user.token.token,
|
||||
workspace.id,
|
||||
false
|
||||
);
|
||||
ok(isPrivate === false, 'failed to unpublish workspace');
|
||||
});
|
||||
|
||||
it('should can read published workspace', async () => {
|
||||
const user = await signUp(app, 'u1', 'u1@affine.pro', '1');
|
||||
const workspace = await createWorkspace(app, user.token.token);
|
||||
|
||||
await rejects(
|
||||
getPublicWorkspace(app, 'not_exists_ws'),
|
||||
'must not get not exists workspace'
|
||||
);
|
||||
await rejects(
|
||||
getPublicWorkspace(app, workspace.id),
|
||||
'must not get private workspace'
|
||||
);
|
||||
|
||||
await updateWorkspace(app, user.token.token, workspace.id, true);
|
||||
|
||||
const publicWorkspace = await getPublicWorkspace(app, workspace.id);
|
||||
ok(publicWorkspace.id === workspace.id, 'failed to get public workspace');
|
||||
});
|
||||
|
||||
it('should share a page', async () => {
|
||||
const u1 = await signUp(app, 'u1', 'u1@affine.pro', '1');
|
||||
const u2 = await signUp(app, 'u2', 'u2@affine.pro', '1');
|
||||
|
||||
const workspace = await createWorkspace(app, u1.token.token);
|
||||
|
||||
const share = await sharePage(app, u1.token.token, workspace.id, 'page1');
|
||||
ok(share === true, 'failed to share page');
|
||||
const pages = await getWorkspaceSharedPages(
|
||||
app,
|
||||
u1.token.token,
|
||||
workspace.id
|
||||
);
|
||||
ok(pages.length === 1, 'failed to get shared pages');
|
||||
ok(pages[0] === 'page1', 'failed to get shared page: page1');
|
||||
|
||||
const msg1 = await sharePage(app, u2.token.token, workspace.id, 'page2');
|
||||
ok(msg1 === 'Permission denied', 'unauthorized user can share page');
|
||||
const msg2 = await revokePage(
|
||||
app,
|
||||
u2.token.token,
|
||||
'not_exists_ws',
|
||||
'page2'
|
||||
);
|
||||
ok(msg2 === 'Permission denied', 'unauthorized user can share page');
|
||||
|
||||
await inviteUser(app, u1.token.token, workspace.id, u2.email, 'Admin');
|
||||
await acceptInvite(app, u2.token.token, workspace.id);
|
||||
const invited = await sharePage(app, u2.token.token, workspace.id, 'page2');
|
||||
ok(invited === true, 'failed to share page');
|
||||
|
||||
const revoke = await revokePage(app, u1.token.token, workspace.id, 'page1');
|
||||
ok(revoke === true, 'failed to revoke page');
|
||||
const pages2 = await getWorkspaceSharedPages(
|
||||
app,
|
||||
u1.token.token,
|
||||
workspace.id
|
||||
);
|
||||
ok(pages2.length === 1, 'failed to get shared pages');
|
||||
ok(pages2[0] === 'page2', 'failed to get shared page: page2');
|
||||
|
||||
const msg3 = await revokePage(app, u1.token.token, workspace.id, 'page3');
|
||||
ok(msg3 === false, 'can revoke non-exists page');
|
||||
|
||||
const msg4 = await revokePage(app, u1.token.token, workspace.id, 'page2');
|
||||
ok(msg4 === true, 'failed to revoke page');
|
||||
const page3 = await getWorkspaceSharedPages(
|
||||
app,
|
||||
u1.token.token,
|
||||
workspace.id
|
||||
);
|
||||
ok(page3.length === 0, 'failed to get shared pages');
|
||||
});
|
||||
|
||||
it('should can get workspace doc', async () => {
|
||||
const u1 = await signUp(app, 'u1', 'u1@affine.pro', '1');
|
||||
const u2 = await signUp(app, 'u2', 'u2@affine.pro', '2');
|
||||
const workspace = await createWorkspace(app, u1.token.token);
|
||||
|
||||
const res1 = await request(app.getHttpServer())
|
||||
.get(`/api/workspaces/${workspace.id}/docs/${workspace.id}`)
|
||||
.auth(u1.token.token, { type: 'bearer' })
|
||||
.expect(200)
|
||||
.type('application/octet-stream');
|
||||
|
||||
deepEqual(
|
||||
res1.body,
|
||||
Buffer.from([0, 0]),
|
||||
'failed to get doc with u1 token'
|
||||
);
|
||||
|
||||
await request(app.getHttpServer())
|
||||
.get(`/api/workspaces/${workspace.id}/docs/${workspace.id}`)
|
||||
.expect(403);
|
||||
await request(app.getHttpServer())
|
||||
.get(`/api/workspaces/${workspace.id}/docs/${workspace.id}`)
|
||||
.auth(u2.token.token, { type: 'bearer' })
|
||||
.expect(403);
|
||||
|
||||
await inviteUser(app, u1.token.token, workspace.id, u2.email, 'Admin');
|
||||
await request(app.getHttpServer())
|
||||
.get(`/api/workspaces/${workspace.id}/docs/${workspace.id}`)
|
||||
.auth(u2.token.token, { type: 'bearer' })
|
||||
.expect(403);
|
||||
|
||||
await acceptInvite(app, u2.token.token, workspace.id);
|
||||
const res2 = await request(app.getHttpServer())
|
||||
.get(`/api/workspaces/${workspace.id}/docs/${workspace.id}`)
|
||||
.auth(u2.token.token, { type: 'bearer' })
|
||||
.expect(200)
|
||||
.type('application/octet-stream');
|
||||
|
||||
deepEqual(
|
||||
res2.body,
|
||||
Buffer.from([0, 0]),
|
||||
'failed to get doc with u2 token'
|
||||
);
|
||||
});
|
||||
|
||||
it('should be able to get public workspace doc', async () => {
|
||||
const user = await signUp(app, 'u1', 'u1@affine.pro', '1');
|
||||
const workspace = await createWorkspace(app, user.token.token);
|
||||
|
||||
const isPublic = await updateWorkspace(
|
||||
app,
|
||||
user.token.token,
|
||||
workspace.id,
|
||||
true
|
||||
);
|
||||
|
||||
ok(isPublic === true, 'failed to publish workspace');
|
||||
|
||||
const res = await request(app.getHttpServer())
|
||||
.get(`/api/workspaces/${workspace.id}/docs/${workspace.id}`)
|
||||
.expect(200)
|
||||
.type('application/octet-stream');
|
||||
|
||||
deepEqual(res.body, Buffer.from([0, 0]), 'failed to get public doc');
|
||||
});
|
||||
// cleanup database before each test
|
||||
test.beforeEach(async () => {
|
||||
await client.$connect();
|
||||
await client.user.deleteMany({});
|
||||
await client.update.deleteMany({});
|
||||
await client.snapshot.deleteMany({});
|
||||
await client.workspace.deleteMany({});
|
||||
await client.$disconnect();
|
||||
});
|
||||
|
||||
test.beforeEach(async () => {
|
||||
const module = await Test.createTestingModule({
|
||||
imports: [AppModule],
|
||||
}).compile();
|
||||
app = module.createNestApplication();
|
||||
app.use(
|
||||
graphqlUploadExpress({
|
||||
maxFileSize: 10 * 1024 * 1024,
|
||||
maxFiles: 5,
|
||||
})
|
||||
);
|
||||
await app.init();
|
||||
});
|
||||
|
||||
test.afterEach(async () => {
|
||||
await app.close();
|
||||
});
|
||||
|
||||
test('should register a user', async t => {
|
||||
const user = await signUp(app, 'u1', 'u1@affine.pro', '123456');
|
||||
t.true(typeof user.id === 'string', 'user.id is not a string');
|
||||
t.true(user.name === 'u1', 'user.name is not valid');
|
||||
t.true(user.email === 'u1@affine.pro', 'user.email is not valid');
|
||||
});
|
||||
|
||||
test.skip('should be throttled at call signUp', async t => {
|
||||
let token = '';
|
||||
for (let i = 0; i < 10; i++) {
|
||||
token = (await signUp(app, `u${i}`, `u${i}@affine.pro`, `${i}`)).token
|
||||
.token;
|
||||
// throttles are applied to each endpoint separately
|
||||
await currentUser(app, token);
|
||||
}
|
||||
await rejects(signUp(app, 'u11', 'u11@affine.pro', '11'));
|
||||
await rejects(currentUser(app, token));
|
||||
t.pass();
|
||||
});
|
||||
|
||||
test('should create a workspace', async t => {
|
||||
const user = await signUp(app, 'u1', 'u1@affine.pro', '1');
|
||||
|
||||
const workspace = await createWorkspace(app, user.token.token);
|
||||
ok(typeof workspace.id === 'string', 'workspace.id is not a string');
|
||||
t.pass();
|
||||
});
|
||||
|
||||
test('should can publish workspace', async t => {
|
||||
const user = await signUp(app, 'u1', 'u1@affine.pro', '1');
|
||||
const workspace = await createWorkspace(app, user.token.token);
|
||||
|
||||
const isPublic = await updateWorkspace(
|
||||
app,
|
||||
user.token.token,
|
||||
workspace.id,
|
||||
true
|
||||
);
|
||||
ok(isPublic === true, 'failed to publish workspace');
|
||||
|
||||
const isPrivate = await updateWorkspace(
|
||||
app,
|
||||
user.token.token,
|
||||
workspace.id,
|
||||
false
|
||||
);
|
||||
ok(isPrivate === false, 'failed to unpublish workspace');
|
||||
t.pass();
|
||||
});
|
||||
|
||||
test('should can read published workspace', async t => {
|
||||
const user = await signUp(app, 'u1', 'u1@affine.pro', '1');
|
||||
const workspace = await createWorkspace(app, user.token.token);
|
||||
|
||||
await rejects(
|
||||
getPublicWorkspace(app, 'not_exists_ws'),
|
||||
'must not get not exists workspace'
|
||||
);
|
||||
await rejects(
|
||||
getPublicWorkspace(app, workspace.id),
|
||||
'must not get private workspace'
|
||||
);
|
||||
|
||||
await updateWorkspace(app, user.token.token, workspace.id, true);
|
||||
|
||||
const publicWorkspace = await getPublicWorkspace(app, workspace.id);
|
||||
ok(publicWorkspace.id === workspace.id, 'failed to get public workspace');
|
||||
t.pass();
|
||||
});
|
||||
|
||||
test('should share a page', async t => {
|
||||
const u1 = await signUp(app, 'u1', 'u1@affine.pro', '1');
|
||||
const u2 = await signUp(app, 'u2', 'u2@affine.pro', '1');
|
||||
|
||||
const workspace = await createWorkspace(app, u1.token.token);
|
||||
|
||||
const share = await sharePage(app, u1.token.token, workspace.id, 'page1');
|
||||
t.true(share === true, 'failed to share page');
|
||||
const pages = await getWorkspaceSharedPages(
|
||||
app,
|
||||
u1.token.token,
|
||||
workspace.id
|
||||
);
|
||||
t.true(pages.length === 1, 'failed to get shared pages');
|
||||
t.true(pages[0] === 'page1', 'failed to get shared page: page1');
|
||||
|
||||
const msg1 = await sharePage(app, u2.token.token, workspace.id, 'page2');
|
||||
t.true(msg1 === 'Permission denied', 'unauthorized user can share page');
|
||||
const msg2 = await revokePage(app, u2.token.token, 'not_exists_ws', 'page2');
|
||||
t.true(msg2 === 'Permission denied', 'unauthorized user can share page');
|
||||
|
||||
await inviteUser(app, u1.token.token, workspace.id, u2.email, 'Admin');
|
||||
await acceptInvite(app, u2.token.token, workspace.id);
|
||||
const invited = await sharePage(app, u2.token.token, workspace.id, 'page2');
|
||||
t.true(invited === true, 'failed to share page');
|
||||
|
||||
const revoke = await revokePage(app, u1.token.token, workspace.id, 'page1');
|
||||
t.true(revoke === true, 'failed to revoke page');
|
||||
const pages2 = await getWorkspaceSharedPages(
|
||||
app,
|
||||
u1.token.token,
|
||||
workspace.id
|
||||
);
|
||||
t.true(pages2.length === 1, 'failed to get shared pages');
|
||||
t.true(pages2[0] === 'page2', 'failed to get shared page: page2');
|
||||
|
||||
const msg3 = await revokePage(app, u1.token.token, workspace.id, 'page3');
|
||||
t.true(msg3 === false, 'can revoke non-exists page');
|
||||
|
||||
const msg4 = await revokePage(app, u1.token.token, workspace.id, 'page2');
|
||||
t.true(msg4 === true, 'failed to revoke page');
|
||||
const page3 = await getWorkspaceSharedPages(
|
||||
app,
|
||||
u1.token.token,
|
||||
workspace.id
|
||||
);
|
||||
t.true(page3.length === 0, 'failed to get shared pages');
|
||||
});
|
||||
|
||||
test('should can get workspace doc', async t => {
|
||||
const u1 = await signUp(app, 'u1', 'u1@affine.pro', '1');
|
||||
const u2 = await signUp(app, 'u2', 'u2@affine.pro', '2');
|
||||
const workspace = await createWorkspace(app, u1.token.token);
|
||||
|
||||
const res1 = await request(app.getHttpServer())
|
||||
.get(`/api/workspaces/${workspace.id}/docs/${workspace.id}`)
|
||||
.auth(u1.token.token, { type: 'bearer' })
|
||||
.expect(200)
|
||||
.type('application/octet-stream');
|
||||
|
||||
deepEqual(res1.body, Buffer.from([0, 0]), 'failed to get doc with u1 token');
|
||||
|
||||
await request(app.getHttpServer())
|
||||
.get(`/api/workspaces/${workspace.id}/docs/${workspace.id}`)
|
||||
.expect(403);
|
||||
await request(app.getHttpServer())
|
||||
.get(`/api/workspaces/${workspace.id}/docs/${workspace.id}`)
|
||||
.auth(u2.token.token, { type: 'bearer' })
|
||||
.expect(403);
|
||||
|
||||
await inviteUser(app, u1.token.token, workspace.id, u2.email, 'Admin');
|
||||
await request(app.getHttpServer())
|
||||
.get(`/api/workspaces/${workspace.id}/docs/${workspace.id}`)
|
||||
.auth(u2.token.token, { type: 'bearer' })
|
||||
.expect(403);
|
||||
|
||||
await acceptInvite(app, u2.token.token, workspace.id);
|
||||
const res2 = await request(app.getHttpServer())
|
||||
.get(`/api/workspaces/${workspace.id}/docs/${workspace.id}`)
|
||||
.auth(u2.token.token, { type: 'bearer' })
|
||||
.expect(200)
|
||||
.type('application/octet-stream');
|
||||
|
||||
deepEqual(res2.body, Buffer.from([0, 0]), 'failed to get doc with u2 token');
|
||||
t.pass();
|
||||
});
|
||||
|
||||
test('should be able to get public workspace doc', async t => {
|
||||
const user = await signUp(app, 'u1', 'u1@affine.pro', '1');
|
||||
const workspace = await createWorkspace(app, user.token.token);
|
||||
|
||||
const isPublic = await updateWorkspace(
|
||||
app,
|
||||
user.token.token,
|
||||
workspace.id,
|
||||
true
|
||||
);
|
||||
|
||||
ok(isPublic === true, 'failed to publish workspace');
|
||||
|
||||
const res = await request(app.getHttpServer())
|
||||
.get(`/api/workspaces/${workspace.id}/docs/${workspace.id}`)
|
||||
.expect(200)
|
||||
.type('application/octet-stream');
|
||||
|
||||
deepEqual(res.body, Buffer.from([0, 0]), 'failed to get public doc');
|
||||
t.pass();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user