mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-04 08:38:34 +00:00
chore(server): data mocking and seeding (#10864)
This commit is contained in:
@@ -40,8 +40,9 @@ yarn affine @affine/server-native build
|
|||||||
```sh
|
```sh
|
||||||
# uncomment all env variables here
|
# uncomment all env variables here
|
||||||
cp packages/backend/server/.env.example packages/backend/server/.env
|
cp packages/backend/server/.env.example packages/backend/server/.env
|
||||||
yarn affine server prisma db push
|
|
||||||
yarn affine server data-migration run
|
# everytime there are new migrations, init command should runned again
|
||||||
|
yarn affine server init
|
||||||
```
|
```
|
||||||
|
|
||||||
## Start server
|
## Start server
|
||||||
@@ -90,3 +91,9 @@ Now you should be able to start developing affine with server enabled.
|
|||||||
# available at http://localhost:5555
|
# available at http://localhost:5555
|
||||||
yarn affine server prisma studio
|
yarn affine server prisma studio
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Seed the db
|
||||||
|
|
||||||
|
```
|
||||||
|
yarn affine server seed -h
|
||||||
|
```
|
||||||
|
|||||||
@@ -16,6 +16,8 @@
|
|||||||
"test:coverage": "c8 ava --concurrency 1 --serial",
|
"test:coverage": "c8 ava --concurrency 1 --serial",
|
||||||
"test:copilot:coverage": "c8 ava --timeout=5m \"src/__tests__/**/copilot-*.spec.ts\"",
|
"test:copilot:coverage": "c8 ava --timeout=5m \"src/__tests__/**/copilot-*.spec.ts\"",
|
||||||
"data-migration": "cross-env NODE_ENV=development r ./src/data/index.ts",
|
"data-migration": "cross-env NODE_ENV=development r ./src/data/index.ts",
|
||||||
|
"init": "yarn prisma migrate dev && yarn data-migration run",
|
||||||
|
"seed": "r ./src/seed/index.ts",
|
||||||
"predeploy": "yarn prisma migrate deploy && node --import ./scripts/register.js ./dist/data/index.js run",
|
"predeploy": "yarn prisma migrate deploy && node --import ./scripts/register.js ./dist/data/index.js run",
|
||||||
"postinstall": "prisma generate"
|
"postinstall": "prisma generate"
|
||||||
},
|
},
|
||||||
@@ -108,6 +110,7 @@
|
|||||||
"@affine-tools/cli": "workspace:*",
|
"@affine-tools/cli": "workspace:*",
|
||||||
"@affine-tools/utils": "workspace:*",
|
"@affine-tools/utils": "workspace:*",
|
||||||
"@affine/server-native": "workspace:*",
|
"@affine/server-native": "workspace:*",
|
||||||
|
"@faker-js/faker": "^9.6.0",
|
||||||
"@nestjs/testing": "^10.4.15",
|
"@nestjs/testing": "^10.4.15",
|
||||||
"@types/cookie-parser": "^1.4.8",
|
"@types/cookie-parser": "^1.4.8",
|
||||||
"@types/express": "^4.17.21",
|
"@types/express": "^4.17.21",
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ test('change email', async t => {
|
|||||||
const u1Email = 'u1@affine.pro';
|
const u1Email = 'u1@affine.pro';
|
||||||
const u2Email = 'u2@affine.pro';
|
const u2Email = 'u2@affine.pro';
|
||||||
|
|
||||||
await app.signup(u1Email);
|
await app.signupV1(u1Email);
|
||||||
const primitiveMailCount = await getCurrentMailMessageCount();
|
const primitiveMailCount = await getCurrentMailMessageCount();
|
||||||
await sendChangeEmail(app, u1Email, 'affine.pro');
|
await sendChangeEmail(app, u1Email, 'affine.pro');
|
||||||
|
|
||||||
@@ -101,7 +101,7 @@ test('set and change password', async t => {
|
|||||||
if (mail.hasConfigured()) {
|
if (mail.hasConfigured()) {
|
||||||
const u1Email = 'u1@affine.pro';
|
const u1Email = 'u1@affine.pro';
|
||||||
|
|
||||||
const u1 = await app.signup(u1Email);
|
const u1 = await app.signupV1(u1Email);
|
||||||
|
|
||||||
const primitiveMailCount = await getCurrentMailMessageCount();
|
const primitiveMailCount = await getCurrentMailMessageCount();
|
||||||
|
|
||||||
@@ -153,7 +153,7 @@ test('should revoke token after change user identify', async t => {
|
|||||||
const u1Email = 'u1@affine.pro';
|
const u1Email = 'u1@affine.pro';
|
||||||
const u2Email = 'u2@affine.pro';
|
const u2Email = 'u2@affine.pro';
|
||||||
|
|
||||||
const u1 = await app.signup(u1Email);
|
const u1 = await app.signupV1(u1Email);
|
||||||
|
|
||||||
{
|
{
|
||||||
const user = await currentUser(app);
|
const user = await currentUser(app);
|
||||||
@@ -190,7 +190,7 @@ test('should revoke token after change user identify', async t => {
|
|||||||
const u3Email = 'u3333@affine.pro';
|
const u3Email = 'u3333@affine.pro';
|
||||||
|
|
||||||
await app.logout();
|
await app.logout();
|
||||||
const u3 = await app.signup(u3Email);
|
const u3 = await app.signupV1(u3Email);
|
||||||
|
|
||||||
{
|
{
|
||||||
const user = await currentUser(app);
|
const user = await currentUser(app);
|
||||||
|
|||||||
@@ -164,7 +164,7 @@ test('should be able to sign out', async t => {
|
|||||||
test('should be able to correct user id cookie', async t => {
|
test('should be able to correct user id cookie', async t => {
|
||||||
const { app } = t.context;
|
const { app } = t.context;
|
||||||
|
|
||||||
const u1 = await app.signup('u1@affine.pro');
|
const u1 = await app.signupV1('u1@affine.pro');
|
||||||
|
|
||||||
const req = app.GET('/api/auth/session');
|
const req = app.GET('/api/auth/session');
|
||||||
let cookies = req.get('cookie') as unknown as string[];
|
let cookies = req.get('cookie') as unknown as string[];
|
||||||
@@ -229,8 +229,8 @@ test('should be able to sign in another account in one session', async t => {
|
|||||||
test('should be able to sign out multiple accounts in one session', async t => {
|
test('should be able to sign out multiple accounts in one session', async t => {
|
||||||
const { app } = t.context;
|
const { app } = t.context;
|
||||||
|
|
||||||
const u1 = await app.signup('u1@affine.pro');
|
const u1 = await app.signupV1('u1@affine.pro');
|
||||||
const u2 = await app.signup('u2@affine.pro');
|
const u2 = await app.signupV1('u2@affine.pro');
|
||||||
|
|
||||||
// sign out u2
|
// sign out u2
|
||||||
await app.GET(`/api/auth/sign-out?user_id=${u2.id}`).expect(200);
|
await app.GET(`/api/auth/sign-out?user_id=${u2.id}`).expect(200);
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ test.beforeEach(async t => {
|
|||||||
const { app, prompt } = t.context;
|
const { app, prompt } = t.context;
|
||||||
await app.initTestingDB();
|
await app.initTestingDB();
|
||||||
await prompt.onModuleInit();
|
await prompt.onModuleInit();
|
||||||
t.context.u1 = await app.signup('u1@affine.pro');
|
t.context.u1 = await app.signupV1('u1@affine.pro');
|
||||||
|
|
||||||
unregisterCopilotProvider(OpenAIProvider.type);
|
unregisterCopilotProvider(OpenAIProvider.type);
|
||||||
unregisterCopilotProvider(FalProvider.type);
|
unregisterCopilotProvider(FalProvider.type);
|
||||||
@@ -223,7 +223,7 @@ test('should update session correctly', async t => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
await app.signup('test@affine.pro');
|
await app.signupV1('test@affine.pro');
|
||||||
const u2 = await app.createUser('u2@affine.pro');
|
const u2 = await app.createUser('u2@affine.pro');
|
||||||
const { id: workspaceId } = await createWorkspace(app);
|
const { id: workspaceId } = await createWorkspace(app);
|
||||||
const inviteId = await inviteUser(app, workspaceId, u2.email);
|
const inviteId = await inviteUser(app, workspaceId, u2.email);
|
||||||
@@ -309,7 +309,7 @@ test('should fork session correctly', async t => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
const u2 = await app.signup('u2@affine.pro');
|
const u2 = await app.signupV1('u2@affine.pro');
|
||||||
await assertForkSession(id, sessionId, randomUUID(), '', async x => {
|
await assertForkSession(id, sessionId, randomUUID(), '', async x => {
|
||||||
await t.throwsAsync(
|
await t.throwsAsync(
|
||||||
x,
|
x,
|
||||||
|
|||||||
@@ -31,8 +31,8 @@ test('should send invite email', async t => {
|
|||||||
const { mail, app } = t.context;
|
const { mail, app } = t.context;
|
||||||
|
|
||||||
if (mail.hasConfigured()) {
|
if (mail.hasConfigured()) {
|
||||||
const u2 = await app.signup('u2@affine.pro');
|
const u2 = await app.signupV1('u2@affine.pro');
|
||||||
const u1 = await app.signup('u1@affine.pro');
|
const u1 = await app.signupV1('u1@affine.pro');
|
||||||
const stub = Sinon.stub(mail, 'send');
|
const stub = Sinon.stub(mail, 'send');
|
||||||
|
|
||||||
const workspace = await createWorkspace(app);
|
const workspace = await createWorkspace(app);
|
||||||
|
|||||||
90
packages/backend/server/src/__tests__/mocks/factory.ts
Normal file
90
packages/backend/server/src/__tests__/mocks/factory.ts
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
import { Type } from '@nestjs/common';
|
||||||
|
import { PrismaClient } from '@prisma/client';
|
||||||
|
|
||||||
|
export abstract class Mocker<In, Out> {
|
||||||
|
// NOTE(@forehalo):
|
||||||
|
// The reason why we don't inject [Models] to Mocker for more easier data creation with built in logic is,
|
||||||
|
// the method in [Models] may introduce side effects like 'events',
|
||||||
|
// which may break the tests with event emitting asserts.
|
||||||
|
protected db!: PrismaClient;
|
||||||
|
|
||||||
|
abstract create(input?: Partial<In>): Promise<Out>;
|
||||||
|
}
|
||||||
|
|
||||||
|
type MockerConstructor<In, Out> = Type<Mocker<In, Out>>;
|
||||||
|
type MockerInput<Ctor extends MockerConstructor<any, any>> =
|
||||||
|
Ctor extends MockerConstructor<infer In, any> ? In : never;
|
||||||
|
type MockerOutput<Ctor extends MockerConstructor<any, any>> =
|
||||||
|
Ctor extends MockerConstructor<any, infer Out> ? Out : never;
|
||||||
|
|
||||||
|
const FACTORIES = new Map<string, Mocker<any, any>>();
|
||||||
|
|
||||||
|
interface FactoryOptions {
|
||||||
|
logger: ((val: any) => void) | boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createFactory(
|
||||||
|
db: PrismaClient,
|
||||||
|
opts: FactoryOptions = { logger: false }
|
||||||
|
) {
|
||||||
|
const log = (val: any) => {
|
||||||
|
if (typeof opts.logger === 'function') {
|
||||||
|
opts.logger(val);
|
||||||
|
} else if (opts.logger) {
|
||||||
|
console.log(val);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class Inner {
|
||||||
|
static create<Ctor extends MockerConstructor<any, any>>(
|
||||||
|
Factory: Ctor,
|
||||||
|
overrides?: Partial<MockerInput<Ctor>>
|
||||||
|
): Promise<MockerOutput<Ctor>>;
|
||||||
|
static create<Ctor extends MockerConstructor<any, any>>(
|
||||||
|
Factory: Ctor,
|
||||||
|
count: number
|
||||||
|
): Promise<MockerOutput<Ctor>[]>;
|
||||||
|
static create<Ctor extends MockerConstructor<any, any>>(
|
||||||
|
Factory: Ctor,
|
||||||
|
overrides: Partial<MockerInput<Ctor>>,
|
||||||
|
count: number
|
||||||
|
): Promise<MockerOutput<Ctor>[]>;
|
||||||
|
static async create<Ctor extends MockerConstructor<any, any>>(
|
||||||
|
Factory: Ctor,
|
||||||
|
overridesOrCount?: Partial<MockerInput<Ctor>> | number,
|
||||||
|
count?: number
|
||||||
|
): Promise<MockerOutput<Ctor> | MockerOutput<Ctor>[]> {
|
||||||
|
let factory = FACTORIES.get(Factory.name);
|
||||||
|
|
||||||
|
if (!factory) {
|
||||||
|
factory = new Factory();
|
||||||
|
// @ts-expect-error private
|
||||||
|
factory.db = db;
|
||||||
|
FACTORIES.set(Factory.name, factory);
|
||||||
|
}
|
||||||
|
|
||||||
|
let overrides: Partial<MockerInput<Ctor>> | undefined = undefined;
|
||||||
|
if (typeof overridesOrCount === 'number') {
|
||||||
|
count = overridesOrCount;
|
||||||
|
} else {
|
||||||
|
overrides = overridesOrCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof count === 'number') {
|
||||||
|
return await Promise.all(
|
||||||
|
Array.from({ length: count }).map(async () => {
|
||||||
|
const row = await factory.create(overrides);
|
||||||
|
log(row);
|
||||||
|
return row;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const row = await factory.create(overrides);
|
||||||
|
log(row);
|
||||||
|
return row;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Inner.create;
|
||||||
|
}
|
||||||
14
packages/backend/server/src/__tests__/mocks/index.ts
Normal file
14
packages/backend/server/src/__tests__/mocks/index.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
export { createFactory } from './factory';
|
||||||
|
export * from './team-workspace.mock';
|
||||||
|
export * from './user.mock';
|
||||||
|
export * from './workspace.mock';
|
||||||
|
|
||||||
|
import { MockTeamWorkspace } from './team-workspace.mock';
|
||||||
|
import { MockUser } from './user.mock';
|
||||||
|
import { MockWorkspace } from './workspace.mock';
|
||||||
|
|
||||||
|
export const Mockers = {
|
||||||
|
User: MockUser,
|
||||||
|
Workspace: MockWorkspace,
|
||||||
|
TeamWorkspace: MockTeamWorkspace,
|
||||||
|
};
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
import { faker } from '@faker-js/faker';
|
||||||
|
|
||||||
|
import { Feature } from '../../models';
|
||||||
|
import { Mocker } from './factory';
|
||||||
|
|
||||||
|
interface MockTeamWorkspaceInput {
|
||||||
|
id: string;
|
||||||
|
quantity: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class MockTeamWorkspace extends Mocker<
|
||||||
|
MockTeamWorkspaceInput,
|
||||||
|
{ id: string }
|
||||||
|
> {
|
||||||
|
override async create(input?: Partial<MockTeamWorkspaceInput>) {
|
||||||
|
const id = input?.id ?? faker.string.uuid();
|
||||||
|
const quantity = input?.quantity ?? 10;
|
||||||
|
|
||||||
|
await this.db.subscription.create({
|
||||||
|
data: {
|
||||||
|
targetId: id,
|
||||||
|
plan: 'team',
|
||||||
|
recurring: 'monthly',
|
||||||
|
status: 'active',
|
||||||
|
start: faker.date.past(),
|
||||||
|
nextBillAt: faker.date.future(),
|
||||||
|
quantity,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const feature = await this.db.feature.findFirst({
|
||||||
|
where: {
|
||||||
|
name: Feature.TeamPlan,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!feature) {
|
||||||
|
throw new Error(
|
||||||
|
`Feature ${Feature.TeamPlan} does not exist in DB. You might forgot to run data-migration first.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.db.workspaceFeature.create({
|
||||||
|
data: {
|
||||||
|
workspaceId: id,
|
||||||
|
featureId: feature.id,
|
||||||
|
reason: 'test',
|
||||||
|
activated: true,
|
||||||
|
configs: {
|
||||||
|
memberLimit: quantity,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return { id };
|
||||||
|
}
|
||||||
|
}
|
||||||
54
packages/backend/server/src/__tests__/mocks/user.mock.ts
Normal file
54
packages/backend/server/src/__tests__/mocks/user.mock.ts
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import { faker } from '@faker-js/faker';
|
||||||
|
import { hashSync } from '@node-rs/argon2';
|
||||||
|
import type { Prisma, User } from '@prisma/client';
|
||||||
|
|
||||||
|
import type { UserFeatureName } from '../../models';
|
||||||
|
import { Mocker } from './factory';
|
||||||
|
|
||||||
|
export type MockUserInput = Prisma.UserCreateInput & {
|
||||||
|
feature?: UserFeatureName;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type MockedUser = Omit<User, 'password'> & {
|
||||||
|
password: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export class MockUser extends Mocker<MockUserInput, MockedUser> {
|
||||||
|
override async create(input?: Partial<MockUserInput>) {
|
||||||
|
const password = input?.password ?? faker.internet.password();
|
||||||
|
const user = await this.db.user.create({
|
||||||
|
data: {
|
||||||
|
email: faker.internet.email(),
|
||||||
|
name: faker.person.fullName(),
|
||||||
|
password: password ? hashSync(password) : undefined,
|
||||||
|
...input,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (input?.feature) {
|
||||||
|
const feature = await this.db.feature.findFirst({
|
||||||
|
where: {
|
||||||
|
name: input.feature,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!feature) {
|
||||||
|
throw new Error(
|
||||||
|
`Feature ${input.feature} does not exist in DB. You might forgot to run data-migration first.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.db.userFeature.create({
|
||||||
|
data: {
|
||||||
|
userId: user.id,
|
||||||
|
featureId: feature.id,
|
||||||
|
reason: 'test',
|
||||||
|
activated: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// return raw password for later usage, for example 'signIn'
|
||||||
|
return { ...user, password };
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import { faker } from '@faker-js/faker';
|
||||||
|
import type { Prisma, Workspace } from '@prisma/client';
|
||||||
|
|
||||||
|
import { WorkspaceRole } from '../../models';
|
||||||
|
import { Mocker } from './factory';
|
||||||
|
|
||||||
|
export type MockWorkspaceInput = Prisma.WorkspaceCreateInput & {
|
||||||
|
owner?: { id: string };
|
||||||
|
};
|
||||||
|
|
||||||
|
export type MockedWorkspace = Workspace;
|
||||||
|
|
||||||
|
export class MockWorkspace extends Mocker<MockWorkspaceInput, MockedWorkspace> {
|
||||||
|
override async create(input?: Partial<MockWorkspaceInput>) {
|
||||||
|
return await this.db.workspace.create({
|
||||||
|
data: {
|
||||||
|
name: faker.animal.cat(),
|
||||||
|
public: false,
|
||||||
|
...input,
|
||||||
|
permissions: input?.owner
|
||||||
|
? {
|
||||||
|
create: {
|
||||||
|
userId: 'id' in input.owner ? input.owner.id : input.owner,
|
||||||
|
type: WorkspaceRole.Owner,
|
||||||
|
status: 'Accepted',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -191,7 +191,7 @@ test('should use specified throttler for unauthenticated user', async t => {
|
|||||||
test('should not protect unspecified routes', async t => {
|
test('should not protect unspecified routes', async t => {
|
||||||
const { app } = t.context;
|
const { app } = t.context;
|
||||||
|
|
||||||
await app.signup('u1@affine.pro');
|
await app.signupV1('u1@affine.pro');
|
||||||
const res = await app.GET('/nonthrottled/default').expect(200);
|
const res = await app.GET('/nonthrottled/default').expect(200);
|
||||||
|
|
||||||
const headers = rateLimitHeaders(res);
|
const headers = rateLimitHeaders(res);
|
||||||
@@ -204,7 +204,7 @@ test('should not protect unspecified routes', async t => {
|
|||||||
test('should use default throttler for authenticated user when not specified', async t => {
|
test('should use default throttler for authenticated user when not specified', async t => {
|
||||||
const { app } = t.context;
|
const { app } = t.context;
|
||||||
|
|
||||||
await app.signup('u1@affine.pro');
|
await app.signupV1('u1@affine.pro');
|
||||||
const res = await app.GET('/throttled/default').expect(200);
|
const res = await app.GET('/throttled/default').expect(200);
|
||||||
|
|
||||||
const headers = rateLimitHeaders(res);
|
const headers = rateLimitHeaders(res);
|
||||||
@@ -216,7 +216,7 @@ test('should use default throttler for authenticated user when not specified', a
|
|||||||
test('should use same throttler for multiple routes', async t => {
|
test('should use same throttler for multiple routes', async t => {
|
||||||
const { app } = t.context;
|
const { app } = t.context;
|
||||||
|
|
||||||
await app.signup('u1@affine.pro');
|
await app.signupV1('u1@affine.pro');
|
||||||
let res = await app.GET('/throttled/default').expect(200);
|
let res = await app.GET('/throttled/default').expect(200);
|
||||||
|
|
||||||
let headers = rateLimitHeaders(res);
|
let headers = rateLimitHeaders(res);
|
||||||
@@ -235,7 +235,7 @@ test('should use same throttler for multiple routes', async t => {
|
|||||||
test('should use different throttler if specified', async t => {
|
test('should use different throttler if specified', async t => {
|
||||||
const { app } = t.context;
|
const { app } = t.context;
|
||||||
|
|
||||||
await app.signup('u1@affine.pro');
|
await app.signupV1('u1@affine.pro');
|
||||||
let res = await app.GET('/throttled/default').expect(200);
|
let res = await app.GET('/throttled/default').expect(200);
|
||||||
|
|
||||||
let headers = rateLimitHeaders(res);
|
let headers = rateLimitHeaders(res);
|
||||||
@@ -254,7 +254,7 @@ test('should use different throttler if specified', async t => {
|
|||||||
test('should skip throttler for authenticated if `authenticated` throttler used', async t => {
|
test('should skip throttler for authenticated if `authenticated` throttler used', async t => {
|
||||||
const { app } = t.context;
|
const { app } = t.context;
|
||||||
|
|
||||||
await app.signup('u1@affine.pro');
|
await app.signupV1('u1@affine.pro');
|
||||||
const res = await app.GET('/throttled/authenticated').expect(200);
|
const res = await app.GET('/throttled/authenticated').expect(200);
|
||||||
|
|
||||||
const headers = rateLimitHeaders(res);
|
const headers = rateLimitHeaders(res);
|
||||||
@@ -278,7 +278,7 @@ test('should apply `default` throttler for unauthenticated user if `authenticate
|
|||||||
test('should skip throttler for authenticated user when specified', async t => {
|
test('should skip throttler for authenticated user when specified', async t => {
|
||||||
const { app } = t.context;
|
const { app } = t.context;
|
||||||
|
|
||||||
await app.signup('u1@affine.pro');
|
await app.signupV1('u1@affine.pro');
|
||||||
const res = await app.GET('/throttled/skip').expect(200);
|
const res = await app.GET('/throttled/skip').expect(200);
|
||||||
|
|
||||||
const headers = rateLimitHeaders(res);
|
const headers = rateLimitHeaders(res);
|
||||||
@@ -291,7 +291,7 @@ test('should skip throttler for authenticated user when specified', async t => {
|
|||||||
test('should use specified throttler for authenticated user', async t => {
|
test('should use specified throttler for authenticated user', async t => {
|
||||||
const { app } = t.context;
|
const { app } = t.context;
|
||||||
|
|
||||||
await app.signup('u1@affine.pro');
|
await app.signupV1('u1@affine.pro');
|
||||||
const res = await app.GET('/throttled/strict').expect(200);
|
const res = await app.GET('/throttled/strict').expect(200);
|
||||||
|
|
||||||
const headers = rateLimitHeaders(res);
|
const headers = rateLimitHeaders(res);
|
||||||
@@ -306,7 +306,7 @@ test('should separate anonymous and authenticated user throttlers', async t => {
|
|||||||
const unauthenticatedUserRes = await app
|
const unauthenticatedUserRes = await app
|
||||||
.GET('/nonthrottled/default')
|
.GET('/nonthrottled/default')
|
||||||
.expect(200);
|
.expect(200);
|
||||||
await app.signup('u1@affine.pro');
|
await app.signupV1('u1@affine.pro');
|
||||||
const authenticatedUserRes = await app.GET('/throttled/default').expect(200);
|
const authenticatedUserRes = await app.GET('/throttled/default').expect(200);
|
||||||
|
|
||||||
const authenticatedResHeaders = rateLimitHeaders(authenticatedUserRes);
|
const authenticatedResHeaders = rateLimitHeaders(authenticatedUserRes);
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ const init = async (
|
|||||||
memberLimit = 10,
|
memberLimit = 10,
|
||||||
prefix = randomUUID()
|
prefix = randomUUID()
|
||||||
) => {
|
) => {
|
||||||
const owner = await app.signup(`${prefix}owner@affine.pro`);
|
const owner = await app.signupV1(`${prefix}owner@affine.pro`);
|
||||||
const models = app.get(Models);
|
const models = app.get(Models);
|
||||||
{
|
{
|
||||||
await models.userFeature.add(owner.id, 'pro_plan_v1', 'test');
|
await models.userFeature.add(owner.id, 'pro_plan_v1', 'test');
|
||||||
@@ -101,7 +101,7 @@ const init = async (
|
|||||||
permission: WorkspaceRole = WorkspaceRole.Collaborator,
|
permission: WorkspaceRole = WorkspaceRole.Collaborator,
|
||||||
shouldSendEmail: boolean = false
|
shouldSendEmail: boolean = false
|
||||||
) => {
|
) => {
|
||||||
const member = await app.signup(email);
|
const member = await app.signupV1(email);
|
||||||
|
|
||||||
{
|
{
|
||||||
// normal workspace
|
// normal workspace
|
||||||
@@ -140,7 +140,7 @@ const init = async (
|
|||||||
) => {
|
) => {
|
||||||
const members = [];
|
const members = [];
|
||||||
for (const email of emails) {
|
for (const email of emails) {
|
||||||
const member = await app.signup(email);
|
const member = await app.signupV1(email);
|
||||||
members.push(member);
|
members.push(member);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,7 +161,7 @@ const init = async (
|
|||||||
return [
|
return [
|
||||||
inviteId,
|
inviteId,
|
||||||
async (email: string, shouldSendEmail: boolean = false) => {
|
async (email: string, shouldSendEmail: boolean = false) => {
|
||||||
const member = await app.signup(email);
|
const member = await app.signupV1(email);
|
||||||
await acceptInviteById(app, ws.id, inviteId, shouldSendEmail);
|
await acceptInviteById(app, ws.id, inviteId, shouldSendEmail);
|
||||||
return member;
|
return member;
|
||||||
},
|
},
|
||||||
@@ -221,8 +221,8 @@ test('should be able to invite multiple users', async t => {
|
|||||||
|
|
||||||
{
|
{
|
||||||
// manager
|
// manager
|
||||||
const m1 = await app.signup('m1@affine.pro');
|
const m1 = await app.signupV1('m1@affine.pro');
|
||||||
const m2 = await app.signup('m2@affine.pro');
|
const m2 = await app.signupV1('m2@affine.pro');
|
||||||
app.switchUser(owner);
|
app.switchUser(owner);
|
||||||
t.is(
|
t.is(
|
||||||
(await inviteUsers(app, ws.id, [m1.email])).length,
|
(await inviteUsers(app, ws.id, [m1.email])).length,
|
||||||
@@ -483,7 +483,7 @@ test('should be able to approve team member', async t => {
|
|||||||
const { link } = await createInviteLink(app, tws.id, 'OneDay');
|
const { link } = await createInviteLink(app, tws.id, 'OneDay');
|
||||||
const inviteId = link.split('/').pop()!;
|
const inviteId = link.split('/').pop()!;
|
||||||
|
|
||||||
const member = await app.signup('newmember@affine.pro');
|
const member = await app.signupV1('newmember@affine.pro');
|
||||||
t.true(
|
t.true(
|
||||||
await acceptInviteById(app, tws.id, inviteId, false),
|
await acceptInviteById(app, tws.id, inviteId, false),
|
||||||
'should be able to accept invite'
|
'should be able to accept invite'
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ test.after.always(async () => {
|
|||||||
test.skip('should register a user', () => {});
|
test.skip('should register a user', () => {});
|
||||||
|
|
||||||
test('should get current user', async t => {
|
test('should get current user', async t => {
|
||||||
const user = await app.signup('u1@affine.pro');
|
const user = await app.signupV1('u1@affine.pro');
|
||||||
const currUser = await currentUser(app);
|
const currUser = await currentUser(app);
|
||||||
t.is(currUser.id, user.id, 'user.id is not valid');
|
t.is(currUser.id, user.id, 'user.id is not valid');
|
||||||
t.is(currUser.name, user.name, 'user.name is not valid');
|
t.is(currUser.name, user.name, 'user.name is not valid');
|
||||||
@@ -34,7 +34,7 @@ test('should get current user', async t => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('should be able to delete user', async t => {
|
test('should be able to delete user', async t => {
|
||||||
await app.signup('u1@affine.pro');
|
await app.signupV1('u1@affine.pro');
|
||||||
const deleted = await deleteAccount(app);
|
const deleted = await deleteAccount(app);
|
||||||
t.true(deleted);
|
t.true(deleted);
|
||||||
const currUser = await currentUser(app);
|
const currUser = await currentUser(app);
|
||||||
|
|||||||
@@ -19,10 +19,6 @@ test.before(async t => {
|
|||||||
t.context.app = app;
|
t.context.app = app;
|
||||||
});
|
});
|
||||||
|
|
||||||
test.beforeEach(async t => {
|
|
||||||
await t.context.app.initTestingDB();
|
|
||||||
});
|
|
||||||
|
|
||||||
test.after.always(async t => {
|
test.after.always(async t => {
|
||||||
await t.context.app.close();
|
await t.context.app.close();
|
||||||
});
|
});
|
||||||
@@ -30,7 +26,7 @@ test.after.always(async t => {
|
|||||||
test('should be able to upload user avatar', async t => {
|
test('should be able to upload user avatar', async t => {
|
||||||
const { app } = t.context;
|
const { app } = t.context;
|
||||||
|
|
||||||
await app.signup('u1@affine.pro');
|
await app.signup();
|
||||||
const avatar = Buffer.from('test');
|
const avatar = Buffer.from('test');
|
||||||
const res = await updateAvatar(app, avatar);
|
const res = await updateAvatar(app, avatar);
|
||||||
|
|
||||||
@@ -46,7 +42,7 @@ test('should be able to upload user avatar', async t => {
|
|||||||
test('should be able to update user avatar, and invalidate old avatar url', async t => {
|
test('should be able to update user avatar, and invalidate old avatar url', async t => {
|
||||||
const { app } = t.context;
|
const { app } = t.context;
|
||||||
|
|
||||||
await app.signup('u1@affine.pro');
|
await app.signup();
|
||||||
const avatar = Buffer.from('test');
|
const avatar = Buffer.from('test');
|
||||||
let res = await updateAvatar(app, avatar);
|
let res = await updateAvatar(app, avatar);
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { randomUUID } from 'node:crypto';
|
|||||||
import { INestApplication, ModuleMetadata } from '@nestjs/common';
|
import { INestApplication, ModuleMetadata } from '@nestjs/common';
|
||||||
import type { NestExpressApplication } from '@nestjs/platform-express';
|
import type { NestExpressApplication } from '@nestjs/platform-express';
|
||||||
import { TestingModuleBuilder } from '@nestjs/testing';
|
import { TestingModuleBuilder } from '@nestjs/testing';
|
||||||
import { User } from '@prisma/client';
|
import { PrismaClient, User } from '@prisma/client';
|
||||||
import cookieParser from 'cookie-parser';
|
import cookieParser from 'cookie-parser';
|
||||||
import graphqlUploadExpress from 'graphql-upload/graphqlUploadExpress.mjs';
|
import graphqlUploadExpress from 'graphql-upload/graphqlUploadExpress.mjs';
|
||||||
import supertest from 'supertest';
|
import supertest from 'supertest';
|
||||||
@@ -11,6 +11,7 @@ import supertest from 'supertest';
|
|||||||
import { AFFiNELogger, ApplyType, GlobalExceptionFilter } from '../../base';
|
import { AFFiNELogger, ApplyType, GlobalExceptionFilter } from '../../base';
|
||||||
import { AuthService } from '../../core/auth';
|
import { AuthService } from '../../core/auth';
|
||||||
import { UserModel } from '../../models';
|
import { UserModel } from '../../models';
|
||||||
|
import { createFactory, MockedUser, MockUser, MockUserInput } from '../mocks';
|
||||||
import { createTestingModule } from './testing-module';
|
import { createTestingModule } from './testing-module';
|
||||||
import { initTestingDB, TEST_LOG_LEVEL } from './utils';
|
import { initTestingDB, TEST_LOG_LEVEL } from './utils';
|
||||||
|
|
||||||
@@ -80,6 +81,8 @@ export class TestingApp extends ApplyType<INestApplication>() {
|
|||||||
private currentUserCookie: string | null = null;
|
private currentUserCookie: string | null = null;
|
||||||
private readonly userCookies: Set<string> = new Set();
|
private readonly userCookies: Set<string> = new Set();
|
||||||
|
|
||||||
|
readonly create!: ReturnType<typeof createFactory>;
|
||||||
|
|
||||||
[Symbol.asyncDispose](): Promise<void> {
|
[Symbol.asyncDispose](): Promise<void> {
|
||||||
return this.close();
|
return this.close();
|
||||||
}
|
}
|
||||||
@@ -188,6 +191,9 @@ export class TestingApp extends ApplyType<INestApplication>() {
|
|||||||
return `test-${randomUUID()}@affine.pro`;
|
return `test-${randomUUID()}@affine.pro`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated use `create(MockUser)`
|
||||||
|
*/
|
||||||
async createUser(
|
async createUser(
|
||||||
email?: string,
|
email?: string,
|
||||||
override?: Partial<User>
|
override?: Partial<User>
|
||||||
@@ -209,13 +215,22 @@ export class TestingApp extends ApplyType<INestApplication>() {
|
|||||||
return user as Omit<User, 'password'> & { password: string };
|
return user as Omit<User, 'password'> & { password: string };
|
||||||
}
|
}
|
||||||
|
|
||||||
async signup(email?: string, override?: Partial<User>) {
|
/**
|
||||||
|
* @deprecated use `signup`
|
||||||
|
*/
|
||||||
|
async signupV1(email?: string, override?: Partial<User>) {
|
||||||
const user = await this.createUser(email ?? this.randomEmail(), override);
|
const user = await this.createUser(email ?? this.randomEmail(), override);
|
||||||
await this.login(user);
|
await this.login(user);
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
async login(user: TestUser) {
|
async signup(overrides?: Partial<MockUserInput>) {
|
||||||
|
const user = await this.create(MockUser, overrides);
|
||||||
|
await this.login(user);
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
async login(user: MockedUser) {
|
||||||
await this.POST('/api/auth/sign-in')
|
await this.POST('/api/auth/sign-in')
|
||||||
.send({
|
.send({
|
||||||
email: user.email,
|
email: user.email,
|
||||||
@@ -263,6 +278,9 @@ export class TestingApp extends ApplyType<INestApplication>() {
|
|||||||
function makeTestingApp(app: INestApplication): TestingApp {
|
function makeTestingApp(app: INestApplication): TestingApp {
|
||||||
const testingApp = new TestingApp();
|
const testingApp = new TestingApp();
|
||||||
|
|
||||||
|
// @ts-expect-error allow
|
||||||
|
testingApp.create = createFactory(app.get(PrismaClient, { strict: false }));
|
||||||
|
|
||||||
return new Proxy(testingApp, {
|
return new Proxy(testingApp, {
|
||||||
get(target, prop) {
|
get(target, prop) {
|
||||||
// @ts-expect-error override
|
// @ts-expect-error override
|
||||||
|
|||||||
@@ -6,12 +6,14 @@ import {
|
|||||||
TestingModule as BaseTestingModule,
|
TestingModule as BaseTestingModule,
|
||||||
TestingModuleBuilder,
|
TestingModuleBuilder,
|
||||||
} from '@nestjs/testing';
|
} from '@nestjs/testing';
|
||||||
|
import { PrismaClient } from '@prisma/client';
|
||||||
|
|
||||||
import { AppModule, FunctionalityModules } from '../../app.module';
|
import { AppModule, FunctionalityModules } from '../../app.module';
|
||||||
import { AFFiNELogger, Runtime } from '../../base';
|
import { AFFiNELogger, Runtime } from '../../base';
|
||||||
import { GqlModule } from '../../base/graphql';
|
import { GqlModule } from '../../base/graphql';
|
||||||
import { AuthGuard, AuthModule } from '../../core/auth';
|
import { AuthGuard, AuthModule } from '../../core/auth';
|
||||||
import { ModelsModule } from '../../models';
|
import { ModelsModule } from '../../models';
|
||||||
|
import { createFactory } from '../mocks';
|
||||||
import { initTestingDB, TEST_LOG_LEVEL } from './utils';
|
import { initTestingDB, TEST_LOG_LEVEL } from './utils';
|
||||||
|
|
||||||
interface TestingModuleMeatdata extends ModuleMetadata {
|
interface TestingModuleMeatdata extends ModuleMetadata {
|
||||||
@@ -20,6 +22,7 @@ interface TestingModuleMeatdata extends ModuleMetadata {
|
|||||||
|
|
||||||
export interface TestingModule extends BaseTestingModule {
|
export interface TestingModule extends BaseTestingModule {
|
||||||
initTestingDB(): Promise<void>;
|
initTestingDB(): Promise<void>;
|
||||||
|
create: ReturnType<typeof createFactory>;
|
||||||
[Symbol.asyncDispose](): Promise<void>;
|
[Symbol.asyncDispose](): Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,6 +94,10 @@ export async function createTestingModule(
|
|||||||
await runtime.set('auth/password.min', 1);
|
await runtime.set('auth/password.min', 1);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
testingModule.create = createFactory(
|
||||||
|
module.get(PrismaClient, { strict: false })
|
||||||
|
);
|
||||||
|
|
||||||
testingModule[Symbol.asyncDispose] = async () => {
|
testingModule[Symbol.asyncDispose] = async () => {
|
||||||
await module.close();
|
await module.close();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -47,8 +47,8 @@ test.after.always(async t => {
|
|||||||
|
|
||||||
test('should invite a user', async t => {
|
test('should invite a user', async t => {
|
||||||
const { app } = t.context;
|
const { app } = t.context;
|
||||||
const u2 = await app.signup('u2@affine.pro');
|
const u2 = await app.signupV1('u2@affine.pro');
|
||||||
await app.signup('u1@affine.pro');
|
await app.signupV1('u1@affine.pro');
|
||||||
|
|
||||||
const workspace = await createWorkspace(app);
|
const workspace = await createWorkspace(app);
|
||||||
|
|
||||||
@@ -58,8 +58,8 @@ test('should invite a user', async t => {
|
|||||||
|
|
||||||
test('should leave a workspace', async t => {
|
test('should leave a workspace', async t => {
|
||||||
const { app } = t.context;
|
const { app } = t.context;
|
||||||
const u2 = await app.signup('u2@affine.pro');
|
const u2 = await app.signupV1('u2@affine.pro');
|
||||||
await app.signup('u1@affine.pro');
|
await app.signupV1('u1@affine.pro');
|
||||||
|
|
||||||
const workspace = await createWorkspace(app);
|
const workspace = await createWorkspace(app);
|
||||||
const invite = await inviteUser(app, workspace.id, u2.email);
|
const invite = await inviteUser(app, workspace.id, u2.email);
|
||||||
@@ -74,8 +74,8 @@ test('should leave a workspace', async t => {
|
|||||||
|
|
||||||
test('should revoke a user', async t => {
|
test('should revoke a user', async t => {
|
||||||
const { app } = t.context;
|
const { app } = t.context;
|
||||||
const u2 = await app.signup('u2@affine.pro');
|
const u2 = await app.signupV1('u2@affine.pro');
|
||||||
await app.signup('u1@affine.pro');
|
await app.signupV1('u1@affine.pro');
|
||||||
|
|
||||||
const workspace = await createWorkspace(app);
|
const workspace = await createWorkspace(app);
|
||||||
await inviteUser(app, workspace.id, u2.email);
|
await inviteUser(app, workspace.id, u2.email);
|
||||||
@@ -89,7 +89,7 @@ test('should revoke a user', async t => {
|
|||||||
|
|
||||||
test('should create user if not exist', async t => {
|
test('should create user if not exist', async t => {
|
||||||
const { app, models } = t.context;
|
const { app, models } = t.context;
|
||||||
await app.signup('u1@affine.pro');
|
await app.signupV1('u1@affine.pro');
|
||||||
|
|
||||||
const workspace = await createWorkspace(app);
|
const workspace = await createWorkspace(app);
|
||||||
|
|
||||||
@@ -102,8 +102,8 @@ test('should create user if not exist', async t => {
|
|||||||
|
|
||||||
test('should invite a user by link', async t => {
|
test('should invite a user by link', async t => {
|
||||||
const { app } = t.context;
|
const { app } = t.context;
|
||||||
const u2 = await app.signup('u2@affine.pro');
|
const u2 = await app.signupV1('u2@affine.pro');
|
||||||
const u1 = await app.signup('u1@affine.pro');
|
const u1 = await app.signupV1('u1@affine.pro');
|
||||||
|
|
||||||
const workspace = await createWorkspace(app);
|
const workspace = await createWorkspace(app);
|
||||||
|
|
||||||
@@ -127,8 +127,8 @@ test('should invite a user by link', async t => {
|
|||||||
test('should send email', async t => {
|
test('should send email', async t => {
|
||||||
const { mail, app } = t.context;
|
const { mail, app } = t.context;
|
||||||
if (mail.hasConfigured()) {
|
if (mail.hasConfigured()) {
|
||||||
const u2 = await app.signup('u2@affine.pro');
|
const u2 = await app.signupV1('u2@affine.pro');
|
||||||
await app.signup('u1@affine.pro');
|
await app.signupV1('u1@affine.pro');
|
||||||
|
|
||||||
const workspace = await createWorkspace(app);
|
const workspace = await createWorkspace(app);
|
||||||
const primitiveMailCount = await getCurrentMailMessageCount();
|
const primitiveMailCount = await getCurrentMailMessageCount();
|
||||||
@@ -193,7 +193,7 @@ test('should send email', async t => {
|
|||||||
|
|
||||||
test('should support pagination for member', async t => {
|
test('should support pagination for member', async t => {
|
||||||
const { app } = t.context;
|
const { app } = t.context;
|
||||||
await app.signup('u1@affine.pro');
|
await app.signupV1('u1@affine.pro');
|
||||||
|
|
||||||
const workspace = await createWorkspace(app);
|
const workspace = await createWorkspace(app);
|
||||||
await inviteUser(app, workspace.id, 'u2@affine.pro');
|
await inviteUser(app, workspace.id, 'u2@affine.pro');
|
||||||
@@ -207,7 +207,7 @@ test('should support pagination for member', async t => {
|
|||||||
|
|
||||||
test('should limit member count correctly', async t => {
|
test('should limit member count correctly', async t => {
|
||||||
const { app } = t.context;
|
const { app } = t.context;
|
||||||
await app.signup('u1@affine.pro');
|
await app.signupV1('u1@affine.pro');
|
||||||
|
|
||||||
const workspace = await createWorkspace(app);
|
const workspace = await createWorkspace(app);
|
||||||
await Promise.allSettled(
|
await Promise.allSettled(
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ test.after.always(async t => {
|
|||||||
test('should create a workspace', async t => {
|
test('should create a workspace', async t => {
|
||||||
const { app } = t.context;
|
const { app } = t.context;
|
||||||
|
|
||||||
await app.signup('u1@affine.pro');
|
await app.signupV1('u1@affine.pro');
|
||||||
const workspace = await createWorkspace(app);
|
const workspace = await createWorkspace(app);
|
||||||
|
|
||||||
t.is(typeof workspace.id, 'string', 'workspace.id is not a string');
|
t.is(typeof workspace.id, 'string', 'workspace.id is not a string');
|
||||||
@@ -45,7 +45,7 @@ test('should create a workspace', async t => {
|
|||||||
|
|
||||||
test('should be able to publish workspace', async t => {
|
test('should be able to publish workspace', async t => {
|
||||||
const { app } = t.context;
|
const { app } = t.context;
|
||||||
await app.signup('u1@affine.pro');
|
await app.signupV1('u1@affine.pro');
|
||||||
const workspace = await createWorkspace(app);
|
const workspace = await createWorkspace(app);
|
||||||
const isPublic = await updateWorkspace(app, workspace.id, true);
|
const isPublic = await updateWorkspace(app, workspace.id, true);
|
||||||
|
|
||||||
@@ -58,7 +58,7 @@ test('should be able to publish workspace', async t => {
|
|||||||
|
|
||||||
test('should visit public page', async t => {
|
test('should visit public page', async t => {
|
||||||
const { app } = t.context;
|
const { app } = t.context;
|
||||||
await app.signup('u1@affine.pro');
|
await app.signupV1('u1@affine.pro');
|
||||||
|
|
||||||
const workspace = await createWorkspace(app);
|
const workspace = await createWorkspace(app);
|
||||||
const share = await publishDoc(app, workspace.id, 'doc1');
|
const share = await publishDoc(app, workspace.id, 'doc1');
|
||||||
@@ -104,7 +104,7 @@ test('should visit public page', async t => {
|
|||||||
test('should not be able to public not permitted doc', async t => {
|
test('should not be able to public not permitted doc', async t => {
|
||||||
const { app } = t.context;
|
const { app } = t.context;
|
||||||
|
|
||||||
await app.signup('u2@affine.pro');
|
await app.signupV1('u2@affine.pro');
|
||||||
|
|
||||||
await t.throwsAsync(publishDoc(app, 'not_exists_ws', 'doc2'), {
|
await t.throwsAsync(publishDoc(app, 'not_exists_ws', 'doc2'), {
|
||||||
message:
|
message:
|
||||||
@@ -119,8 +119,8 @@ test('should not be able to public not permitted doc', async t => {
|
|||||||
|
|
||||||
test('should be able to get workspace doc', async t => {
|
test('should be able to get workspace doc', async t => {
|
||||||
const { app } = t.context;
|
const { app } = t.context;
|
||||||
const u1 = await app.signup('u1@affine.pro');
|
const u1 = await app.signupV1('u1@affine.pro');
|
||||||
const u2 = await app.signup('u2@affine.pro');
|
const u2 = await app.signupV1('u2@affine.pro');
|
||||||
|
|
||||||
app.switchUser(u1.id);
|
app.switchUser(u1.id);
|
||||||
const workspace = await createWorkspace(app);
|
const workspace = await createWorkspace(app);
|
||||||
@@ -167,7 +167,7 @@ test('should be able to get workspace doc', async t => {
|
|||||||
|
|
||||||
test('should be able to get public workspace doc', async t => {
|
test('should be able to get public workspace doc', async t => {
|
||||||
const { app } = t.context;
|
const { app } = t.context;
|
||||||
await app.signup('u1@affine.pro');
|
await app.signupV1('u1@affine.pro');
|
||||||
|
|
||||||
const workspace = await createWorkspace(app);
|
const workspace = await createWorkspace(app);
|
||||||
const isPublic = await updateWorkspace(app, workspace.id, true);
|
const isPublic = await updateWorkspace(app, workspace.id, true);
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ test.after.always(async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('should set blobs', async t => {
|
test('should set blobs', async t => {
|
||||||
await app.signup('u1@affine.pro');
|
await app.signupV1('u1@affine.pro');
|
||||||
|
|
||||||
const workspace = await createWorkspace(app);
|
const workspace = await createWorkspace(app);
|
||||||
|
|
||||||
@@ -63,7 +63,7 @@ test('should set blobs', async t => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('should list blobs', async t => {
|
test('should list blobs', async t => {
|
||||||
await app.signup('u1@affine.pro');
|
await app.signupV1('u1@affine.pro');
|
||||||
|
|
||||||
const workspace = await createWorkspace(app);
|
const workspace = await createWorkspace(app);
|
||||||
const blobs = await listBlobs(app, workspace.id);
|
const blobs = await listBlobs(app, workspace.id);
|
||||||
@@ -81,7 +81,7 @@ test('should list blobs', async t => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('should auto delete blobs when workspace is deleted', async t => {
|
test('should auto delete blobs when workspace is deleted', async t => {
|
||||||
await app.signup('u1@affine.pro');
|
await app.signupV1('u1@affine.pro');
|
||||||
|
|
||||||
const workspace = await createWorkspace(app);
|
const workspace = await createWorkspace(app);
|
||||||
const buffer1 = Buffer.from([0, 0]);
|
const buffer1 = Buffer.from([0, 0]);
|
||||||
@@ -100,7 +100,7 @@ test('should auto delete blobs when workspace is deleted', async t => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('should calc blobs size', async t => {
|
test('should calc blobs size', async t => {
|
||||||
await app.signup('u1@affine.pro');
|
await app.signupV1('u1@affine.pro');
|
||||||
|
|
||||||
const workspace = await createWorkspace(app);
|
const workspace = await createWorkspace(app);
|
||||||
|
|
||||||
@@ -114,7 +114,7 @@ test('should calc blobs size', async t => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('should calc all blobs size', async t => {
|
test('should calc all blobs size', async t => {
|
||||||
await app.signup('u1@affine.pro');
|
await app.signupV1('u1@affine.pro');
|
||||||
|
|
||||||
const workspace1 = await createWorkspace(app);
|
const workspace1 = await createWorkspace(app);
|
||||||
|
|
||||||
@@ -135,7 +135,7 @@ test('should calc all blobs size', async t => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('should reject blob exceeded limit', async t => {
|
test('should reject blob exceeded limit', async t => {
|
||||||
await app.signup('u1@affine.pro');
|
await app.signupV1('u1@affine.pro');
|
||||||
|
|
||||||
const workspace1 = await createWorkspace(app);
|
const workspace1 = await createWorkspace(app);
|
||||||
await model.add(workspace1.id, 'team_plan_v1', 'test', RESTRICTED_QUOTA);
|
await model.add(workspace1.id, 'team_plan_v1', 'test', RESTRICTED_QUOTA);
|
||||||
@@ -147,7 +147,7 @@ test('should reject blob exceeded limit', async t => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('should reject blob exceeded quota', async t => {
|
test('should reject blob exceeded quota', async t => {
|
||||||
await app.signup('u1@affine.pro');
|
await app.signupV1('u1@affine.pro');
|
||||||
|
|
||||||
const workspace = await createWorkspace(app);
|
const workspace = await createWorkspace(app);
|
||||||
await model.add(workspace.id, 'team_plan_v1', 'test', RESTRICTED_QUOTA);
|
await model.add(workspace.id, 'team_plan_v1', 'test', RESTRICTED_QUOTA);
|
||||||
@@ -159,7 +159,7 @@ test('should reject blob exceeded quota', async t => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('should accept blob even storage out of quota if workspace has unlimited feature', async t => {
|
test('should accept blob even storage out of quota if workspace has unlimited feature', async t => {
|
||||||
await app.signup('u1@affine.pro');
|
await app.signupV1('u1@affine.pro');
|
||||||
|
|
||||||
const workspace = await createWorkspace(app);
|
const workspace = await createWorkspace(app);
|
||||||
await model.add(workspace.id, 'team_plan_v1', 'test', RESTRICTED_QUOTA);
|
await model.add(workspace.id, 'team_plan_v1', 'test', RESTRICTED_QUOTA);
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ test.before(async t => {
|
|||||||
|
|
||||||
const db = app.get(PrismaClient);
|
const db = app.get(PrismaClient);
|
||||||
|
|
||||||
t.context.u1 = await app.signup('u1@affine.pro');
|
t.context.u1 = await app.signupV1('u1@affine.pro');
|
||||||
t.context.db = db;
|
t.context.db = db;
|
||||||
t.context.app = app;
|
t.context.app = app;
|
||||||
t.context.storage = app.get(WorkspaceBlobStorage);
|
t.context.storage = app.get(WorkspaceBlobStorage);
|
||||||
|
|||||||
@@ -29,8 +29,8 @@ test.after.always(async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('should mention user in a doc', async t => {
|
test('should mention user in a doc', async t => {
|
||||||
const member = await app.signup();
|
const member = await app.signupV1();
|
||||||
const owner = await app.signup();
|
const owner = await app.signupV1();
|
||||||
|
|
||||||
await app.switchUser(owner);
|
await app.switchUser(owner);
|
||||||
const workspace = await createWorkspace(app);
|
const workspace = await createWorkspace(app);
|
||||||
@@ -161,8 +161,8 @@ test('should mention doc mode support string value', async t => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('should throw error when mention user has no Doc.Read role', async t => {
|
test('should throw error when mention user has no Doc.Read role', async t => {
|
||||||
const member = await app.signup();
|
const member = await app.signupV1();
|
||||||
const owner = await app.signup();
|
const owner = await app.signupV1();
|
||||||
|
|
||||||
await app.switchUser(owner);
|
await app.switchUser(owner);
|
||||||
const workspace = await createWorkspace(app);
|
const workspace = await createWorkspace(app);
|
||||||
@@ -187,7 +187,7 @@ test('should throw error when mention user has no Doc.Read role', async t => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('should throw error when mention a not exists user', async t => {
|
test('should throw error when mention a not exists user', async t => {
|
||||||
const owner = await app.signup();
|
const owner = await app.signupV1();
|
||||||
const workspace = await createWorkspace(app);
|
const workspace = await createWorkspace(app);
|
||||||
await app.switchUser(owner);
|
await app.switchUser(owner);
|
||||||
const docId = randomUUID();
|
const docId = randomUUID();
|
||||||
@@ -209,7 +209,7 @@ test('should throw error when mention a not exists user', async t => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('should not mention user oneself', async t => {
|
test('should not mention user oneself', async t => {
|
||||||
const owner = await app.signup();
|
const owner = await app.signupV1();
|
||||||
const workspace = await createWorkspace(app);
|
const workspace = await createWorkspace(app);
|
||||||
await app.switchUser(owner);
|
await app.switchUser(owner);
|
||||||
await t.throwsAsync(
|
await t.throwsAsync(
|
||||||
@@ -230,8 +230,8 @@ test('should not mention user oneself', async t => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('should mark notification as read', async t => {
|
test('should mark notification as read', async t => {
|
||||||
const member = await app.signup();
|
const member = await app.signupV1();
|
||||||
const owner = await app.signup();
|
const owner = await app.signupV1();
|
||||||
|
|
||||||
await app.switchUser(owner);
|
await app.switchUser(owner);
|
||||||
const workspace = await createWorkspace(app);
|
const workspace = await createWorkspace(app);
|
||||||
@@ -273,8 +273,8 @@ test('should mark notification as read', async t => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('should throw error when read the other user notification', async t => {
|
test('should throw error when read the other user notification', async t => {
|
||||||
const member = await app.signup();
|
const member = await app.signupV1();
|
||||||
const owner = await app.signup();
|
const owner = await app.signupV1();
|
||||||
|
|
||||||
await app.switchUser(owner);
|
await app.switchUser(owner);
|
||||||
const workspace = await createWorkspace(app);
|
const workspace = await createWorkspace(app);
|
||||||
@@ -356,8 +356,8 @@ test('should throw error when mention mode value is invalid', async t => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('should list and count notifications', async t => {
|
test('should list and count notifications', async t => {
|
||||||
const member = await app.signup();
|
const member = await app.signupV1();
|
||||||
const owner = await app.signup();
|
const owner = await app.signupV1();
|
||||||
|
|
||||||
{
|
{
|
||||||
await app.switchUser(member);
|
await app.switchUser(member);
|
||||||
|
|||||||
88
packages/backend/server/src/seed/index.ts
Normal file
88
packages/backend/server/src/seed/index.ts
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
import '../prelude';
|
||||||
|
|
||||||
|
import { PrismaClient } from '@prisma/client';
|
||||||
|
|
||||||
|
import { createFactory, Mockers } from '../__tests__/mocks';
|
||||||
|
|
||||||
|
const client = new PrismaClient();
|
||||||
|
|
||||||
|
const args = process.argv.slice(2);
|
||||||
|
|
||||||
|
if (!args.length || args.includes('-h') || args.includes('--help')) {
|
||||||
|
console.log(`
|
||||||
|
seed [Entity] [count] [[field]=[val]]
|
||||||
|
|
||||||
|
Checkout [server/src/__tests__/mocks/*.mock.ts] for all available Entities and Inputs
|
||||||
|
|
||||||
|
examples:
|
||||||
|
|
||||||
|
$ seed User Create an User
|
||||||
|
$ seed User 3 Create 3 Users
|
||||||
|
$ seed User feature=pro_plan_v1 Create an User with Pro feature
|
||||||
|
$ seed TeamWorkspace id=xxx Seed a workspace with Team feature
|
||||||
|
$ seed Workspace id=xxx public=true Seed with boolean property
|
||||||
|
$ seed TeamWorkspace id=xxx quantity=10n Seed with numberic property, use \`={num}n\` suffix
|
||||||
|
`);
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
const name = args.shift() as keyof typeof Mockers;
|
||||||
|
const Mocker = Mockers[name];
|
||||||
|
|
||||||
|
if (!name || !Mocker) {
|
||||||
|
throw new Error(
|
||||||
|
'First argument must be one of: ' + JSON.stringify(Object.keys(Mockers))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const create = createFactory(client, {
|
||||||
|
logger: (val: any) => {
|
||||||
|
console.log(`${name} ${JSON.stringify(val)}`);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
function parseArgs(args: string[]) {
|
||||||
|
if (!args.length) {
|
||||||
|
return { count: 1 };
|
||||||
|
}
|
||||||
|
|
||||||
|
const overrides: Record<string, any> = {};
|
||||||
|
let count: number = 1;
|
||||||
|
|
||||||
|
args.forEach(arg => {
|
||||||
|
let kvSep = arg.indexOf('=');
|
||||||
|
if (kvSep) {
|
||||||
|
const key = arg.slice(0, kvSep);
|
||||||
|
const val = arg.slice(kvSep + 1);
|
||||||
|
|
||||||
|
if (/[\d]+n$/.test(val)) {
|
||||||
|
const num = Number(val.slice(0, -1));
|
||||||
|
if (Number.isNaN(num)) {
|
||||||
|
throw new Error(`Invalid numeric parameter: ${arg}`);
|
||||||
|
}
|
||||||
|
overrides[key] = num;
|
||||||
|
} else if (val.length === 4 && val.toLowerCase() === 'true') {
|
||||||
|
overrides[key] = true;
|
||||||
|
} else if (val.length === 5 && val.toLowerCase() === 'false') {
|
||||||
|
overrides[key] = false;
|
||||||
|
} else {
|
||||||
|
overrides[key] = val;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const maybeCount = parseInt(arg);
|
||||||
|
if (!maybeCount || Number.isNaN(maybeCount)) {
|
||||||
|
console.warn(`Invalid parameter: ${arg}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
count = maybeCount;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
overrides,
|
||||||
|
count,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const { overrides, count } = parseArgs(args);
|
||||||
|
await create(Mocker, overrides as any, count);
|
||||||
@@ -834,6 +834,7 @@ __metadata:
|
|||||||
"@affine/server-native": "workspace:*"
|
"@affine/server-native": "workspace:*"
|
||||||
"@apollo/server": "npm:^4.11.2"
|
"@apollo/server": "npm:^4.11.2"
|
||||||
"@aws-sdk/client-s3": "npm:^3.709.0"
|
"@aws-sdk/client-s3": "npm:^3.709.0"
|
||||||
|
"@faker-js/faker": "npm:^9.6.0"
|
||||||
"@fal-ai/serverless-client": "npm:^0.15.0"
|
"@fal-ai/serverless-client": "npm:^0.15.0"
|
||||||
"@google-cloud/opentelemetry-cloud-monitoring-exporter": "npm:^0.20.0"
|
"@google-cloud/opentelemetry-cloud-monitoring-exporter": "npm:^0.20.0"
|
||||||
"@google-cloud/opentelemetry-cloud-trace-exporter": "npm:^2.4.1"
|
"@google-cloud/opentelemetry-cloud-trace-exporter": "npm:^2.4.1"
|
||||||
@@ -5105,7 +5106,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@faker-js/faker@npm:^9.3.0":
|
"@faker-js/faker@npm:^9.3.0, @faker-js/faker@npm:^9.6.0":
|
||||||
version: 9.6.0
|
version: 9.6.0
|
||||||
resolution: "@faker-js/faker@npm:9.6.0"
|
resolution: "@faker-js/faker@npm:9.6.0"
|
||||||
checksum: 10/f53aeca972a16e7cbb26024c457cea7e1c6bff9dd60561f942a48eb3233863ed7b5fbb1392eb98a0901cb5bea23cf7dbb0793a30c1655478c6a76a43ebb6c360
|
checksum: 10/f53aeca972a16e7cbb26024c457cea7e1c6bff9dd60561f942a48eb3233863ed7b5fbb1392eb98a0901cb5bea23cf7dbb0793a30c1655478c6a76a43ebb6c360
|
||||||
|
|||||||
Reference in New Issue
Block a user