diff --git a/nestjs-BE/server/src/auth/auth.module.ts b/nestjs-BE/server/src/auth/auth.module.ts index 027a13dc..c1c27693 100644 --- a/nestjs-BE/server/src/auth/auth.module.ts +++ b/nestjs-BE/server/src/auth/auth.module.ts @@ -1,13 +1,13 @@ import { Module } from '@nestjs/common'; import { AuthController } from './auth.controller'; import { AuthService } from './auth.service'; -import { UsersModule } from 'src/users/users.module'; +import { UsersModule } from '../users/users.module'; import { PassportModule } from '@nestjs/passport'; import { JwtModule } from '@nestjs/jwt'; import { JwtStrategy } from './jwt.strategy'; import { APP_GUARD } from '@nestjs/core'; import { JwtAuthGuard } from './jwt-auth.guard'; -import { ProfilesModule } from 'src/profiles/profiles.module'; +import { ProfilesModule } from '../profiles/profiles.module'; import { RefreshTokensService } from './refresh-tokens.service'; @Module({ diff --git a/nestjs-BE/server/src/board-trees/board-trees.gateway.ts b/nestjs-BE/server/src/board-trees/board-trees.gateway.ts index c7578d13..f1e8e110 100644 --- a/nestjs-BE/server/src/board-trees/board-trees.gateway.ts +++ b/nestjs-BE/server/src/board-trees/board-trees.gateway.ts @@ -10,7 +10,7 @@ import { OperationDelete, OperationMove, OperationUpdate, -} from 'src/crdt/operation'; +} from '../crdt/operation'; @WebSocketGateway({ namespace: 'board' }) export class BoardTreesGateway { diff --git a/nestjs-BE/server/src/board-trees/board-trees.service.ts b/nestjs-BE/server/src/board-trees/board-trees.service.ts index 9d85fd22..7651f9ec 100644 --- a/nestjs-BE/server/src/board-trees/board-trees.service.ts +++ b/nestjs-BE/server/src/board-trees/board-trees.service.ts @@ -2,8 +2,8 @@ import { Injectable } from '@nestjs/common'; import { InjectModel } from '@nestjs/mongoose'; import { BoardTree } from './schemas/board-tree.schema'; import { Model } from 'mongoose'; -import { CrdtTree } from 'src/crdt/crdt-tree'; -import { Operation } from 'src/crdt/operation'; +import { CrdtTree } from '../crdt/crdt-tree'; +import { Operation } from '../crdt/operation'; @Injectable() export class BoardTreesService { diff --git a/nestjs-BE/server/src/boards/boards.module.ts b/nestjs-BE/server/src/boards/boards.module.ts index 6a826401..91c082ed 100644 --- a/nestjs-BE/server/src/boards/boards.module.ts +++ b/nestjs-BE/server/src/boards/boards.module.ts @@ -3,7 +3,7 @@ import { BoardsService } from './boards.service'; import { BoardsController } from './boards.controller'; import { MongooseModule } from '@nestjs/mongoose'; import { Board, BoardSchema } from './schemas/board.schema'; -import { UploadModule } from 'src/upload/upload.module'; +import { UploadModule } from '../upload/upload.module'; @Module({ imports: [ diff --git a/nestjs-BE/server/src/invite-codes/invite-codes.controller.ts b/nestjs-BE/server/src/invite-codes/invite-codes.controller.ts index 1a125b1c..85ba6d14 100644 --- a/nestjs-BE/server/src/invite-codes/invite-codes.controller.ts +++ b/nestjs-BE/server/src/invite-codes/invite-codes.controller.ts @@ -11,7 +11,7 @@ import { import { InviteCodesService } from './invite-codes.service'; import { CreateInviteCodeDto } from './dto/create-invite-code.dto'; import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger'; -import { SpacesService } from 'src/spaces/spaces.service'; +import { SpacesService } from '../spaces/spaces.service'; @Controller('inviteCodes') @ApiTags('inviteCodes') diff --git a/nestjs-BE/server/src/invite-codes/invite-codes.module.ts b/nestjs-BE/server/src/invite-codes/invite-codes.module.ts index ba6ec305..3d035fd0 100644 --- a/nestjs-BE/server/src/invite-codes/invite-codes.module.ts +++ b/nestjs-BE/server/src/invite-codes/invite-codes.module.ts @@ -1,7 +1,7 @@ import { Module } from '@nestjs/common'; import { InviteCodesService } from './invite-codes.service'; import { InviteCodesController } from './invite-codes.controller'; -import { SpacesService } from 'src/spaces/spaces.service'; +import { SpacesService } from '../spaces/spaces.service'; @Module({ controllers: [InviteCodesController], diff --git a/nestjs-BE/server/src/invite-codes/invite-codes.service.ts b/nestjs-BE/server/src/invite-codes/invite-codes.service.ts index c48722bf..cdd296bf 100644 --- a/nestjs-BE/server/src/invite-codes/invite-codes.service.ts +++ b/nestjs-BE/server/src/invite-codes/invite-codes.service.ts @@ -1,11 +1,11 @@ import { Injectable } from '@nestjs/common'; -import { PrismaService } from 'src/prisma/prisma.service'; +import { PrismaService } from '../prisma/prisma.service'; import { INVITE_CODE_EXPIRY_HOURS, INVITE_CODE_LENGTH, -} from 'src/config/magic-number'; +} from '../config/magic-number'; import { InviteCode, Prisma } from '@prisma/client'; -import generateUuid from 'src/utils/uuid'; +import generateUuid from '../utils/uuid'; @Injectable() export class InviteCodesService { diff --git a/nestjs-BE/server/src/profile-space/profile-space.controller.ts b/nestjs-BE/server/src/profile-space/profile-space.controller.ts index f4df1f0b..3d1ade56 100644 --- a/nestjs-BE/server/src/profile-space/profile-space.controller.ts +++ b/nestjs-BE/server/src/profile-space/profile-space.controller.ts @@ -13,10 +13,10 @@ import { } from '@nestjs/common'; import { ProfileSpaceService } from './profile-space.service'; import { CreateProfileSpaceDto } from './dto/create-profile-space.dto'; -import { RequestWithUser } from 'src/utils/interface'; -import { SpacesService } from 'src/spaces/spaces.service'; +import { RequestWithUser } from '../utils/interface'; +import { SpacesService } from '../spaces/spaces.service'; import { ApiTags, ApiResponse, ApiOperation } from '@nestjs/swagger'; -import { ProfilesService } from 'src/profiles/profiles.service'; +import { ProfilesService } from '../profiles/profiles.service'; @Controller('profileSpace') @ApiTags('profileSpace') diff --git a/nestjs-BE/server/src/profile-space/profile-space.module.ts b/nestjs-BE/server/src/profile-space/profile-space.module.ts index c79af8a9..9e662309 100644 --- a/nestjs-BE/server/src/profile-space/profile-space.module.ts +++ b/nestjs-BE/server/src/profile-space/profile-space.module.ts @@ -1,8 +1,8 @@ import { Module, forwardRef } from '@nestjs/common'; import { ProfileSpaceService } from './profile-space.service'; import { ProfileSpaceController } from './profile-space.controller'; -import { ProfilesModule } from 'src/profiles/profiles.module'; -import { SpacesModule } from 'src/spaces/spaces.module'; +import { ProfilesModule } from '../profiles/profiles.module'; +import { SpacesModule } from '../spaces/spaces.module'; @Module({ imports: [ProfilesModule, forwardRef(() => SpacesModule)], diff --git a/nestjs-BE/server/src/spaces/spaces.module.ts b/nestjs-BE/server/src/spaces/spaces.module.ts index d208085c..4b6e2dfd 100644 --- a/nestjs-BE/server/src/spaces/spaces.module.ts +++ b/nestjs-BE/server/src/spaces/spaces.module.ts @@ -1,9 +1,9 @@ import { forwardRef, Module } from '@nestjs/common'; import { SpacesService } from './spaces.service'; import { SpacesController } from './spaces.controller'; -import { UploadService } from 'src/upload/upload.service'; -import { ProfileSpaceModule } from 'src/profile-space/profile-space.module'; -import { ProfilesModule } from 'src/profiles/profiles.module'; +import { UploadService } from '../upload/upload.service'; +import { ProfileSpaceModule } from '../profile-space/profile-space.module'; +import { ProfilesModule } from '../profiles/profiles.module'; @Module({ imports: [forwardRef(() => ProfileSpaceModule), ProfilesModule], diff --git a/nestjs-BE/server/test/app.e2e-spec.ts b/nestjs-BE/server/test/app.e2e-spec.ts index 50cda623..a0d22461 100644 --- a/nestjs-BE/server/test/app.e2e-spec.ts +++ b/nestjs-BE/server/test/app.e2e-spec.ts @@ -1,24 +1,126 @@ import { Test, TestingModule } from '@nestjs/testing'; -import { INestApplication } from '@nestjs/common'; +import { HttpStatus, INestApplication } from '@nestjs/common'; import * as request from 'supertest'; -import { AppModule } from './../src/app.module'; +import { AppModule } from '../src/app.module'; +import { PrismaService } from '../src/prisma/prisma.service'; +import { sign } from 'jsonwebtoken'; +import { ConfigService } from '@nestjs/config'; describe('AppController (e2e)', () => { let app: INestApplication; + let configService: ConfigService; - beforeEach(async () => { + beforeAll(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); + await app.init(); + + configService = moduleFixture.get(ConfigService); + const prisma: PrismaService = + moduleFixture.get(PrismaService); + + const testUser = { email: 'test@email.com', provider: 'kakao' }; + prisma.user.upsert({ + where: { + email_provider: { email: testUser.email, provider: testUser.provider }, + }, + update: {}, + create: { + uuid: 'test uuid', + email: testUser.email, + provider: testUser.provider, + }, + }); + }); + + afterAll(async () => { + await app.close(); }); it('/ (GET)', () => { return request(app.getHttpServer()) .get('/') - .expect(200) + .expect(HttpStatus.OK) .expect('Hello World!'); }); + + it('/login-test (GET) not logged in', () => { + return request(app.getHttpServer()) + .get('/login-test') + .expect(HttpStatus.UNAUTHORIZED) + .expect({ message: 'Unauthorized', statusCode: 401 }); + }); + + it('/login-test (GET) expired access token', () => { + const invalidToken = sign( + { sub: 'test uuid' }, + configService.get('JWT_ACCESS_SECRET'), + { expiresIn: '-5m' }, + ); + + return request(app.getHttpServer()) + .get('/login-test') + .auth(invalidToken, { type: 'bearer' }) + .expect(HttpStatus.UNAUTHORIZED) + .expect({ message: 'Unauthorized', statusCode: 401 }); + }); + + it('/login-test (GET) expired access token', () => { + const expiredToken = sign( + { sub: 'test uuid' }, + configService.get('JWT_ACCESS_SECRET'), + { expiresIn: '-5m' }, + ); + + return request(app.getHttpServer()) + .get('/login-test') + .auth(expiredToken, { type: 'bearer' }) + .expect(HttpStatus.UNAUTHORIZED) + .expect({ message: 'Unauthorized', statusCode: 401 }); + }); + + it('/login-test (GET) wrong user uuid access token', () => { + // access token은 user uuid가 맞는지 검증할 수 없다. + const wrongUserUuidToken = sign( + { sub: 'wrong uuid' }, + configService.get('JWT_ACCESS_SECRET'), + { expiresIn: '5m' }, + ); + + return request(app.getHttpServer()) + .get('/login-test') + .auth(wrongUserUuidToken, { type: 'bearer' }) + .expect(HttpStatus.OK) + .expect('login success'); + }); + + it('/login-test (GET) wrong secret access token', () => { + const invalidToken = sign({ sub: 'test uuid' }, 'wrong jwt access token', { + expiresIn: '5m', + }); + + return request(app.getHttpServer()) + .get('/login-test') + .auth(invalidToken, { type: 'bearer' }) + .expect(HttpStatus.UNAUTHORIZED) + .expect({ message: 'Unauthorized', statusCode: 401 }); + }); + + it('/login-test (GET) logged in', async () => { + const testToken = sign( + { sub: 'test uuid' }, + configService.get('JWT_ACCESS_SECRET'), + { expiresIn: '5m' }, + ); + + return request(app.getHttpServer()) + .get('/login-test') + .auth(testToken, { type: 'bearer' }) + .expect(HttpStatus.OK) + .expect('login success'); + }); }); diff --git a/nestjs-BE/server/test/spaces.e2e-spec.ts b/nestjs-BE/server/test/spaces.e2e-spec.ts new file mode 100644 index 00000000..74388106 --- /dev/null +++ b/nestjs-BE/server/test/spaces.e2e-spec.ts @@ -0,0 +1,180 @@ +import { HttpStatus, INestApplication } from '@nestjs/common'; +import { Test, TestingModule } from '@nestjs/testing'; +import { AuthModule } from '../src/auth/auth.module'; +import { SpacesModule } from '../src/spaces/spaces.module'; +import * as request from 'supertest'; +import { ConfigModule, ConfigService } from '@nestjs/config'; +import { PrismaService } from '../src/prisma/prisma.service'; +import { sign } from 'jsonwebtoken'; +import { Profile, Space } from '@prisma/client'; + +describe('SpacesController (e2e)', () => { + let app: INestApplication; + let testToken: string; + let testSpace: Space; + let testProfile: Profile; + let configService: ConfigService; + + beforeAll(async () => { + const module: TestingModule = await Test.createTestingModule({ + imports: [ConfigModule], + }).compile(); + + const configService: ConfigService = + module.get(ConfigService); + testToken = sign( + { sub: 'test uuid' }, + configService.get('JWT_ACCESS_SECRET'), + { expiresIn: '5m' }, + ); + testSpace = { + uuid: 'space-uuid', + name: 'test space', + icon: 'test space icon', + }; + testProfile = { + uuid: 'profile uuid', + user_id: 'test uuid', + image: configService.get('BASE_IMAGE_URL'), + nickname: 'test nickname', + }; + }); + + beforeEach(async () => { + const moduleFixture: TestingModule = await Test.createTestingModule({ + imports: [ + ConfigModule.forRoot({ isGlobal: true }), + SpacesModule, + AuthModule, + ], + }).compile(); + + app = moduleFixture.createNestApplication(); + + await app.init(); + + const prisma: PrismaService = + moduleFixture.get(PrismaService); + configService = moduleFixture.get(ConfigService); + + const testUser = { email: 'test@email.com', provider: 'kakao' }; + await prisma.user.upsert({ + where: { + email_provider: { email: testUser.email, provider: testUser.provider }, + }, + update: {}, + create: { + uuid: 'test uuid', + email: testUser.email, + provider: testUser.provider, + }, + }); + await prisma.profile.upsert({ + where: { user_id: 'test uuid' }, + update: {}, + create: { + uuid: testProfile.uuid, + user_id: testProfile.user_id, + image: testProfile.image, + nickname: testProfile.nickname, + }, + }); + await prisma.space.upsert({ + where: { + uuid: testSpace.uuid, + }, + update: {}, + create: { + uuid: testSpace.uuid, + name: testSpace.name, + icon: testSpace.icon, + }, + }); + }); + + afterEach(async () => { + await app.close(); + }); + + it('/spaces (POST) not logged in', () => { + return request(app.getHttpServer()) + .post('/spaces') + .expect(HttpStatus.UNAUTHORIZED) + .expect({ message: 'Unauthorized', statusCode: HttpStatus.UNAUTHORIZED }); + }); + + it('/spaces (POST) without space name', () => { + return request(app.getHttpServer()) + .post('/spaces') + .auth(testToken, { type: 'bearer' }) + .expect(HttpStatus.BAD_REQUEST) + .expect({ message: 'Bad Request', statusCode: HttpStatus.BAD_REQUEST }); + }); + + it('/spaces (POST) without space image', () => { + return request(app.getHttpServer()) + .post('/spaces') + .auth(testToken, { type: 'bearer' }) + .send({ name: 'new test space' }) + .expect(HttpStatus.CREATED) + .expect((res) => { + expect(res.body.message).toBe('Created'); + expect(res.body.statusCode).toBe(HttpStatus.CREATED); + expect(res.body.data.uuid).toMatch(/^[0-9a-f]{32}$/); + expect(res.body.data.name).toBe('new test space'); + expect(res.body.data.icon).toBe( + configService.get('APP_ICON_URL'), + ); + }); + }); + + it('/spaces (POST)', () => { + const imageUrlPattern = `^https\\:\\/\\/${configService.get( + 'S3_BUCKET_NAME', + )}\\.s3\\.${configService.get( + 'AWS_REGION', + )}\\.amazonaws\\.com\\/[0-9a-f]{32}-`; + const imageRegExp = new RegExp(imageUrlPattern); + + return request(app.getHttpServer()) + .post('/spaces') + .auth(testToken, { type: 'bearer' }) + .field('name', 'new test space') + .attach('icon', './test/base_image.png', { contentType: 'image/png' }) + .expect(HttpStatus.CREATED) + .expect((res) => { + expect(res.body.message).toBe('Created'); + expect(res.body.statusCode).toBe(HttpStatus.CREATED); + expect(res.body.data.uuid).toMatch(/^[0-9a-f]{32}$/); + expect(res.body.data.name).toBe('new test space'); + expect(res.body.data.icon).toMatch(imageRegExp); + }); + }); + + it('/spaces/{space_uuid} (GET) not logged in', () => { + return request(app.getHttpServer()) + .get('/spaces/space-uuid') + .expect(HttpStatus.UNAUTHORIZED) + .expect({ message: 'Unauthorized', statusCode: HttpStatus.UNAUTHORIZED }); + }); + + it('/spaces/{space_uuid} (GET) not existing space', () => { + return request(app.getHttpServer()) + .get('/spaces/wrong-space-uuid') + .auth(testToken, { type: 'bearer' }) + .expect(HttpStatus.NOT_FOUND) + .expect({ message: 'Not Found', statusCode: HttpStatus.NOT_FOUND }); + }); + + it('/spaces/{space_uuid} (GET) space found', () => { + return request(app.getHttpServer()) + .get('/spaces/space-uuid') + .auth(testToken, { type: 'bearer' }) + .expect(HttpStatus.OK) + .expect({ + message: 'Success', + statusCode: HttpStatus.OK, + data: testSpace, + }); + }); +});