import { randomUUID } from 'node:crypto'; import type { INestApplication } from '@nestjs/common'; import { Test } from '@nestjs/testing'; import { hashSync } from '@node-rs/argon2'; import { type User } from '@prisma/client'; import ava, { type TestFn } from 'ava'; import graphqlUploadExpress from 'graphql-upload/graphqlUploadExpress.mjs'; import { AppModule } from '../src/app'; import { MailService } from '../src/modules/auth/mailer'; import { FeatureManagementService } from '../src/modules/features'; import { PrismaService } from '../src/prisma'; import { createWorkspace, getInviteInfo, inviteUser, signUp } from './utils'; const FakePrisma = { fakeUser: { id: randomUUID(), name: 'Alex Yang', avatarUrl: '', email: 'alex.yang@example.org', password: hashSync('123456'), emailVerified: new Date(), createdAt: new Date(), } satisfies User, get user() { // eslint-disable-next-line @typescript-eslint/no-this-alias const prisma = this; return { async findFirst() { return null; }, async create({ data }: any) { return { ...prisma.fakeUser, ...data, }; }, async findUnique() { return prisma.fakeUser; }, }; }, get workspace() { return { id: randomUUID(), async create({ data }: any) { return { id: this.id, public: data.public ?? false, createdAt: new Date(), }; }, }; }, snapshot: { id: randomUUID(), async create() {}, async findFirstOrThrow() { return { id: this.id, blob: Buffer.from([0, 0]) }; }, }, get workspaceUserPermission() { return { id: randomUUID(), prisma: this, async count() { return 1; }, async create() { return { id: this.id }; }, async findUniqueOrThrow() { return { id: this.id, workspaceId: this.prisma.workspace.id }; }, async findFirst() { return { id: this.id }; }, async findFirstOrThrow() { return { id: this.id, user: this.prisma.fakeUser }; }, async workspaceUserPermission() { return { id: randomUUID(), createdAt: new Date(), }; }, }; }, }; const test = ava as TestFn<{ app: INestApplication; mail: MailService; }>; test.beforeEach(async t => { const module = await Test.createTestingModule({ imports: [AppModule], }) .overrideProvider(PrismaService) .useValue(FakePrisma) .overrideProvider(FeatureManagementService) .useValue({}) .compile(); const app = module.createNestApplication(); app.use( graphqlUploadExpress({ maxFileSize: 10 * 1024 * 1024, maxFiles: 5, }) ); await app.init(); const mail = module.get(MailService); t.context.app = app; t.context.mail = mail; }); test.afterEach.always(async t => { await t.context.app.close(); }); test('should send invite email', async t => { const { mail, app } = t.context; 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 || '', }, } ); t.is(resp.accepted.length, 1, 'failed to send invite email'); } t.pass(); });