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 { Express } from 'express'; // @ts-expect-error graphql-upload is not typed import graphqlUploadExpress from 'graphql-upload/graphqlUploadExpress.mjs'; import request from 'supertest'; import { AppModule } from '../app'; import { getDefaultAFFiNEConfig } from '../config/default'; const gql = '/graphql'; globalThis.AFFiNE = getDefaultAFFiNEConfig(); describe('AppModule', () => { 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'), }, }); }); 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('should init app', async () => { ok(typeof app === 'object'); await request(app.getHttpServer()) .post(gql) .send({ query: ` query { error } `, }) .expect(400); const { token } = await createToken(app); await request(app.getHttpServer()) .post(gql) .auth(token, { type: 'bearer' }) .send({ query: ` mutation { createWorkspace { id public createdAt } } `, }) .expect(200) .expect(res => { ok( typeof res.body.data.createWorkspace === 'object', 'res.body.data.createWorkspace is not an object' ); ok( typeof res.body.data.createWorkspace.id === 'string', 'res.body.data.createWorkspace.id is not a string' ); ok( typeof res.body.data.createWorkspace.public === 'boolean', 'res.body.data.createWorkspace.public is not a boolean' ); ok( typeof res.body.data.createWorkspace.createdAt === 'string', 'res.body.data.createWorkspace.created_at is not a string' ); }); }); 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 avatarUrl } } `, }) .expect(200) .expect(res => { equal(res.body.data.user.email, 'alex.yang@example.org'); }); }); 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!) { uploadAvatar(id: $id, avatar: $avatar) { id name avatarUrl email } } `, 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): Promise<{ id: string; token: string; }> { let token; let id; await request(app.getHttpServer()) .post(gql) .send({ query: ` mutation { signIn(email: "alex.yang@example.org", password: "123456") { id token { token } } } `, }) .expect(200) .expect(res => { id = res.body.data.signIn.id; ok( typeof res.body.data.signIn.token.token === 'string', 'res.body.data.signIn.token.token is not a string' ); token = res.body.data.signIn.token.token; }); return { token: token!, id: id! }; }