From e34e3b30204d155002000a311f43efad71fd09c9 Mon Sep 17 00:00:00 2001 From: Yuren Ju Date: Thu, 21 Sep 2023 10:04:31 +0800 Subject: [PATCH 1/2] test: added tests for national module --- .../src/national/national.controller.ts | 2 + apps/server/src/national/national.e2e-spec.ts | 61 +++++++++++++------ apps/server/tests/helper.ts | 23 +++++++ apps/server/tsconfig.spec.json | 1 + 4 files changed, 70 insertions(+), 17 deletions(-) create mode 100644 apps/server/tests/helper.ts diff --git a/apps/server/src/national/national.controller.ts b/apps/server/src/national/national.controller.ts index c19f9b3..44318ec 100644 --- a/apps/server/src/national/national.controller.ts +++ b/apps/server/src/national/national.controller.ts @@ -10,6 +10,7 @@ import { UsersService } from '../user/user.service'; import { NationalService } from './national.service'; import { RegisterNationalDto } from './register-national.dto'; import { LocalAuthGuard } from './guards/local-auth.guard'; +import { JwtAuthGuard } from './guards/jwt-auth.guard'; @Controller('auth/national') export class NationalController { @@ -31,6 +32,7 @@ export class NationalController { return this.nationalService.login(user); } + @UseGuards(JwtAuthGuard) @Get('check') check(@Request() req) { const { user } = req; diff --git a/apps/server/src/national/national.e2e-spec.ts b/apps/server/src/national/national.e2e-spec.ts index 2b678e5..029419b 100644 --- a/apps/server/src/national/national.e2e-spec.ts +++ b/apps/server/src/national/national.e2e-spec.ts @@ -1,36 +1,63 @@ -import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import request from 'supertest'; import { MongoMemoryServer } from 'mongodb-memory-server'; -import { MongooseModule } from '@nestjs/mongoose'; -import { UsersModule } from '../user/user.module'; -import { AuthModule } from '../auth/auth.module'; +import { setupMongoDb, setupTestApplication } from '../../tests/helper'; describe('NationalModule', () => { let app: INestApplication; let mongod: MongoMemoryServer; - beforeAll(async () => { - mongod = await MongoMemoryServer.create(); - const uri = mongod.getUri(); - - const testModule: TestingModule = await Test.createTestingModule({ - imports: [MongooseModule.forRoot(uri), UsersModule, AuthModule], - }).compile(); - - app = testModule.createNestApplication(); - await app.init(); + beforeEach(async () => { + mongod = await setupMongoDb(); + app = await setupTestApplication(mongod.getUri()); }); - afterAll(async () => { + afterEach(async () => { await mongod.stop(); await app.close(); }); it('should register a new user', async () => { - return request(app.getHttpServer()) - .post('/auth/national/register') + const server = app.getHttpServer(); + const res = await request(server) + .post('/api/auth/national/register') .send({ username: 'username', password: 'password' }) .expect(201); + + return request(server) + .get(`/api/users/${res.body.id}`) + .set('Authorization', `Bearer ${res.body.token}`) + .expect(200); + }); + + it('should login an existing user', async () => { + const server = app.getHttpServer(); + + await request(server) + .post('/api/auth/national/register') + .send({ username: 'username', password: 'password' }); + + await request(server) + .post('/api/auth/national/login') + .send({ username: 'username', password: 'password' }) + .expect(201) + .expect((res) => { + expect(res.body).toHaveProperty('token'); + }); + }); + + it('should check an existing user', async () => { + const server = app.getHttpServer(); + const res = await request(server) + .post('/api/auth/national/register') + .send({ username: 'username', password: 'password' }); + + await request(server) + .get('/api/auth/national/check') + .set('Authorization', `Bearer ${res.body.token}`) + .expect(200) + .expect((res) => { + expect(res.body).toHaveProperty('user'); + }); }); }); diff --git a/apps/server/tests/helper.ts b/apps/server/tests/helper.ts new file mode 100644 index 0000000..603bb59 --- /dev/null +++ b/apps/server/tests/helper.ts @@ -0,0 +1,23 @@ +import { INestApplication } from '@nestjs/common'; +import { MongooseModule } from '@nestjs/mongoose'; +import { Test, TestingModule } from '@nestjs/testing'; +import { MongoMemoryServer } from 'mongodb-memory-server'; +import { UsersModule } from '../src/user/user.module'; +import { AuthModule } from '../src/auth/auth.module'; + +export function setupMongoDb(): Promise { + return MongoMemoryServer.create(); +} + +export async function setupTestApplication( + mongoUri: string +): Promise { + const testModule: TestingModule = await Test.createTestingModule({ + imports: [MongooseModule.forRoot(mongoUri), UsersModule, AuthModule], + }).compile(); + + const app = testModule.createNestApplication(); + app.setGlobalPrefix('api'); + await app.init(); + return app; +} diff --git a/apps/server/tsconfig.spec.json b/apps/server/tsconfig.spec.json index 141fdf2..7807cbc 100644 --- a/apps/server/tsconfig.spec.json +++ b/apps/server/tsconfig.spec.json @@ -7,6 +7,7 @@ }, "include": [ "jest.config.ts", + "tests/**.ts", "src/**/*.test.ts", "src/**/*.spec.ts", "src/**/*.e2e-spec.ts", From d43148a3bf80b0720fba6fc4c68da339e1872ee0 Mon Sep 17 00:00:00 2001 From: Yuren Ju Date: Thu, 21 Sep 2023 11:26:24 +0800 Subject: [PATCH 2/2] fix: added test cases for ethereum module --- apps/server/src/ethereum/ethereum.e2e-spec.ts | 116 ++++++++++++++++++ apps/server/src/ethereum/nonce.service.ts | 7 +- 2 files changed, 122 insertions(+), 1 deletion(-) create mode 100644 apps/server/src/ethereum/ethereum.e2e-spec.ts diff --git a/apps/server/src/ethereum/ethereum.e2e-spec.ts b/apps/server/src/ethereum/ethereum.e2e-spec.ts new file mode 100644 index 0000000..d18373a --- /dev/null +++ b/apps/server/src/ethereum/ethereum.e2e-spec.ts @@ -0,0 +1,116 @@ +import { INestApplication } from '@nestjs/common'; +import request from 'supertest'; +import { MongoMemoryServer } from 'mongodb-memory-server'; +import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts'; +import { SiweMessage } from 'siwe'; +import { setupMongoDb, setupTestApplication } from '../../tests/helper'; +import { getRandomHexString } from '../utils'; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function getDomain(server: any): string { + server.listen(); + const address = server.address(); + return `127.0.0.1:${address.port}`; +} + +function generateSiweMessage(domain: string, address: string, nonce: string) { + const rawMessage = new SiweMessage({ + domain, + address, + statement: 'Sign in with Ethereum to the app.', + uri: `http://${domain}`, + version: '1', + chainId: 1, + nonce, + }); + const message = rawMessage.prepareMessage() as `0x${string}`; + + return message; +} + +describe('EthereumModule', () => { + let app: INestApplication; + let mongod: MongoMemoryServer; + + beforeEach(async () => { + mongod = await setupMongoDb(); + app = await setupTestApplication(mongod.getUri()); + }); + + afterEach(async () => { + await mongod.stop(); + await app.close(); + }); + + it('should login with an ethereum account', async () => { + const server = app.getHttpServer(); + const domain = getDomain(server); + + const res = await request(server) + .post('/api/auth/national/register') + .send({ username: 'username', password: 'password' }); + + const { id, token } = res.body; + + const challengeRes = await request(server) + .post('/api/auth/ethereum/challenge') + .set('Authorization', `Bearer ${token}`) + .expect(201); + + const privateKey = generatePrivateKey(); + const account = privateKeyToAccount(privateKey); + const message = generateSiweMessage( + domain, + account.address, + challengeRes.body.value + ); + const signature = await account.signMessage({ message }); + const payload = { + id, + account: account.address, + message, + signature, + }; + + await request(server) + .post('/api/auth/ethereum/login') + .send(payload) + .set('Authorization', `Bearer ${token}`) + .expect(201) + .expect((res) => { + expect(res.body).toEqual({ address: account.address }); + }); + }); + + it('should login failed with incorrect nonce', async () => { + const server = app.getHttpServer(); + const domain = getDomain(server); + + const res = await request(server) + .post('/api/auth/national/register') + .send({ username: 'username', password: 'password' }); + + const { id, token } = res.body; + + const privateKey = generatePrivateKey(); + const account = privateKeyToAccount(privateKey); + const message = generateSiweMessage( + domain, + account.address, + getRandomHexString() + ); + const signature = await account.signMessage({ message }); + const payload = { + id, + account: account.address, + message, + signature, + }; + + request(server) + .post('/api/auth/ethereum/login') + .send(payload) + .set('Authorization', `Bearer ${token}`) + .expect(401); + }); +}); diff --git a/apps/server/src/ethereum/nonce.service.ts b/apps/server/src/ethereum/nonce.service.ts index 759f7f7..4ad7e7d 100644 --- a/apps/server/src/ethereum/nonce.service.ts +++ b/apps/server/src/ethereum/nonce.service.ts @@ -26,8 +26,13 @@ export class NonceService { .findOneAndDelete({ value: nonceValue }) .exec(); + // this callback hander is for compatible of passport-siwe if (!nonce) { - throw new NotFoundException('Nonce not found'); + const error = new NotFoundException('Nonce not found'); + if (cb) { + cb(error, false); + } + throw error; } if (cb) {