diff --git a/jest-e2e.json b/jest-e2e.json index 4dccfdafa..e0a3c5d31 100644 --- a/jest-e2e.json +++ b/jest-e2e.json @@ -16,7 +16,7 @@ "testTimeout": 10000, "globals": { "ts-jest": { - "tsconfig": "tsconfig.test.json" + "tsconfig": "test/tsconfig.json" } } } diff --git a/package.json b/package.json index 3f887e1d6..092291fe1 100644 --- a/package.json +++ b/package.json @@ -108,7 +108,7 @@ "testEnvironment": "node", "globals": { "ts-jest": { - "tsconfig": "tsconfig.test.json" + "tsconfig": "test/tsconfig.json" } } } diff --git a/test/private-api/alias.e2e-spec.ts b/test/private-api/alias.e2e-spec.ts index d05242734..00056543b 100644 --- a/test/private-api/alias.e2e-spec.ts +++ b/test/private-api/alias.e2e-spec.ts @@ -3,90 +3,38 @@ * * SPDX-License-Identifier: AGPL-3.0-only */ -import { INestApplication } from '@nestjs/common'; -import { ConfigModule, ConfigService } from '@nestjs/config'; -import { Test } from '@nestjs/testing'; -import { TypeOrmModule } from '@nestjs/typeorm'; import request from 'supertest'; -import { PrivateApiModule } from '../../src/api/private/private-api.module'; -import { AuthModule } from '../../src/auth/auth.module'; import { AuthConfig } from '../../src/config/auth.config'; -import appConfigMock from '../../src/config/mock/app.config.mock'; -import authConfigMock from '../../src/config/mock/auth.config.mock'; -import customizationConfigMock from '../../src/config/mock/customization.config.mock'; -import externalConfigMock from '../../src/config/mock/external-services.config.mock'; -import mediaConfigMock from '../../src/config/mock/media.config.mock'; -import { GroupsModule } from '../../src/groups/groups.module'; -import { IdentityService } from '../../src/identity/identity.service'; -import { LoggerModule } from '../../src/logger/logger.module'; import { AliasCreateDto } from '../../src/notes/alias-create.dto'; import { AliasUpdateDto } from '../../src/notes/alias-update.dto'; -import { AliasService } from '../../src/notes/alias.service'; -import { NotesModule } from '../../src/notes/notes.module'; -import { NotesService } from '../../src/notes/notes.service'; -import { PermissionsModule } from '../../src/permissions/permissions.module'; import { User } from '../../src/users/user.entity'; -import { UsersModule } from '../../src/users/users.module'; -import { UsersService } from '../../src/users/users.service'; import { setupSessionMiddleware } from '../../src/utils/session'; +import { TestSetup } from '../test-setup'; describe('Alias', () => { - let app: INestApplication; - let aliasService: AliasService; - let notesService: NotesService; - let identityService: IdentityService; + let testSetup: TestSetup; + let user: User; let content: string; let forbiddenNoteId: string; + let agent: request.SuperAgentTest; beforeAll(async () => { - const moduleRef = await Test.createTestingModule({ - imports: [ - ConfigModule.forRoot({ - isGlobal: true, - load: [ - mediaConfigMock, - appConfigMock, - authConfigMock, - customizationConfigMock, - externalConfigMock, - ], - }), - PrivateApiModule, - NotesModule, - PermissionsModule, - GroupsModule, - TypeOrmModule.forRoot({ - type: 'sqlite', - database: './hedgedoc-e2e-private-alias.sqlite', - autoLoadEntities: true, - synchronize: true, - dropSchema: true, - }), - LoggerModule, - AuthModule, - UsersModule, - ], - }).compile(); + testSetup = await TestSetup.create(); - const config = moduleRef.get(ConfigService); - forbiddenNoteId = config.get('appConfig').forbiddenNoteIds[0]; - app = moduleRef.createNestApplication(); - const authConfig = config.get('authConfig') as AuthConfig; - setupSessionMiddleware(app, authConfig); - await app.init(); - aliasService = moduleRef.get(AliasService); - notesService = moduleRef.get(NotesService); - identityService = moduleRef.get(IdentityService); - const userService = moduleRef.get(UsersService); - user = await userService.createUser('hardcoded', 'Testy'); - await identityService.createLocalIdentity(user, 'test'); + forbiddenNoteId = + testSetup.configService.get('appConfig').forbiddenNoteIds[0]; + const authConfig = testSetup.configService.get('authConfig') as AuthConfig; + setupSessionMiddleware(testSetup.app, authConfig); + await testSetup.app.init(); + user = await testSetup.userService.createUser('hardcoded', 'Testy'); + await testSetup.identityService.createLocalIdentity(user, 'test'); content = 'This is a test note.'; - agent = request.agent(app.getHttpServer()); + agent = request.agent(testSetup.app.getHttpServer()); await agent - .post('/auth/local/login') + .post('/api/private/auth/local/login') .send({ username: 'hardcoded', password: 'test' }) .expect(201); }); @@ -99,7 +47,11 @@ describe('Alias', () => { }; let publicId = ''; beforeAll(async () => { - const note = await notesService.createNote(content, testAlias, user); + const note = await testSetup.notesService.createNote( + content, + testAlias, + user, + ); publicId = note.publicId; }); @@ -107,14 +59,16 @@ describe('Alias', () => { const newAlias = 'normalAlias'; newAliasDto.newAlias = newAlias; const metadata = await agent - .post(`/alias`) + .post(`/api/private/alias`) .set('Content-Type', 'application/json') .send(newAliasDto) .expect(201); expect(metadata.body.name).toEqual(newAlias); expect(metadata.body.primaryAlias).toBeFalsy(); expect(metadata.body.noteId).toEqual(publicId); - const note = await agent.get(`/notes/${newAlias}`).expect(200); + const note = await agent + .get(`/api/private/notes/${newAlias}`) + .expect(200); expect(note.body.metadata.aliases).toContain(newAlias); expect(note.body.metadata.primaryAlias).toBeTruthy(); expect(note.body.metadata.id).toEqual(publicId); @@ -124,7 +78,7 @@ describe('Alias', () => { it('because of a forbidden alias', async () => { newAliasDto.newAlias = forbiddenNoteId; await agent - .post(`/alias`) + .post(`/api/private/alias`) .set('Content-Type', 'application/json') .send(newAliasDto) .expect(400); @@ -132,7 +86,7 @@ describe('Alias', () => { it('because of a alias that is a public id', async () => { newAliasDto.newAlias = publicId; await agent - .post(`/alias`) + .post(`/api/private/alias`) .set('Content-Type', 'application/json') .send(newAliasDto) .expect(400); @@ -148,21 +102,27 @@ describe('Alias', () => { }; let publicId = ''; beforeAll(async () => { - const note = await notesService.createNote(content, testAlias, user); + const note = await testSetup.notesService.createNote( + content, + testAlias, + user, + ); publicId = note.publicId; - await aliasService.addAlias(note, newAlias); + await testSetup.aliasService.addAlias(note, newAlias); }); it('updates a note with a normal alias', async () => { const metadata = await agent - .put(`/alias/${newAlias}`) + .put(`/api/private/alias/${newAlias}`) .set('Content-Type', 'application/json') .send(changeAliasDto) .expect(200); expect(metadata.body.name).toEqual(newAlias); expect(metadata.body.primaryAlias).toBeTruthy(); expect(metadata.body.noteId).toEqual(publicId); - const note = await agent.get(`/notes/${newAlias}`).expect(200); + const note = await agent + .get(`/api/private/notes/${newAlias}`) + .expect(200); expect(note.body.metadata.aliases).toContain(newAlias); expect(note.body.metadata.primaryAlias).toBeTruthy(); expect(note.body.metadata.id).toEqual(publicId); @@ -171,7 +131,7 @@ describe('Alias', () => { describe('does not update', () => { it('a note with unknown alias', async () => { await agent - .put(`/alias/i_dont_exist`) + .put(`/api/private/alias/i_dont_exist`) .set('Content-Type', 'application/json') .send(changeAliasDto) .expect(404); @@ -179,7 +139,7 @@ describe('Alias', () => { it('if the property primaryAlias is false', async () => { changeAliasDto.primaryAlias = false; await agent - .put(`/alias/${newAlias}`) + .put(`/api/private/alias/${newAlias}`) .set('Content-Type', 'application/json') .send(changeAliasDto) .expect(400); @@ -191,29 +151,33 @@ describe('Alias', () => { const testAlias = 'aliasTest3'; const newAlias = 'normalAlias3'; beforeAll(async () => { - const note = await notesService.createNote(content, testAlias, user); - await aliasService.addAlias(note, newAlias); + const note = await testSetup.notesService.createNote( + content, + testAlias, + user, + ); + await testSetup.aliasService.addAlias(note, newAlias); }); it('deletes a normal alias', async () => { - await agent.delete(`/alias/${newAlias}`).expect(204); - await agent.get(`/notes/${newAlias}`).expect(404); + await agent.delete(`/api/private/alias/${newAlias}`).expect(204); + await agent.get(`/api/private/notes/${newAlias}`).expect(404); }); it('does not delete an unknown alias', async () => { - await agent.delete(`/alias/i_dont_exist`).expect(404); + await agent.delete(`/api/private/alias/i_dont_exist`).expect(404); }); it('does not delete an primary alias (if it is not the only one)', async () => { - const note = await notesService.getNoteByIdOrAlias(testAlias); - await aliasService.addAlias(note, newAlias); - await agent.delete(`/alias/${testAlias}`).expect(400); - await agent.get(`/notes/${newAlias}`).expect(200); + const note = await testSetup.notesService.getNoteByIdOrAlias(testAlias); + await testSetup.aliasService.addAlias(note, newAlias); + await agent.delete(`/api/private/alias/${testAlias}`).expect(400); + await agent.get(`/api/private/notes/${newAlias}`).expect(200); }); it('deletes a primary alias (if it is the only one)', async () => { - await agent.delete(`/alias/${newAlias}`).expect(204); - await agent.delete(`/alias/${testAlias}`).expect(204); + await agent.delete(`/api/private/alias/${newAlias}`).expect(204); + await agent.delete(`/api/private/alias/${testAlias}`).expect(204); }); }); }); diff --git a/test/private-api/auth.e2e-spec.ts b/test/private-api/auth.e2e-spec.ts index 3dd887fa9..7df133331 100644 --- a/test/private-api/auth.e2e-spec.ts +++ b/test/private-api/auth.e2e-spec.ts @@ -8,80 +8,31 @@ @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access */ -import { INestApplication } from '@nestjs/common'; -import { ConfigModule, ConfigService } from '@nestjs/config'; -import { Test } from '@nestjs/testing'; -import { TypeOrmModule } from '@nestjs/typeorm'; import request from 'supertest'; -import { PrivateApiModule } from '../../src/api/private/private-api.module'; -import { AuthModule } from '../../src/auth/auth.module'; import { AuthConfig } from '../../src/config/auth.config'; -import appConfigMock from '../../src/config/mock/app.config.mock'; -import authConfigMock from '../../src/config/mock/auth.config.mock'; -import customizationConfigMock from '../../src/config/mock/customization.config.mock'; -import externalServicesConfigMock from '../../src/config/mock/external-services.config.mock'; -import mediaConfigMock from '../../src/config/mock/media.config.mock'; -import { GroupsModule } from '../../src/groups/groups.module'; -import { HistoryModule } from '../../src/history/history.module'; import { LoginDto } from '../../src/identity/local/login.dto'; import { RegisterDto } from '../../src/identity/local/register.dto'; import { UpdatePasswordDto } from '../../src/identity/local/update-password.dto'; -import { LoggerModule } from '../../src/logger/logger.module'; -import { MediaModule } from '../../src/media/media.module'; -import { NotesModule } from '../../src/notes/notes.module'; -import { PermissionsModule } from '../../src/permissions/permissions.module'; import { UserRelationEnum } from '../../src/users/user-relation.enum'; -import { UsersModule } from '../../src/users/users.module'; -import { UsersService } from '../../src/users/users.service'; import { checkPassword } from '../../src/utils/password'; import { setupSessionMiddleware } from '../../src/utils/session'; +import { TestSetup } from '../test-setup'; describe('Auth', () => { - let app: INestApplication; - let userService: UsersService; + let testSetup: TestSetup; + let username: string; let displayname: string; let password: string; - let config: ConfigService; beforeAll(async () => { - const moduleRef = await Test.createTestingModule({ - imports: [ - ConfigModule.forRoot({ - isGlobal: true, - load: [ - appConfigMock, - authConfigMock, - mediaConfigMock, - customizationConfigMock, - externalServicesConfigMock, - ], - }), - PrivateApiModule, - NotesModule, - PermissionsModule, - GroupsModule, - TypeOrmModule.forRoot({ - type: 'sqlite', - database: './hedgedoc-e2e-private-auth.sqlite', - autoLoadEntities: true, - synchronize: true, - dropSchema: true, - }), - LoggerModule, - AuthModule, - UsersModule, - MediaModule, - HistoryModule, - ], - }).compile(); - config = moduleRef.get(ConfigService); - app = moduleRef.createNestApplication(); - const authConfig = config.get('authConfig') as AuthConfig; - setupSessionMiddleware(app, authConfig); - await app.init(); - userService = moduleRef.get(UsersService); + testSetup = await TestSetup.create(); + + const authConfig = testSetup.configService.get('authConfig') as AuthConfig; + setupSessionMiddleware(testSetup.app, authConfig); + await testSetup.app.init(); + username = 'hardcoded'; displayname = 'Testy'; password = 'test_password'; @@ -94,12 +45,12 @@ describe('Auth', () => { password: password, username: username, }; - await request(app.getHttpServer()) - .post('/auth/local') + await request(testSetup.app.getHttpServer()) + .post('/api/private/auth/local') .set('Content-Type', 'application/json') .send(JSON.stringify(registrationDto)) .expect(201); - const newUser = await userService.getUserByUsername(username, [ + const newUser = await testSetup.userService.getUserByUsername(username, [ UserRelationEnum.IDENTITIES, ]); expect(newUser.displayName).toEqual(displayname); @@ -114,31 +65,31 @@ describe('Auth', () => { describe('fails', () => { it('when the user already exits', async () => { const username2 = 'already_existing'; - await userService.createUser(username2, displayname); + await testSetup.userService.createUser(username2, displayname); const registrationDto: RegisterDto = { displayname: displayname, password: password, username: username2, }; - await request(app.getHttpServer()) - .post('/auth/local') + await request(testSetup.app.getHttpServer()) + .post('/api/private/auth/local') .set('Content-Type', 'application/json') .send(JSON.stringify(registrationDto)) .expect(400); }); it('when registration is disabled', async () => { - config.get('authConfig').local.enableRegister = false; + testSetup.configService.get('authConfig').local.enableRegister = false; const registrationDto: RegisterDto = { displayname: displayname, password: password, username: username, }; - await request(app.getHttpServer()) - .post('/auth/local') + await request(testSetup.app.getHttpServer()) + .post('/api/private/auth/local') .set('Content-Type', 'application/json') .send(JSON.stringify(registrationDto)) .expect(400); - config.get('authConfig').local.enableRegister = true; + testSetup.configService.get('authConfig').local.enableRegister = true; }); }); }); @@ -151,8 +102,8 @@ describe('Auth', () => { password: password, username: username, }; - const response = await request(app.getHttpServer()) - .post('/auth/local/login') + const response = await request(testSetup.app.getHttpServer()) + .post('/api/private/auth/local/login') .set('Content-Type', 'application/json') .send(JSON.stringify(loginDto)) .expect(201); @@ -163,8 +114,8 @@ describe('Auth', () => { const changePasswordDto: UpdatePasswordDto = { newPassword: newPassword, }; - await request(app.getHttpServer()) - .put('/auth/local') + await request(testSetup.app.getHttpServer()) + .put('/api/private/auth/local') .set('Content-Type', 'application/json') .set('Cookie', cookie) .send(JSON.stringify(changePasswordDto)) @@ -174,8 +125,8 @@ describe('Auth', () => { password: newPassword, username: username, }; - const response = await request(app.getHttpServer()) - .post('/auth/local/login') + const response = await request(testSetup.app.getHttpServer()) + .post('/api/private/auth/local/login') .set('Content-Type', 'application/json') .send(JSON.stringify(loginDto)) .expect(201); @@ -184,34 +135,34 @@ describe('Auth', () => { const changePasswordBackDto: UpdatePasswordDto = { newPassword: password, }; - await request(app.getHttpServer()) - .put('/auth/local') + await request(testSetup.app.getHttpServer()) + .put('/api/private/auth/local') .set('Content-Type', 'application/json') .set('Cookie', cookie) .send(JSON.stringify(changePasswordBackDto)) .expect(200); }); it('fails, when registration is disabled', async () => { - config.get('authConfig').local.enableLogin = false; + testSetup.configService.get('authConfig').local.enableLogin = false; // Try to change password const changePasswordDto: UpdatePasswordDto = { newPassword: newPassword, }; - await request(app.getHttpServer()) - .put('/auth/local') + await request(testSetup.app.getHttpServer()) + .put('/api/private/auth/local') .set('Content-Type', 'application/json') .set('Cookie', cookie) .send(JSON.stringify(changePasswordDto)) .expect(400); // enable login again - config.get('authConfig').local.enableLogin = true; + testSetup.configService.get('authConfig').local.enableLogin = true; // new password doesn't work for login const loginNewPasswordDto: LoginDto = { password: newPassword, username: username, }; - await request(app.getHttpServer()) - .post('/auth/local/login') + await request(testSetup.app.getHttpServer()) + .post('/api/private/auth/local/login') .set('Content-Type', 'application/json') .send(JSON.stringify(loginNewPasswordDto)) .expect(401); @@ -220,8 +171,8 @@ describe('Auth', () => { password: password, username: username, }; - await request(app.getHttpServer()) - .post('/auth/local/login') + await request(testSetup.app.getHttpServer()) + .post('/api/private/auth/local/login') .set('Content-Type', 'application/json') .send(JSON.stringify(loginOldPasswordDto)) .expect(201); @@ -230,13 +181,13 @@ describe('Auth', () => { describe('POST /auth/local/login', () => { it('works', async () => { - config.get('authConfig').local.enableLogin = true; + testSetup.configService.get('authConfig').local.enableLogin = true; const loginDto: LoginDto = { password: password, username: username, }; - await request(app.getHttpServer()) - .post('/auth/local/login') + await request(testSetup.app.getHttpServer()) + .post('/api/private/auth/local/login') .set('Content-Type', 'application/json') .send(JSON.stringify(loginDto)) .expect(201); @@ -245,19 +196,19 @@ describe('Auth', () => { describe('DELETE /auth/logout', () => { it('works', async () => { - config.get('authConfig').local.enableLogin = true; + testSetup.configService.get('authConfig').local.enableLogin = true; const loginDto: LoginDto = { password: password, username: username, }; - const response = await request(app.getHttpServer()) - .post('/auth/local/login') + const response = await request(testSetup.app.getHttpServer()) + .post('/api/private/auth/local/login') .set('Content-Type', 'application/json') .send(JSON.stringify(loginDto)) .expect(201); const cookie = response.get('Set-Cookie')[0]; - await request(app.getHttpServer()) - .delete('/auth/logout') + await request(testSetup.app.getHttpServer()) + .delete('/api/private/auth/logout') .set('Cookie', cookie) .expect(200); }); diff --git a/test/private-api/history.e2e-spec.ts b/test/private-api/history.e2e-spec.ts index 4d7795a7f..ec10b132e 100644 --- a/test/private-api/history.e2e-spec.ts +++ b/test/private-api/history.e2e-spec.ts @@ -3,37 +3,23 @@ * * SPDX-License-Identifier: AGPL-3.0-only */ -import { INestApplication } from '@nestjs/common'; -import { ConfigModule, ConfigService } from '@nestjs/config'; -import { Test } from '@nestjs/testing'; -import { TypeOrmModule } from '@nestjs/typeorm'; +import { ConfigService } from '@nestjs/config'; import request from 'supertest'; -import { PrivateApiModule } from '../../src/api/private/private-api.module'; -import { AuthModule } from '../../src/auth/auth.module'; import { AuthConfig } from '../../src/config/auth.config'; -import appConfigMock from '../../src/config/mock/app.config.mock'; -import authConfigMock from '../../src/config/mock/auth.config.mock'; -import customizationConfigMock from '../../src/config/mock/customization.config.mock'; -import externalServicesConfigMock from '../../src/config/mock/external-services.config.mock'; -import mediaConfigMock from '../../src/config/mock/media.config.mock'; -import { GroupsModule } from '../../src/groups/groups.module'; import { HistoryEntryImportDto } from '../../src/history/history-entry-import.dto'; import { HistoryEntry } from '../../src/history/history-entry.entity'; import { HistoryService } from '../../src/history/history.service'; import { IdentityService } from '../../src/identity/identity.service'; -import { LoggerModule } from '../../src/logger/logger.module'; import { Note } from '../../src/notes/note.entity'; -import { NotesModule } from '../../src/notes/notes.module'; import { NotesService } from '../../src/notes/notes.service'; -import { PermissionsModule } from '../../src/permissions/permissions.module'; import { User } from '../../src/users/user.entity'; -import { UsersModule } from '../../src/users/users.module'; import { UsersService } from '../../src/users/users.service'; import { setupSessionMiddleware } from '../../src/utils/session'; +import { TestSetup } from '../test-setup'; describe('History', () => { - let app: INestApplication; + let testSetup: TestSetup; let historyService: HistoryService; let identityService: IdentityService; let user: User; @@ -44,41 +30,19 @@ describe('History', () => { let agent: request.SuperAgentTest; beforeAll(async () => { - const moduleRef = await Test.createTestingModule({ - imports: [ - ConfigModule.forRoot({ - isGlobal: true, - load: [ - appConfigMock, - mediaConfigMock, - authConfigMock, - customizationConfigMock, - externalServicesConfigMock, - ], - }), - PrivateApiModule, - NotesModule, - PermissionsModule, - GroupsModule, - TypeOrmModule.forRoot({ - type: 'sqlite', - database: './hedgedoc-e2e-private-history.sqlite', - autoLoadEntities: true, - synchronize: true, - dropSchema: true, - }), - LoggerModule, - AuthModule, - UsersModule, - ], - }).compile(); + testSetup = await TestSetup.create(); + + forbiddenNoteId = + testSetup.configService.get('appConfig').forbiddenNoteIds[0]; + + const moduleRef = testSetup.moduleRef; const config = moduleRef.get(ConfigService); forbiddenNoteId = config.get('appConfig').forbiddenNoteIds[0]; - app = moduleRef.createNestApplication(); + const authConfig = config.get('authConfig') as AuthConfig; - setupSessionMiddleware(app, authConfig); - await app.init(); + setupSessionMiddleware(testSetup.app, authConfig); + await testSetup.app.init(); content = 'This is a test note.'; historyService = moduleRef.get(HistoryService); const userService = moduleRef.get(UsersService); @@ -88,23 +52,26 @@ describe('History', () => { const notesService = moduleRef.get(NotesService); note = await notesService.createNote(content, 'note', user); note2 = await notesService.createNote(content, 'note2', user); - agent = request.agent(app.getHttpServer()); + agent = request.agent(testSetup.app.getHttpServer()); await agent - .post('/auth/local/login') + .post('/api/private/auth/local/login') .send({ username: 'hardcoded', password: 'test' }) .expect(201); }); it('GET /me/history', async () => { const emptyResponse = await agent - .get('/me/history') + .get('/api/private/me/history') .expect('Content-Type', /json/) .expect(200); expect(emptyResponse.body.length).toEqual(0); - const entry = await historyService.updateHistoryEntryTimestamp(note, user); - const entryDto = historyService.toHistoryEntryDto(entry); + const entry = await testSetup.historyService.updateHistoryEntryTimestamp( + note, + user, + ); + const entryDto = testSetup.historyService.toHistoryEntryDto(entry); const response = await agent - .get('/me/history') + .get('/api/private/me/history') .expect('Content-Type', /json/) .expect(200); expect(response.body.length).toEqual(1); @@ -119,7 +86,9 @@ describe('History', () => { describe('POST /me/history', () => { it('works', async () => { - expect(await historyService.getEntriesByUser(user)).toHaveLength(1); + expect( + await testSetup.historyService.getEntriesByUser(user), + ).toHaveLength(1); const pinStatus = true; const lastVisited = new Date('2020-12-01 12:23:34'); const postEntryDto = new HistoryEntryImportDto(); @@ -129,11 +98,11 @@ describe('History', () => { postEntryDto.pinStatus = pinStatus; postEntryDto.lastVisited = lastVisited; await agent - .post('/me/history') + .post('/api/private/me/history') .set('Content-Type', 'application/json') .send(JSON.stringify({ history: [postEntryDto] })) .expect(201); - const userEntries = await historyService.getEntriesByUser(user); + const userEntries = await testSetup.historyService.getEntriesByUser(user); expect(userEntries.length).toEqual(1); expect(userEntries[0].note.aliases).toEqual(note2.aliases); expect(userEntries[0].user.username).toEqual(user.username); @@ -146,7 +115,9 @@ describe('History', () => { let postEntryDto: HistoryEntryImportDto; let prevEntry: HistoryEntry; beforeAll(async () => { - const previousHistory = await historyService.getEntriesByUser(user); + const previousHistory = await testSetup.historyService.getEntriesByUser( + user, + ); expect(previousHistory).toHaveLength(1); prevEntry = previousHistory[0]; pinStatus = !previousHistory[0].pinStatus; @@ -164,7 +135,7 @@ describe('History', () => { brokenEntryDto.pinStatus = pinStatus; brokenEntryDto.lastVisited = lastVisited; await agent - .post('/me/history') + .post('/api/private/me/history') .set('Content-Type', 'application/json') .send(JSON.stringify({ history: [brokenEntryDto] })) .expect(400); @@ -175,13 +146,15 @@ describe('History', () => { brokenEntryDto.pinStatus = pinStatus; brokenEntryDto.lastVisited = lastVisited; await agent - .post('/me/history') + .post('/api/private/me/history') .set('Content-Type', 'application/json') .send(JSON.stringify({ history: [brokenEntryDto] })) .expect(400); }); afterEach(async () => { - const historyEntries = await historyService.getEntriesByUser(user); + const historyEntries = await testSetup.historyService.getEntriesByUser( + user, + ); expect(historyEntries).toHaveLength(1); expect(historyEntries[0].note.aliases).toEqual(prevEntry.note.aliases); expect(historyEntries[0].user.username).toEqual( @@ -194,23 +167,30 @@ describe('History', () => { }); it('DELETE /me/history', async () => { - expect((await historyService.getEntriesByUser(user)).length).toEqual(1); - await agent.delete('/me/history').expect(200); - expect((await historyService.getEntriesByUser(user)).length).toEqual(0); + expect( + (await testSetup.historyService.getEntriesByUser(user)).length, + ).toEqual(1); + await agent.delete('/api/private/me/history').expect(200); + expect( + (await testSetup.historyService.getEntriesByUser(user)).length, + ).toEqual(0); }); it('PUT /me/history/:note', async () => { - const entry = await historyService.updateHistoryEntryTimestamp(note2, user); + const entry = await testSetup.historyService.updateHistoryEntryTimestamp( + note2, + user, + ); expect(entry.pinStatus).toBeFalsy(); const alias = entry.note.aliases.filter((alias) => alias.primary)[0].name; await agent - .put(`/me/history/${alias || 'undefined'}`) + .put(`/api/private/me/history/${alias || 'undefined'}`) .send({ pinStatus: true }) .expect(200); - const userEntries = await historyService.getEntriesByUser(user); + const userEntries = await testSetup.historyService.getEntriesByUser(user); expect(userEntries.length).toEqual(1); expect(userEntries[0].pinStatus).toBeTruthy(); - await historyService.deleteHistoryEntry(note2, user); + await testSetup.historyService.deleteHistoryEntry(note2, user); }); it('DELETE /me/history/:note', async () => { @@ -218,7 +198,9 @@ describe('History', () => { const alias = entry.note.aliases.filter((alias) => alias.primary)[0].name; const entry2 = await historyService.updateHistoryEntryTimestamp(note, user); const entryDto = historyService.toHistoryEntryDto(entry2); - await agent.delete(`/me/history/${alias || 'undefined'}`).expect(200); + await agent + .delete(`/api/private/me/history/${alias || 'undefined'}`) + .expect(200); const userEntries = await historyService.getEntriesByUser(user); expect(userEntries.length).toEqual(1); const userEntryDto = historyService.toHistoryEntryDto(userEntries[0]); @@ -230,6 +212,6 @@ describe('History', () => { }); afterAll(async () => { - await app.close(); + await testSetup.app.close(); }); }); diff --git a/test/private-api/me.e2e-spec.ts b/test/private-api/me.e2e-spec.ts index f10768cda..54e80ab7b 100644 --- a/test/private-api/me.e2e-spec.ts +++ b/test/private-api/me.e2e-spec.ts @@ -3,48 +3,20 @@ * * SPDX-License-Identifier: AGPL-3.0-only */ - -/* eslint-disable -@typescript-eslint/no-unsafe-assignment, -@typescript-eslint/no-unsafe-member-access -*/ -import { INestApplication } from '@nestjs/common'; -import { ConfigModule, ConfigService } from '@nestjs/config'; -import { Test } from '@nestjs/testing'; -import { TypeOrmModule } from '@nestjs/typeorm'; import { promises as fs } from 'fs'; import request from 'supertest'; -import { PrivateApiModule } from '../../src/api/private/private-api.module'; -import { AuthModule } from '../../src/auth/auth.module'; import { AuthConfig } from '../../src/config/auth.config'; -import appConfigMock from '../../src/config/mock/app.config.mock'; -import authConfigMock from '../../src/config/mock/auth.config.mock'; -import customizationConfigMock from '../../src/config/mock/customization.config.mock'; -import externalServicesConfigMock from '../../src/config/mock/external-services.config.mock'; -import mediaConfigMock from '../../src/config/mock/media.config.mock'; import { NotInDBError } from '../../src/errors/errors'; -import { GroupsModule } from '../../src/groups/groups.module'; -import { HistoryModule } from '../../src/history/history.module'; -import { IdentityService } from '../../src/identity/identity.service'; -import { LoggerModule } from '../../src/logger/logger.module'; -import { MediaModule } from '../../src/media/media.module'; -import { MediaService } from '../../src/media/media.service'; import { Note } from '../../src/notes/note.entity'; -import { NotesModule } from '../../src/notes/notes.module'; -import { NotesService } from '../../src/notes/notes.service'; -import { PermissionsModule } from '../../src/permissions/permissions.module'; import { UserInfoDto } from '../../src/users/user-info.dto'; import { User } from '../../src/users/user.entity'; -import { UsersModule } from '../../src/users/users.module'; -import { UsersService } from '../../src/users/users.service'; import { setupSessionMiddleware } from '../../src/utils/session'; +import { TestSetup } from '../test-setup'; describe('Me', () => { - let app: INestApplication; - let userService: UsersService; - let mediaService: MediaService; - let identityService: IdentityService; + let testSetup: TestSetup; + let uploadPath: string; let user: User; let content: string; @@ -54,65 +26,33 @@ describe('Me', () => { let agent: request.SuperAgentTest; beforeAll(async () => { - const moduleRef = await Test.createTestingModule({ - imports: [ - ConfigModule.forRoot({ - isGlobal: true, - load: [ - appConfigMock, - authConfigMock, - mediaConfigMock, - customizationConfigMock, - externalServicesConfigMock, - ], - }), - PrivateApiModule, - NotesModule, - PermissionsModule, - GroupsModule, - TypeOrmModule.forRoot({ - type: 'sqlite', - database: './hedgedoc-e2e-private-me.sqlite', - autoLoadEntities: true, - synchronize: true, - dropSchema: true, - }), - LoggerModule, - AuthModule, - UsersModule, - MediaModule, - HistoryModule, - ], - }).compile(); - const config = moduleRef.get(ConfigService); - uploadPath = config.get('mediaConfig').backend.filesystem.uploadPath; - app = moduleRef.createNestApplication(); - const authConfig = config.get('authConfig') as AuthConfig; - setupSessionMiddleware(app, authConfig); - await app.init(); - //historyService = moduleRef.get(); - userService = moduleRef.get(UsersService); - mediaService = moduleRef.get(MediaService); - identityService = moduleRef.get(IdentityService); - user = await userService.createUser('hardcoded', 'Testy'); - await identityService.createLocalIdentity(user, 'test'); + testSetup = await TestSetup.create(); + + uploadPath = + testSetup.configService.get('mediaConfig').backend.filesystem.uploadPath; + + const authConfig = testSetup.configService.get('authConfig') as AuthConfig; + setupSessionMiddleware(testSetup.app, authConfig); + await testSetup.app.init(); + + user = await testSetup.userService.createUser('hardcoded', 'Testy'); + await testSetup.identityService.createLocalIdentity(user, 'test'); - const notesService = moduleRef.get(NotesService); content = 'This is a test note.'; alias2 = 'note2'; - note1 = await notesService.createNote(content, undefined, user); - note2 = await notesService.createNote(content, alias2, user); - agent = request.agent(app.getHttpServer()); + note1 = await testSetup.notesService.createNote(content, undefined, user); + note2 = await testSetup.notesService.createNote(content, alias2, user); + agent = request.agent(testSetup.app.getHttpServer()); await agent - .post('/auth/local/login') + .post('/api/private/auth/local/login') .send({ username: 'hardcoded', password: 'test' }) .expect(201); }); it('GET /me', async () => { - const userInfo = userService.toUserDto(user); + const userInfo = testSetup.userService.toUserDto(user); const response = await agent - .get('/me') + .get('/api/private/me') .expect('Content-Type', /json/) .expect(200); const gotUser = response.body as UserInfoDto; @@ -121,19 +61,19 @@ describe('Me', () => { it('GET /me/media', async () => { const responseBefore = await agent - .get('/me/media/') + .get('/api/private/me/media/') .expect('Content-Type', /json/) .expect(200); expect(responseBefore.body).toHaveLength(0); const testImage = await fs.readFile('test/public-api/fixtures/test.png'); - const url0 = await mediaService.saveFile(testImage, user, note1); - const url1 = await mediaService.saveFile(testImage, user, note1); - const url2 = await mediaService.saveFile(testImage, user, note2); - const url3 = await mediaService.saveFile(testImage, user, note2); + const url0 = await testSetup.mediaService.saveFile(testImage, user, note1); + const url1 = await testSetup.mediaService.saveFile(testImage, user, note1); + const url2 = await testSetup.mediaService.saveFile(testImage, user, note2); + const url3 = await testSetup.mediaService.saveFile(testImage, user, note2); const response = await agent - .get('/me/media/') + .get('/api/private/me/media/') .expect('Content-Type', /json/) .expect(200); expect(response.body).toHaveLength(4); @@ -141,9 +81,9 @@ describe('Me', () => { expect(response.body[1].url).toEqual(url1); expect(response.body[2].url).toEqual(url2); expect(response.body[3].url).toEqual(url3); - const mediaUploads = await mediaService.listUploadsByUser(user); + const mediaUploads = await testSetup.mediaService.listUploadsByUser(user); for (const upload of mediaUploads) { - await mediaService.deleteFile(upload); + await testSetup.mediaService.deleteFile(upload); } await fs.rmdir(uploadPath); }); @@ -152,28 +92,30 @@ describe('Me', () => { const newDisplayName = 'Another name'; expect(user.displayName).not.toEqual(newDisplayName); await agent - .post('/me/profile') + .post('/api/private/me/profile') .send({ name: newDisplayName, }) .expect(200); - const dbUser = await userService.getUserByUsername('hardcoded'); + const dbUser = await testSetup.userService.getUserByUsername('hardcoded'); expect(dbUser.displayName).toEqual(newDisplayName); }); it('DELETE /me', async () => { const testImage = await fs.readFile('test/public-api/fixtures/test.png'); - const url0 = await mediaService.saveFile(testImage, user, note1); - const dbUser = await userService.getUserByUsername('hardcoded'); + const url0 = await testSetup.mediaService.saveFile(testImage, user, note1); + const dbUser = await testSetup.userService.getUserByUsername('hardcoded'); expect(dbUser).toBeInstanceOf(User); - const mediaUploads = await mediaService.listUploadsByUser(dbUser); + const mediaUploads = await testSetup.mediaService.listUploadsByUser(dbUser); expect(mediaUploads).toHaveLength(1); expect(mediaUploads[0].fileUrl).toEqual(url0); - await agent.delete('/me').expect(204); - await expect(userService.getUserByUsername('hardcoded')).rejects.toThrow( - NotInDBError, + await agent.delete('/api/private/me').expect(204); + await expect( + testSetup.userService.getUserByUsername('hardcoded'), + ).rejects.toThrow(NotInDBError); + const mediaUploadsAfter = await testSetup.mediaService.listUploadsByNote( + note1, ); - const mediaUploadsAfter = await mediaService.listUploadsByNote(note1); expect(mediaUploadsAfter).toHaveLength(0); }); }); diff --git a/test/private-api/media.e2e-spec.ts b/test/private-api/media.e2e-spec.ts index 725421fce..2160ce71f 100644 --- a/test/private-api/media.e2e-spec.ts +++ b/test/private-api/media.e2e-spec.ts @@ -3,100 +3,51 @@ * * SPDX-License-Identifier: AGPL-3.0-only */ -import { ConfigModule, ConfigService } from '@nestjs/config'; -import { NestExpressApplication } from '@nestjs/platform-express'; -import { Test } from '@nestjs/testing'; -import { TypeOrmModule } from '@nestjs/typeorm'; import { promises as fs } from 'fs'; import { join } from 'path'; +import { User } from 'src/users/user.entity'; import request from 'supertest'; -import { PrivateApiModule } from '../../src/api/private/private-api.module'; -import { AuthModule } from '../../src/auth/auth.module'; import { AuthConfig } from '../../src/config/auth.config'; -import appConfigMock from '../../src/config/mock/app.config.mock'; -import authConfigMock from '../../src/config/mock/auth.config.mock'; -import customizationConfigMock from '../../src/config/mock/customization.config.mock'; -import externalConfigMock from '../../src/config/mock/external-services.config.mock'; -import mediaConfigMock from '../../src/config/mock/media.config.mock'; -import { GroupsModule } from '../../src/groups/groups.module'; -import { IdentityService } from '../../src/identity/identity.service'; import { ConsoleLoggerService } from '../../src/logger/console-logger.service'; -import { LoggerModule } from '../../src/logger/logger.module'; -import { MediaModule } from '../../src/media/media.module'; -import { MediaService } from '../../src/media/media.service'; -import { Note } from '../../src/notes/note.entity'; -import { NotesModule } from '../../src/notes/notes.module'; -import { NotesService } from '../../src/notes/notes.service'; -import { PermissionsModule } from '../../src/permissions/permissions.module'; -import { User } from '../../src/users/user.entity'; -import { UsersService } from '../../src/users/users.service'; import { setupSessionMiddleware } from '../../src/utils/session'; +import { TestSetup } from '../test-setup'; import { ensureDeleted } from '../utils'; describe('Media', () => { - let identityService: IdentityService; - let app: NestExpressApplication; - let mediaService: MediaService; + let testSetup: TestSetup; + let uploadPath: string; let agent: request.SuperAgentTest; - let testNote: Note; let user: User; beforeAll(async () => { - const moduleRef = await Test.createTestingModule({ - imports: [ - ConfigModule.forRoot({ - isGlobal: true, - load: [ - mediaConfigMock, - appConfigMock, - authConfigMock, - customizationConfigMock, - externalConfigMock, - ], - }), - PrivateApiModule, - MediaModule, - TypeOrmModule.forRoot({ - type: 'sqlite', - database: './hedgedoc-e2e-private-media.sqlite', - autoLoadEntities: true, - dropSchema: true, - synchronize: true, - }), - NotesModule, - PermissionsModule, - GroupsModule, - LoggerModule, - AuthModule, - ], - }).compile(); - const config = moduleRef.get(ConfigService); - uploadPath = config.get('mediaConfig').backend.filesystem.uploadPath; - app = moduleRef.createNestApplication(); - app.useStaticAssets(uploadPath, { + testSetup = await TestSetup.create(); + + uploadPath = + testSetup.configService.get('mediaConfig').backend.filesystem.uploadPath; + + testSetup.app.useStaticAssets(uploadPath, { prefix: '/uploads', }); - const authConfig = config.get('authConfig') as AuthConfig; - setupSessionMiddleware(app, authConfig); - await app.init(); - const logger = await app.resolve(ConsoleLoggerService); + const authConfig = testSetup.configService.get('authConfig') as AuthConfig; + setupSessionMiddleware(testSetup.app, authConfig); + await testSetup.app.init(); + + const logger = await testSetup.app.resolve(ConsoleLoggerService); logger.log('Switching logger', 'AppBootstrap'); - app.useLogger(logger); - identityService = moduleRef.get(IdentityService); - const notesService: NotesService = moduleRef.get(NotesService); - const userService: UsersService = moduleRef.get(UsersService); - user = await userService.createUser('hardcoded', 'Testy'); - testNote = await notesService.createNote( + testSetup.app.useLogger(logger); + + await testSetup.notesService.createNote( 'test content', 'test_upload_media', ); - mediaService = moduleRef.get(MediaService); - await identityService.createLocalIdentity(user, 'test'); - agent = request.agent(app.getHttpServer()); + user = await testSetup.userService.createUser('hardcoded', 'Testy'); + await testSetup.identityService.createLocalIdentity(user, 'test'); + + agent = request.agent(testSetup.app.getHttpServer()); await agent - .post('/auth/local/login') + .post('/api/private/auth/local/login') .send({ username: 'hardcoded', password: 'test' }) .expect(201); }); @@ -104,7 +55,7 @@ describe('Media', () => { describe('POST /media', () => { it('works', async () => { const uploadResponse = await agent - .post('/media') + .post('/api/private/media') .attach('file', 'test/private-api/fixtures/test.png') .set('HedgeDoc-Note', 'test_upload_media') .expect('Content-Type', /json/) @@ -124,7 +75,7 @@ describe('Media', () => { }); it('MIME type not supported', async () => { await agent - .post('/media') + .post('/api/private/media') .attach('file', 'test/private-api/fixtures/test.zip') .set('HedgeDoc-Note', 'test_upload_media') .expect(400); @@ -132,7 +83,7 @@ describe('Media', () => { }); it('note does not exist', async () => { await agent - .post('/media') + .post('/api/private/media') .attach('file', 'test/private-api/fixtures/test.zip') .set('HedgeDoc-Note', 'i_dont_exist') .expect(400); @@ -143,7 +94,7 @@ describe('Media', () => { mode: '444', }); await agent - .post('/media') + .post('/api/private/media') .attach('file', 'test/private-api/fixtures/test.png') .set('HedgeDoc-Note', 'test_upload_media') .expect('Content-Type', /json/) @@ -156,15 +107,23 @@ describe('Media', () => { }); it('DELETE /media/{filename}', async () => { + const testNote = await testSetup.notesService.createNote( + 'test content', + 'test_delete_media', + ); const testImage = await fs.readFile('test/private-api/fixtures/test.png'); - const url = await mediaService.saveFile(testImage, user, testNote); + const url = await testSetup.mediaService.saveFile( + testImage, + user, + testNote, + ); const filename = url.split('/').pop() || ''; - await agent.delete('/media/' + filename).expect(204); + await agent.delete('/api/private/media/' + filename).expect(204); }); afterAll(async () => { // Delete the upload folder await ensureDeleted(uploadPath); - await app.close(); + await testSetup.app.close(); }); }); diff --git a/test/private-api/notes.e2e-spec.ts b/test/private-api/notes.e2e-spec.ts index 75cdc5313..1589c6d3d 100644 --- a/test/private-api/notes.e2e-spec.ts +++ b/test/private-api/notes.e2e-spec.ts @@ -3,40 +3,19 @@ * * SPDX-License-Identifier: AGPL-3.0-only */ -import { INestApplication } from '@nestjs/common'; -import { ConfigModule, ConfigService } from '@nestjs/config'; -import { Test } from '@nestjs/testing'; -import { TypeOrmModule } from '@nestjs/typeorm'; import { promises as fs } from 'fs'; import { join } from 'path'; import request from 'supertest'; -import { PrivateApiModule } from '../../src/api/private/private-api.module'; -import { AuthModule } from '../../src/auth/auth.module'; import { AuthConfig } from '../../src/config/auth.config'; -import appConfigMock from '../../src/config/mock/app.config.mock'; -import authConfigMock from '../../src/config/mock/auth.config.mock'; -import customizationConfigMock from '../../src/config/mock/customization.config.mock'; -import externalConfigMock from '../../src/config/mock/external-services.config.mock'; -import mediaConfigMock from '../../src/config/mock/media.config.mock'; import { NotInDBError } from '../../src/errors/errors'; -import { GroupsModule } from '../../src/groups/groups.module'; -import { IdentityService } from '../../src/identity/identity.service'; -import { LoggerModule } from '../../src/logger/logger.module'; -import { MediaService } from '../../src/media/media.service'; -import { NotesModule } from '../../src/notes/notes.module'; -import { NotesService } from '../../src/notes/notes.service'; -import { PermissionsModule } from '../../src/permissions/permissions.module'; import { User } from '../../src/users/user.entity'; -import { UsersModule } from '../../src/users/users.module'; -import { UsersService } from '../../src/users/users.service'; import { setupSessionMiddleware } from '../../src/utils/session'; +import { TestSetup } from '../test-setup'; describe('Notes', () => { - let app: INestApplication; - let notesService: NotesService; - let mediaService: MediaService; - let identityService: IdentityService; + let testSetup: TestSetup; + let user: User; let user2: User; let content: string; @@ -46,70 +25,48 @@ describe('Notes', () => { let agent: request.SuperAgentTest; beforeAll(async () => { - const moduleRef = await Test.createTestingModule({ - imports: [ - ConfigModule.forRoot({ - isGlobal: true, - load: [ - mediaConfigMock, - appConfigMock, - authConfigMock, - customizationConfigMock, - externalConfigMock, - ], - }), - PrivateApiModule, - NotesModule, - PermissionsModule, - GroupsModule, - TypeOrmModule.forRoot({ - type: 'sqlite', - database: './hedgedoc-e2e-private-notes.sqlite', - autoLoadEntities: true, - synchronize: true, - dropSchema: true, - }), - LoggerModule, - AuthModule, - UsersModule, - ], - }).compile(); + testSetup = await TestSetup.create(); - const config = moduleRef.get(ConfigService); - forbiddenNoteId = config.get('appConfig').forbiddenNoteIds[0]; - uploadPath = config.get('mediaConfig').backend.filesystem.uploadPath; - app = moduleRef.createNestApplication(); - const authConfig = config.get('authConfig') as AuthConfig; - setupSessionMiddleware(app, authConfig); - await app.init(); - notesService = moduleRef.get(NotesService); - mediaService = moduleRef.get(MediaService); - identityService = moduleRef.get(IdentityService); - const userService = moduleRef.get(UsersService); - user = await userService.createUser('hardcoded', 'Testy'); - await identityService.createLocalIdentity(user, 'test'); - user2 = await userService.createUser('hardcoded2', 'Max Mustermann'); - await identityService.createLocalIdentity(user2, 'test'); + forbiddenNoteId = + testSetup.configService.get('appConfig').forbiddenNoteIds[0]; + uploadPath = + testSetup.configService.get('mediaConfig').backend.filesystem.uploadPath; + + const authConfig = testSetup.configService.get('authConfig') as AuthConfig; + setupSessionMiddleware(testSetup.app, authConfig); + + await testSetup.app.init(); + + user = await testSetup.userService.createUser('hardcoded', 'Testy'); + await testSetup.identityService.createLocalIdentity(user, 'test'); + user2 = await testSetup.userService.createUser( + 'hardcoded2', + 'Max Mustermann', + ); + await testSetup.identityService.createLocalIdentity(user2, 'test'); content = 'This is a test note.'; testImage = await fs.readFile('test/public-api/fixtures/test.png'); - agent = request.agent(app.getHttpServer()); + + agent = request.agent(testSetup.app.getHttpServer()); await agent - .post('/auth/local/login') + .post('/api/private/auth/local/login') .send({ username: 'hardcoded', password: 'test' }) .expect(201); }); it('POST /notes', async () => { const response = await agent - .post('/notes') + .post('/api/private/notes') .set('Content-Type', 'text/markdown') .send(content) .expect('Content-Type', /json/) .expect(201); expect(response.body.metadata?.id).toBeDefined(); expect( - await notesService.getNoteContent( - await notesService.getNoteByIdOrAlias(response.body.metadata.id), + await testSetup.notesService.getNoteContent( + await testSetup.notesService.getNoteByIdOrAlias( + response.body.metadata.id, + ), ), ).toEqual(content); }); @@ -117,9 +74,9 @@ describe('Notes', () => { describe('GET /notes/{note}', () => { it('works with an existing note', async () => { // check if we can succefully get a note that exists - await notesService.createNote(content, 'test1', user); + await testSetup.notesService.createNote(content, 'test1', user); const response = await agent - .get('/notes/test1') + .get('/api/private/notes/test1') .expect('Content-Type', /json/) .expect(200); expect(response.body.content).toEqual(content); @@ -127,7 +84,7 @@ describe('Notes', () => { it('fails with an non-existing note', async () => { // check if a missing note correctly returns 404 await agent - .get('/notes/i_dont_exist') + .get('/api/private/notes/i_dont_exist') .expect('Content-Type', /json/) .expect(404); }); @@ -136,22 +93,24 @@ describe('Notes', () => { describe('POST /notes/{note}', () => { it('works with a non-existing alias', async () => { const response = await agent - .post('/notes/test2') + .post('/api/private/notes/test2') .set('Content-Type', 'text/markdown') .send(content) .expect('Content-Type', /json/) .expect(201); expect(response.body.metadata?.id).toBeDefined(); return expect( - await notesService.getNoteContent( - await notesService.getNoteByIdOrAlias(response.body.metadata?.id), + await testSetup.notesService.getNoteContent( + await testSetup.notesService.getNoteByIdOrAlias( + response.body.metadata?.id, + ), ), ).toEqual(content); }); it('fails with a forbidden alias', async () => { await agent - .post(`/notes/${forbiddenNoteId}`) + .post(`/api/private/notes/${forbiddenNoteId}`) .set('Content-Type', 'text/markdown') .send(content) .expect('Content-Type', /json/) @@ -160,7 +119,7 @@ describe('Notes', () => { it('fails with a existing alias', async () => { await agent - .post('/notes/test2') + .post('/api/private/notes/test2') .set('Content-Type', 'text/markdown') .send(content) .expect('Content-Type', /json/) @@ -172,36 +131,56 @@ describe('Notes', () => { describe('works', () => { it('with an existing alias and keepMedia false', async () => { const noteId = 'test3'; - const note = await notesService.createNote(content, noteId, user); - await mediaService.saveFile(testImage, user, note); + const note = await testSetup.notesService.createNote( + content, + noteId, + user, + ); + await testSetup.mediaService.saveFile(testImage, user, note); await agent - .delete(`/notes/${noteId}`) + .delete(`/api/private/notes/${noteId}`) .set('Content-Type', 'application/json') .send({ keepMedia: false, }) .expect(204); - await expect(notesService.getNoteByIdOrAlias(noteId)).rejects.toEqual( + await expect( + testSetup.notesService.getNoteByIdOrAlias(noteId), + ).rejects.toEqual( new NotInDBError(`Note with id/alias '${noteId}' not found.`), ); - expect(await mediaService.listUploadsByUser(user)).toHaveLength(0); + expect( + await testSetup.mediaService.listUploadsByUser(user), + ).toHaveLength(0); await fs.rmdir(uploadPath); }); it('with an existing alias and keepMedia true', async () => { const noteId = 'test3a'; - const note = await notesService.createNote(content, noteId, user); - const url = await mediaService.saveFile(testImage, user, note); + const note = await testSetup.notesService.createNote( + content, + noteId, + user, + ); + const url = await testSetup.mediaService.saveFile( + testImage, + user, + note, + ); await agent - .delete(`/notes/${noteId}`) + .delete(`/api/private/notes/${noteId}`) .set('Content-Type', 'application/json') .send({ keepMedia: true, }) .expect(204); - await expect(notesService.getNoteByIdOrAlias(noteId)).rejects.toEqual( + await expect( + testSetup.notesService.getNoteByIdOrAlias(noteId), + ).rejects.toEqual( new NotInDBError(`Note with id/alias '${noteId}' not found.`), ); - expect(await mediaService.listUploadsByUser(user)).toHaveLength(1); + expect( + await testSetup.mediaService.listUploadsByUser(user), + ).toHaveLength(1); // Remove /upload/ from path as we just need the filename. const fileName = url.replace('/uploads/', ''); // delete the file afterwards @@ -210,31 +189,33 @@ describe('Notes', () => { }); }); it('fails with a forbidden alias', async () => { - await agent.delete(`/notes/${forbiddenNoteId}`).expect(400); + await agent.delete(`/api/private/notes/${forbiddenNoteId}`).expect(400); }); it('fails with a non-existing alias', async () => { - await agent.delete('/notes/i_dont_exist').expect(404); + await agent.delete('/api/private/notes/i_dont_exist').expect(404); }); }); describe('GET /notes/{note}/revisions', () => { it('works with existing alias', async () => { - await notesService.createNote(content, 'test4', user); + await testSetup.notesService.createNote(content, 'test4', user); const response = await agent - .get('/notes/test4/revisions') + .get('/api/private/notes/test4/revisions') .expect('Content-Type', /json/) .expect(200); expect(response.body).toHaveLength(1); }); it('fails with a forbidden alias', async () => { - await agent.get(`/notes/${forbiddenNoteId}/revisions`).expect(400); + await agent + .get(`/api/private/notes/${forbiddenNoteId}/revisions`) + .expect(400); }); it('fails with non-existing alias', async () => { // check if a missing note correctly returns 404 await agent - .get('/notes/i_dont_exist/revisions') + .get('/api/private/notes/i_dont_exist/revisions') .expect('Content-Type', /json/) .expect(404); }); @@ -243,30 +224,36 @@ describe('Notes', () => { describe('DELETE /notes/{note}/revisions', () => { it('works with an existing alias', async () => { const noteId = 'test8'; - const note = await notesService.createNote(content, noteId, user); - await notesService.updateNote(note, 'update'); + const note = await testSetup.notesService.createNote( + content, + noteId, + user, + ); + await testSetup.notesService.updateNote(note, 'update'); const responseBeforeDeleting = await agent - .get('/notes/test8/revisions') + .get('/api/private/notes/test8/revisions') .expect('Content-Type', /json/) .expect(200); expect(responseBeforeDeleting.body).toHaveLength(2); await agent - .delete(`/notes/${noteId}/revisions`) + .delete(`/api/private/notes/${noteId}/revisions`) .set('Content-Type', 'application/json') .expect(204); const responseAfterDeleting = await agent - .get('/notes/test8/revisions') + .get('/api/private/notes/test8/revisions') .expect('Content-Type', /json/) .expect(200); expect(responseAfterDeleting.body).toHaveLength(1); }); it('fails with a forbidden alias', async () => { - await agent.delete(`/notes/${forbiddenNoteId}/revisions`).expect(400); + await agent + .delete(`/api/private/notes/${forbiddenNoteId}/revisions`) + .expect(400); }); it('fails with non-existing alias', async () => { // check if a missing note correctly returns 404 await agent - .delete('/notes/i_dont_exist/revisions') + .delete('/api/private/notes/i_dont_exist/revisions') .expect('Content-Type', /json/) .expect(404); }); @@ -274,21 +261,27 @@ describe('Notes', () => { describe('GET /notes/{note}/revisions/{revision-id}', () => { it('works with an existing alias', async () => { - const note = await notesService.createNote(content, 'test5', user); - const revision = await notesService.getLatestRevision(note); + const note = await testSetup.notesService.createNote( + content, + 'test5', + user, + ); + const revision = await testSetup.notesService.getLatestRevision(note); const response = await agent - .get(`/notes/test5/revisions/${revision.id}`) + .get(`/api/private/notes/test5/revisions/${revision.id}`) .expect('Content-Type', /json/) .expect(200); expect(response.body.content).toEqual(content); }); it('fails with a forbidden alias', async () => { - await agent.get(`/notes/${forbiddenNoteId}/revisions/1`).expect(400); + await agent + .get(`/api/private/notes/${forbiddenNoteId}/revisions/1`) + .expect(400); }); it('fails with non-existing alias', async () => { // check if a missing note correctly returns 404 await agent - .get('/notes/i_dont_exist/revisions/1') + .get('/api/private/notes/i_dont_exist/revisions/1') .expect('Content-Type', /json/) .expect(404); }); @@ -298,20 +291,36 @@ describe('Notes', () => { it('works', async () => { const alias = 'test6'; const extraAlias = 'test7'; - const note1 = await notesService.createNote(content, alias, user); - const note2 = await notesService.createNote(content, extraAlias, user); + const note1 = await testSetup.notesService.createNote( + content, + alias, + user, + ); + const note2 = await testSetup.notesService.createNote( + content, + extraAlias, + user, + ); const response = await agent - .get(`/notes/${alias}/media/`) + .get(`/api/private/notes/${alias}/media/`) .expect('Content-Type', /json/) .expect(200); expect(response.body).toHaveLength(0); const testImage = await fs.readFile('test/private-api/fixtures/test.png'); - const url0 = await mediaService.saveFile(testImage, user, note1); - const url1 = await mediaService.saveFile(testImage, user, note2); + const url0 = await testSetup.mediaService.saveFile( + testImage, + user, + note1, + ); + const url1 = await testSetup.mediaService.saveFile( + testImage, + user, + note2, + ); const responseAfter = await agent - .get(`/notes/${alias}/media/`) + .get(`/api/private/notes/${alias}/media/`) .expect('Content-Type', /json/) .expect(200); expect(responseAfter.body).toHaveLength(1); @@ -326,21 +335,25 @@ describe('Notes', () => { }); it('fails, when note does not exist', async () => { await agent - .get(`/notes/i_dont_exist/media/`) + .get(`/api/private/notes/i_dont_exist/media/`) .expect('Content-Type', /json/) .expect(404); }); it("fails, when user can't read note", async () => { const alias = 'test11'; - await notesService.createNote('This is a test note.', alias, user2); + await testSetup.notesService.createNote( + 'This is a test note.', + alias, + user2, + ); await agent - .get(`/notes/${alias}/media/`) + .get(`/api/private/notes/${alias}/media/`) .expect('Content-Type', /json/) .expect(401); }); }); afterAll(async () => { - await app.close(); + await testSetup.app.close(); }); }); diff --git a/test/private-api/tokens.e2e-spec.ts b/test/private-api/tokens.e2e-spec.ts new file mode 100644 index 000000000..b49e769cf --- /dev/null +++ b/test/private-api/tokens.e2e-spec.ts @@ -0,0 +1,78 @@ +/* + * SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ +import request from 'supertest'; + +import { AuthConfig } from '../../src/config/auth.config'; +import { User } from '../../src/users/user.entity'; +import { setupSessionMiddleware } from '../../src/utils/session'; +import { TestSetup } from '../test-setup'; + +describe('Tokens', () => { + let testSetup: TestSetup; + let agent: request.SuperAgentTest; + + let user: User; + let keyId: string; + + beforeAll(async () => { + testSetup = await TestSetup.create(); + + user = await testSetup.userService.createUser('hardcoded', 'Testy'); + await testSetup.identityService.createLocalIdentity(user, 'test'); + + const authConfig = testSetup.configService.get('authConfig') as AuthConfig; + setupSessionMiddleware(testSetup.app, authConfig); + + await testSetup.app.init(); + + agent = request.agent(testSetup.app.getHttpServer()); + await agent + .post('/api/private/auth/local/login') + .send({ username: 'hardcoded', password: 'test' }) + .expect(201); + }); + + it(`POST /tokens`, async () => { + const tokenName = 'testToken'; + const response = await agent + .post('/api/private/tokens') + .send({ + label: tokenName, + }) + .expect('Content-Type', /json/) + .expect(201); + keyId = response.body.keyId; + expect(response.body.label).toBe(tokenName); + expect(response.body.validUntil).toBe(null); + expect(response.body.lastUsed).toBe(null); + expect(response.body.secret.length).toBe(84); + }); + + it(`GET /tokens`, async () => { + const tokenName = 'testToken'; + const response = await agent + .get('/api/private/tokens/') + .expect('Content-Type', /json/) + .expect(200); + expect(response.body[0].label).toBe(tokenName); + expect(response.body[0].validUntil).toBe(null); + expect(response.body[0].lastUsed).toBe(null); + expect(response.body[0].secret).not.toBeDefined(); + }); + it(`DELETE /tokens/:keyid`, async () => { + const response = await agent + .delete('/api/private/tokens/' + keyId) + .expect(204); + expect(response.body).toStrictEqual({}); + }); + it(`GET /tokens 2`, async () => { + const response = await agent + .get('/api/private/tokens/') + .expect('Content-Type', /json/) + .expect(200); + expect(response.body).toStrictEqual([]); + }); +}); diff --git a/test/public-api/alias.e2e-spec.ts b/test/public-api/alias.e2e-spec.ts index 5bc359c61..c8c8284b9 100644 --- a/test/public-api/alias.e2e-spec.ts +++ b/test/public-api/alias.e2e-spec.ts @@ -3,73 +3,29 @@ * * SPDX-License-Identifier: AGPL-3.0-only */ -import { INestApplication } from '@nestjs/common'; -import { ConfigModule, ConfigService } from '@nestjs/config'; -import { Test } from '@nestjs/testing'; -import { TypeOrmModule } from '@nestjs/typeorm'; import request from 'supertest'; -import { PublicApiModule } from '../../src/api/public/public-api.module'; -import { AuthModule } from '../../src/auth/auth.module'; -import { MockAuthGuard } from '../../src/auth/mock-auth.guard'; -import { TokenAuthGuard } from '../../src/auth/token.strategy'; -import appConfigMock from '../../src/config/mock/app.config.mock'; -import mediaConfigMock from '../../src/config/mock/media.config.mock'; -import { GroupsModule } from '../../src/groups/groups.module'; -import { LoggerModule } from '../../src/logger/logger.module'; import { AliasCreateDto } from '../../src/notes/alias-create.dto'; import { AliasUpdateDto } from '../../src/notes/alias-update.dto'; -import { AliasService } from '../../src/notes/alias.service'; -import { NotesModule } from '../../src/notes/notes.module'; -import { NotesService } from '../../src/notes/notes.service'; -import { PermissionsModule } from '../../src/permissions/permissions.module'; import { User } from '../../src/users/user.entity'; -import { UsersModule } from '../../src/users/users.module'; -import { UsersService } from '../../src/users/users.service'; +import { TestSetup } from '../test-setup'; describe('Notes', () => { - let app: INestApplication; - let notesService: NotesService; - let aliasService: AliasService; + let testSetup: TestSetup; + let user: User; let content: string; let forbiddenNoteId: string; beforeAll(async () => { - const moduleRef = await Test.createTestingModule({ - imports: [ - ConfigModule.forRoot({ - isGlobal: true, - load: [mediaConfigMock, appConfigMock], - }), - PublicApiModule, - NotesModule, - PermissionsModule, - GroupsModule, - TypeOrmModule.forRoot({ - type: 'sqlite', - database: './hedgedoc-e2e-notes.sqlite', - autoLoadEntities: true, - synchronize: true, - dropSchema: true, - }), - LoggerModule, - AuthModule, - UsersModule, - ], - }) - .overrideGuard(TokenAuthGuard) - .useClass(MockAuthGuard) - .compile(); + testSetup = await TestSetup.create(); - const config = moduleRef.get(ConfigService); - forbiddenNoteId = config.get('appConfig').forbiddenNoteIds[0]; - app = moduleRef.createNestApplication(); - await app.init(); - notesService = moduleRef.get(NotesService); - aliasService = moduleRef.get(AliasService); - const userService = moduleRef.get(UsersService); - user = await userService.createUser('hardcoded', 'Testy'); + forbiddenNoteId = + testSetup.configService.get('appConfig').forbiddenNoteIds[0]; + + await testSetup.app.init(); + + user = await testSetup.userService.createUser('hardcoded', 'Testy'); content = 'This is a test note.'; }); @@ -81,23 +37,27 @@ describe('Notes', () => { }; let publicId = ''; beforeAll(async () => { - const note = await notesService.createNote(content, testAlias, user); + const note = await testSetup.notesService.createNote( + content, + testAlias, + user, + ); publicId = note.publicId; }); it('create with normal alias', async () => { const newAlias = 'normalAlias'; newAliasDto.newAlias = newAlias; - const metadata = await request(app.getHttpServer()) - .post(`/alias`) + const metadata = await request(testSetup.app.getHttpServer()) + .post(`/api/v2/alias`) .set('Content-Type', 'application/json') .send(newAliasDto) .expect(201); expect(metadata.body.name).toEqual(newAlias); expect(metadata.body.primaryAlias).toBeFalsy(); expect(metadata.body.noteId).toEqual(publicId); - const note = await request(app.getHttpServer()) - .get(`/notes/${newAlias}`) + const note = await request(testSetup.app.getHttpServer()) + .get(`/api/v2/notes/${newAlias}`) .expect(200); expect(note.body.metadata.aliases).toContain(newAlias); expect(note.body.metadata.primaryAlias).toBeTruthy(); @@ -107,16 +67,16 @@ describe('Notes', () => { describe('does not create an alias', () => { it('because of a forbidden alias', async () => { newAliasDto.newAlias = forbiddenNoteId; - await request(app.getHttpServer()) - .post(`/alias`) + await request(testSetup.app.getHttpServer()) + .post(`/api/v2/alias`) .set('Content-Type', 'application/json') .send(newAliasDto) .expect(400); }); it('because of a alias that is a public id', async () => { newAliasDto.newAlias = publicId; - await request(app.getHttpServer()) - .post(`/alias`) + await request(testSetup.app.getHttpServer()) + .post(`/api/v2/alias`) .set('Content-Type', 'application/json') .send(newAliasDto) .expect(400); @@ -132,22 +92,26 @@ describe('Notes', () => { }; let publicId = ''; beforeAll(async () => { - const note = await notesService.createNote(content, testAlias, user); + const note = await testSetup.notesService.createNote( + content, + testAlias, + user, + ); publicId = note.publicId; - await aliasService.addAlias(note, newAlias); + await testSetup.aliasService.addAlias(note, newAlias); }); it('updates a note with a normal alias', async () => { - const metadata = await request(app.getHttpServer()) - .put(`/alias/${newAlias}`) + const metadata = await request(testSetup.app.getHttpServer()) + .put(`/api/v2/alias/${newAlias}`) .set('Content-Type', 'application/json') .send(changeAliasDto) .expect(200); expect(metadata.body.name).toEqual(newAlias); expect(metadata.body.primaryAlias).toBeTruthy(); expect(metadata.body.noteId).toEqual(publicId); - const note = await request(app.getHttpServer()) - .get(`/notes/${newAlias}`) + const note = await request(testSetup.app.getHttpServer()) + .get(`/api/v2/notes/${newAlias}`) .expect(200); expect(note.body.metadata.aliases).toContain(newAlias); expect(note.body.metadata.primaryAlias).toBeTruthy(); @@ -156,16 +120,16 @@ describe('Notes', () => { describe('does not update', () => { it('a note with unknown alias', async () => { - await request(app.getHttpServer()) - .put(`/alias/i_dont_exist`) + await request(testSetup.app.getHttpServer()) + .put(`/api/v2/alias/i_dont_exist`) .set('Content-Type', 'application/json') .send(changeAliasDto) .expect(404); }); it('if the property primaryAlias is false', async () => { changeAliasDto.primaryAlias = false; - await request(app.getHttpServer()) - .put(`/alias/${newAlias}`) + await request(testSetup.app.getHttpServer()) + .put(`/api/v2/alias/${newAlias}`) .set('Content-Type', 'application/json') .send(changeAliasDto) .expect(400); @@ -177,38 +141,46 @@ describe('Notes', () => { const testAlias = 'aliasTest3'; const newAlias = 'normalAlias3'; beforeAll(async () => { - const note = await notesService.createNote(content, testAlias, user); - await aliasService.addAlias(note, newAlias); + const note = await testSetup.notesService.createNote( + content, + testAlias, + user, + ); + await testSetup.aliasService.addAlias(note, newAlias); }); it('deletes a normal alias', async () => { - await request(app.getHttpServer()) - .delete(`/alias/${newAlias}`) + await request(testSetup.app.getHttpServer()) + .delete(`/api/v2/alias/${newAlias}`) .expect(204); - await request(app.getHttpServer()).get(`/notes/${newAlias}`).expect(404); + await request(testSetup.app.getHttpServer()) + .get(`/api/v2/notes/${newAlias}`) + .expect(404); }); it('does not delete an unknown alias', async () => { - await request(app.getHttpServer()) - .delete(`/alias/i_dont_exist`) + await request(testSetup.app.getHttpServer()) + .delete(`/api/v2/alias/i_dont_exist`) .expect(404); }); it('does not delete a primary alias (if it is not the only one)', async () => { - const note = await notesService.getNoteByIdOrAlias(testAlias); - await aliasService.addAlias(note, newAlias); - await request(app.getHttpServer()) - .delete(`/alias/${testAlias}`) + const note = await testSetup.notesService.getNoteByIdOrAlias(testAlias); + await testSetup.aliasService.addAlias(note, newAlias); + await request(testSetup.app.getHttpServer()) + .delete(`/api/v2/alias/${testAlias}`) .expect(400); - await request(app.getHttpServer()).get(`/notes/${newAlias}`).expect(200); + await request(testSetup.app.getHttpServer()) + .get(`/api/v2/notes/${newAlias}`) + .expect(200); }); it('deletes a primary alias (if it is the only one)', async () => { - await request(app.getHttpServer()) - .delete(`/alias/${newAlias}`) + await request(testSetup.app.getHttpServer()) + .delete(`/api/v2/alias/${newAlias}`) .expect(204); - await request(app.getHttpServer()) - .delete(`/alias/${testAlias}`) + await request(testSetup.app.getHttpServer()) + .delete(`/api/v2/alias/${testAlias}`) .expect(204); }); }); diff --git a/test/public-api/me.e2e-spec.ts b/test/public-api/me.e2e-spec.ts index 95452e957..a52a52137 100644 --- a/test/public-api/me.e2e-spec.ts +++ b/test/public-api/me.e2e-spec.ts @@ -3,90 +3,38 @@ * * SPDX-License-Identifier: AGPL-3.0-only */ -import { INestApplication } from '@nestjs/common'; -import { ConfigModule, ConfigService } from '@nestjs/config'; -import { Test } from '@nestjs/testing'; -import { TypeOrmModule } from '@nestjs/typeorm'; import { promises as fs } from 'fs'; import { join } from 'path'; import request from 'supertest'; -import { PublicApiModule } from '../../src/api/public/public-api.module'; -import { AuthModule } from '../../src/auth/auth.module'; -import { MockAuthGuard } from '../../src/auth/mock-auth.guard'; -import { TokenAuthGuard } from '../../src/auth/token.strategy'; -import appConfigMock from '../../src/config/mock/app.config.mock'; -import mediaConfigMock from '../../src/config/mock/media.config.mock'; -import { GroupsModule } from '../../src/groups/groups.module'; import { HistoryEntryUpdateDto } from '../../src/history/history-entry-update.dto'; import { HistoryEntryDto } from '../../src/history/history-entry.dto'; -import { HistoryModule } from '../../src/history/history.module'; -import { HistoryService } from '../../src/history/history.service'; -import { LoggerModule } from '../../src/logger/logger.module'; -import { MediaModule } from '../../src/media/media.module'; -import { MediaService } from '../../src/media/media.service'; import { NoteMetadataDto } from '../../src/notes/note-metadata.dto'; -import { NotesModule } from '../../src/notes/notes.module'; -import { NotesService } from '../../src/notes/notes.service'; -import { PermissionsModule } from '../../src/permissions/permissions.module'; import { User } from '../../src/users/user.entity'; -import { UsersModule } from '../../src/users/users.module'; -import { UsersService } from '../../src/users/users.service'; +import { TestSetup } from '../test-setup'; // TODO Tests have to be reworked using UserService functions describe('Me', () => { - let app: INestApplication; - let historyService: HistoryService; - let notesService: NotesService; - let userService: UsersService; - let mediaService: MediaService; + let testSetup: TestSetup; + let uploadPath: string; let user: User; beforeAll(async () => { - const moduleRef = await Test.createTestingModule({ - imports: [ - ConfigModule.forRoot({ - isGlobal: true, - load: [mediaConfigMock, appConfigMock], - }), - PublicApiModule, - NotesModule, - PermissionsModule, - GroupsModule, - TypeOrmModule.forRoot({ - type: 'sqlite', - database: './hedgedoc-e2e-me.sqlite', - autoLoadEntities: true, - synchronize: true, - dropSchema: true, - }), - LoggerModule, - AuthModule, - UsersModule, - HistoryModule, - MediaModule, - ], - }) - .overrideGuard(TokenAuthGuard) - .useClass(MockAuthGuard) - .compile(); - const config = moduleRef.get(ConfigService); - uploadPath = config.get('mediaConfig').backend.filesystem.uploadPath; - app = moduleRef.createNestApplication(); - notesService = moduleRef.get(NotesService); - historyService = moduleRef.get(HistoryService); - userService = moduleRef.get(UsersService); - mediaService = moduleRef.get(MediaService); - user = await userService.createUser('hardcoded', 'Testy'); - await app.init(); + testSetup = await TestSetup.create(); + + uploadPath = + testSetup.configService.get('mediaConfig').backend.filesystem.uploadPath; + + user = await testSetup.userService.createUser('hardcoded', 'Testy'); + await testSetup.app.init(); }); it(`GET /me`, async () => { - const userInfo = userService.toUserDto(user); - const response = await request(app.getHttpServer()) - .get('/me') + const userInfo = testSetup.userService.toUserDto(user); + const response = await request(testSetup.app.getHttpServer()) + .get('/api/v2/me') .expect('Content-Type', /json/) .expect(200); expect(response.body).toEqual(userInfo); @@ -94,16 +42,17 @@ describe('Me', () => { it(`GET /me/history`, async () => { const noteName = 'testGetNoteHistory1'; - const note = await notesService.createNote('', noteName); + const note = await testSetup.notesService.createNote('', noteName); const createdHistoryEntry = - await historyService.updateHistoryEntryTimestamp(note, user); - const response = await request(app.getHttpServer()) - .get('/me/history') + await testSetup.historyService.updateHistoryEntryTimestamp(note, user); + const response = await request(testSetup.app.getHttpServer()) + .get('/api/v2/me/history') .expect('Content-Type', /json/) .expect(200); const history: HistoryEntryDto[] = response.body; expect(history.length).toEqual(1); - const historyDto = historyService.toHistoryEntryDto(createdHistoryEntry); + const historyDto = + testSetup.historyService.toHistoryEntryDto(createdHistoryEntry); for (const historyEntry of history) { expect(historyEntry.identifier).toEqual(historyDto.identifier); expect(historyEntry.title).toEqual(historyDto.title); @@ -118,16 +67,16 @@ describe('Me', () => { describe(`GET /me/history/{note}`, () => { it('works with an existing note', async () => { const noteName = 'testGetNoteHistory2'; - const note = await notesService.createNote('', noteName); + const note = await testSetup.notesService.createNote('', noteName); const createdHistoryEntry = - await historyService.updateHistoryEntryTimestamp(note, user); - const response = await request(app.getHttpServer()) - .get(`/me/history/${noteName}`) + await testSetup.historyService.updateHistoryEntryTimestamp(note, user); + const response = await request(testSetup.app.getHttpServer()) + .get(`/api/v2/me/history/${noteName}`) .expect('Content-Type', /json/) .expect(200); const historyEntry: HistoryEntryDto = response.body; const historyEntryDto = - historyService.toHistoryEntryDto(createdHistoryEntry); + testSetup.historyService.toHistoryEntryDto(createdHistoryEntry); expect(historyEntry.identifier).toEqual(historyEntryDto.identifier); expect(historyEntry.title).toEqual(historyEntryDto.title); expect(historyEntry.tags).toEqual(historyEntryDto.tags); @@ -137,8 +86,8 @@ describe('Me', () => { ); }); it('fails with a non-existing note', async () => { - await request(app.getHttpServer()) - .get('/me/history/i_dont_exist') + await request(testSetup.app.getHttpServer()) + .get('/api/v2/me/history/i_dont_exist') .expect('Content-Type', /json/) .expect(404); }); @@ -147,28 +96,28 @@ describe('Me', () => { describe(`PUT /me/history/{note}`, () => { it('works', async () => { const noteName = 'testGetNoteHistory3'; - const note = await notesService.createNote('', noteName); - await historyService.updateHistoryEntryTimestamp(note, user); + const note = await testSetup.notesService.createNote('', noteName); + await testSetup.historyService.updateHistoryEntryTimestamp(note, user); const historyEntryUpdateDto = new HistoryEntryUpdateDto(); historyEntryUpdateDto.pinStatus = true; - const response = await request(app.getHttpServer()) - .put('/me/history/' + noteName) + const response = await request(testSetup.app.getHttpServer()) + .put('/api/v2/me/history/' + noteName) .send(historyEntryUpdateDto) .expect(200); - const history = await historyService.getEntriesByUser(user); + const history = await testSetup.historyService.getEntriesByUser(user); const historyEntry: HistoryEntryDto = response.body; expect(historyEntry.pinStatus).toEqual(true); let theEntry: HistoryEntryDto; for (const entry of history) { if (entry.note.aliases.find((element) => element.name === noteName)) { - theEntry = historyService.toHistoryEntryDto(entry); + theEntry = testSetup.historyService.toHistoryEntryDto(entry); } } expect(theEntry.pinStatus).toEqual(true); }); it('fails with a non-existing note', async () => { - await request(app.getHttpServer()) - .put('/me/history/i_dont_exist') + await request(testSetup.app.getHttpServer()) + .put('/api/v2/me/history/i_dont_exist') .expect('Content-Type', /json/) .expect(404); }); @@ -177,13 +126,13 @@ describe('Me', () => { describe(`DELETE /me/history/{note}`, () => { it('works', async () => { const noteName = 'testGetNoteHistory4'; - const note = await notesService.createNote('', noteName); - await historyService.updateHistoryEntryTimestamp(note, user); - const response = await request(app.getHttpServer()) - .delete(`/me/history/${noteName}`) + const note = await testSetup.notesService.createNote('', noteName); + await testSetup.historyService.updateHistoryEntryTimestamp(note, user); + const response = await request(testSetup.app.getHttpServer()) + .delete(`/api/v2/me/history/${noteName}`) .expect(204); expect(response.body).toEqual({}); - const history = await historyService.getEntriesByUser(user); + const history = await testSetup.historyService.getEntriesByUser(user); for (const entry of history) { if (entry.note.aliases.find((element) => element.name === noteName)) { throw new Error('Deleted history entry still in history'); @@ -192,15 +141,15 @@ describe('Me', () => { }); describe('fails', () => { it('with a non-existing note', async () => { - await request(app.getHttpServer()) - .delete('/me/history/i_dont_exist') + await request(testSetup.app.getHttpServer()) + .delete('/api/v2/me/history/i_dont_exist') .expect(404); }); it('with a non-existing history entry', async () => { const noteName = 'testGetNoteHistory5'; - await notesService.createNote('', noteName); - await request(app.getHttpServer()) - .delete(`/me/history/${noteName}`) + await testSetup.notesService.createNote('', noteName); + await request(testSetup.app.getHttpServer()) + .delete(`/api/v2/me/history/${noteName}`) .expect(404); }); }); @@ -208,9 +157,9 @@ describe('Me', () => { it(`GET /me/notes/`, async () => { const noteName = 'testNote'; - await notesService.createNote('', noteName, user); - const response = await request(app.getHttpServer()) - .get('/me/notes/') + await testSetup.notesService.createNote('', noteName, user); + const response = await request(testSetup.app.getHttpServer()) + .get('/api/v2/me/notes/') .expect('Content-Type', /json/) .expect(200); const noteMetaDtos = response.body as NoteMetadataDto[]; @@ -220,31 +169,31 @@ describe('Me', () => { }); it('GET /me/media', async () => { - const note1 = await notesService.createNote( + const note1 = await testSetup.notesService.createNote( 'This is a test note.', 'test8', - await userService.getUserByUsername('hardcoded'), + await testSetup.userService.getUserByUsername('hardcoded'), ); - const note2 = await notesService.createNote( + const note2 = await testSetup.notesService.createNote( 'This is a test note.', 'test9', - await userService.getUserByUsername('hardcoded'), + await testSetup.userService.getUserByUsername('hardcoded'), ); - const httpServer = app.getHttpServer(); + const httpServer = testSetup.app.getHttpServer(); const response1 = await request(httpServer) - .get('/me/media/') + .get('/api/v2/me/media/') .expect('Content-Type', /json/) .expect(200); expect(response1.body).toHaveLength(0); const testImage = await fs.readFile('test/public-api/fixtures/test.png'); - const url0 = await mediaService.saveFile(testImage, user, note1); - const url1 = await mediaService.saveFile(testImage, user, note1); - const url2 = await mediaService.saveFile(testImage, user, note2); - const url3 = await mediaService.saveFile(testImage, user, note2); + const url0 = await testSetup.mediaService.saveFile(testImage, user, note1); + const url1 = await testSetup.mediaService.saveFile(testImage, user, note1); + const url2 = await testSetup.mediaService.saveFile(testImage, user, note2); + const url3 = await testSetup.mediaService.saveFile(testImage, user, note2); const response = await request(httpServer) - .get('/me/media/') + .get('/api/v2/me/media/') .expect('Content-Type', /json/) .expect(200); expect(response.body).toHaveLength(4); @@ -261,6 +210,6 @@ describe('Me', () => { }); afterAll(async () => { - await app.close(); + await testSetup.app.close(); }); }); diff --git a/test/public-api/media.e2e-spec.ts b/test/public-api/media.e2e-spec.ts index 464255543..c2ad6e4de 100644 --- a/test/public-api/media.e2e-spec.ts +++ b/test/public-api/media.e2e-spec.ts @@ -3,97 +3,58 @@ * * SPDX-License-Identifier: AGPL-3.0-only */ -import { ConfigModule, ConfigService } from '@nestjs/config'; -import { NestExpressApplication } from '@nestjs/platform-express'; -import { Test } from '@nestjs/testing'; -import { TypeOrmModule } from '@nestjs/typeorm'; import { promises as fs } from 'fs'; import { join } from 'path'; import request from 'supertest'; -import { PublicApiModule } from '../../src/api/public/public-api.module'; -import { AuthModule } from '../../src/auth/auth.module'; -import { MockAuthGuard } from '../../src/auth/mock-auth.guard'; -import { TokenAuthGuard } from '../../src/auth/token.strategy'; -import appConfigMock from '../../src/config/mock/app.config.mock'; -import mediaConfigMock from '../../src/config/mock/media.config.mock'; -import { GroupsModule } from '../../src/groups/groups.module'; import { ConsoleLoggerService } from '../../src/logger/console-logger.service'; -import { LoggerModule } from '../../src/logger/logger.module'; -import { MediaModule } from '../../src/media/media.module'; -import { MediaService } from '../../src/media/media.service'; import { Note } from '../../src/notes/note.entity'; -import { NotesModule } from '../../src/notes/notes.module'; -import { NotesService } from '../../src/notes/notes.service'; -import { PermissionsModule } from '../../src/permissions/permissions.module'; import { User } from '../../src/users/user.entity'; -import { UsersService } from '../../src/users/users.service'; +import { TestSetup } from '../test-setup'; import { ensureDeleted } from '../utils'; describe('Media', () => { - let app: NestExpressApplication; - let mediaService: MediaService; + let testSetup: TestSetup; let uploadPath: string; let testNote: Note; let user: User; beforeAll(async () => { - const moduleRef = await Test.createTestingModule({ - imports: [ - ConfigModule.forRoot({ - isGlobal: true, - load: [mediaConfigMock, appConfigMock], - }), - PublicApiModule, - MediaModule, - TypeOrmModule.forRoot({ - type: 'sqlite', - database: './hedgedoc-e2e-media.sqlite', - autoLoadEntities: true, - dropSchema: true, - synchronize: true, - }), - NotesModule, - PermissionsModule, - GroupsModule, - LoggerModule, - AuthModule, - ], - }) - .overrideGuard(TokenAuthGuard) - .useClass(MockAuthGuard) - .compile(); - const config = moduleRef.get(ConfigService); - uploadPath = config.get('mediaConfig').backend.filesystem.uploadPath; - app = moduleRef.createNestApplication(); - app.useStaticAssets(uploadPath, { + testSetup = await TestSetup.create(); + + uploadPath = + testSetup.configService.get('mediaConfig').backend.filesystem.uploadPath; + + testSetup.app.useStaticAssets(uploadPath, { prefix: '/uploads', }); - await app.init(); - const logger = await app.resolve(ConsoleLoggerService); + + await testSetup.app.init(); + + const logger = await testSetup.app.resolve(ConsoleLoggerService); logger.log('Switching logger', 'AppBootstrap'); - app.useLogger(logger); - const notesService: NotesService = moduleRef.get(NotesService); - const userService = moduleRef.get(UsersService); - user = await userService.createUser('hardcoded', 'Testy'); - testNote = await notesService.createNote( + testSetup.app.useLogger(logger); + + user = await testSetup.userService.createUser('hardcoded', 'Testy'); + testNote = await testSetup.notesService.createNote( 'test content', 'test_upload_media', ); - mediaService = moduleRef.get(MediaService); }); describe('POST /media', () => { it('works', async () => { - const uploadResponse = await request(app.getHttpServer()) - .post('/media') + const uploadResponse = await request(testSetup.app.getHttpServer()) + .post('/api/v2/media') .attach('file', 'test/public-api/fixtures/test.png') .set('HedgeDoc-Note', 'test_upload_media') .expect('Content-Type', /json/) .expect(201); const path: string = uploadResponse.body.link; const testImage = await fs.readFile('test/public-api/fixtures/test.png'); - const downloadResponse = await request(app.getHttpServer()).get(path); + const downloadResponse = await request(testSetup.app.getHttpServer()).get( + path, + ); expect(downloadResponse.body).toEqual(testImage); // Remove /uploads/ from path as we just need the filename. const fileName = path.replace('/uploads/', ''); @@ -105,16 +66,16 @@ describe('Media', () => { await ensureDeleted(uploadPath); }); it('MIME type not supported', async () => { - await request(app.getHttpServer()) - .post('/media') + await request(testSetup.app.getHttpServer()) + .post('/api/v2/media') .attach('file', 'test/public-api/fixtures/test.zip') .set('HedgeDoc-Note', 'test_upload_media') .expect(400); await expect(fs.access(uploadPath)).rejects.toBeDefined(); }); it('note does not exist', async () => { - await request(app.getHttpServer()) - .post('/media') + await request(testSetup.app.getHttpServer()) + .post('/api/v2/media') .attach('file', 'test/public-api/fixtures/test.zip') .set('HedgeDoc-Note', 'i_dont_exist') .expect(400); @@ -124,8 +85,8 @@ describe('Media', () => { await fs.mkdir(uploadPath, { mode: '444', }); - await request(app.getHttpServer()) - .post('/media') + await request(testSetup.app.getHttpServer()) + .post('/api/v2/media') .attach('file', 'test/public-api/fixtures/test.png') .set('HedgeDoc-Note', 'test_upload_media') .expect('Content-Type', /json/) @@ -139,16 +100,20 @@ describe('Media', () => { it('DELETE /media/{filename}', async () => { const testImage = await fs.readFile('test/public-api/fixtures/test.png'); - const url = await mediaService.saveFile(testImage, user, testNote); + const url = await testSetup.mediaService.saveFile( + testImage, + user, + testNote, + ); const filename = url.split('/').pop() || ''; - await request(app.getHttpServer()) - .delete('/media/' + filename) + await request(testSetup.app.getHttpServer()) + .delete('/api/v2/media/' + filename) .expect(204); }); afterAll(async () => { // Delete the upload folder await ensureDeleted(uploadPath); - await app.close(); + await testSetup.app.close(); }); }); diff --git a/test/public-api/notes.e2e-spec.ts b/test/public-api/notes.e2e-spec.ts index de21d4c26..487c8a58e 100644 --- a/test/public-api/notes.e2e-spec.ts +++ b/test/public-api/notes.e2e-spec.ts @@ -3,36 +3,18 @@ * * SPDX-License-Identifier: AGPL-3.0-only */ -import { INestApplication } from '@nestjs/common'; -import { ConfigModule, ConfigService } from '@nestjs/config'; -import { Test } from '@nestjs/testing'; -import { TypeOrmModule } from '@nestjs/typeorm'; import { promises as fs } from 'fs'; import { join } from 'path'; import request from 'supertest'; -import { PublicApiModule } from '../../src/api/public/public-api.module'; -import { AuthModule } from '../../src/auth/auth.module'; -import { MockAuthGuard } from '../../src/auth/mock-auth.guard'; -import { TokenAuthGuard } from '../../src/auth/token.strategy'; -import appConfigMock from '../../src/config/mock/app.config.mock'; -import mediaConfigMock from '../../src/config/mock/media.config.mock'; import { NotInDBError } from '../../src/errors/errors'; -import { GroupsModule } from '../../src/groups/groups.module'; -import { LoggerModule } from '../../src/logger/logger.module'; -import { MediaService } from '../../src/media/media.service'; import { NotePermissionsUpdateDto } from '../../src/notes/note-permissions.dto'; -import { NotesModule } from '../../src/notes/notes.module'; -import { NotesService } from '../../src/notes/notes.service'; -import { PermissionsModule } from '../../src/permissions/permissions.module'; import { User } from '../../src/users/user.entity'; -import { UsersModule } from '../../src/users/users.module'; -import { UsersService } from '../../src/users/users.service'; +import { TestSetup } from '../test-setup'; describe('Notes', () => { - let app: INestApplication; - let notesService: NotesService; - let mediaService: MediaService; + let testSetup: TestSetup; + let user: User; let user2: User; let content: string; @@ -41,57 +23,37 @@ describe('Notes', () => { let testImage: Buffer; beforeAll(async () => { - const moduleRef = await Test.createTestingModule({ - imports: [ - ConfigModule.forRoot({ - isGlobal: true, - load: [mediaConfigMock, appConfigMock], - }), - PublicApiModule, - NotesModule, - PermissionsModule, - GroupsModule, - TypeOrmModule.forRoot({ - type: 'sqlite', - database: './hedgedoc-e2e-notes.sqlite', - autoLoadEntities: true, - synchronize: true, - dropSchema: true, - }), - LoggerModule, - AuthModule, - UsersModule, - ], - }) - .overrideGuard(TokenAuthGuard) - .useClass(MockAuthGuard) - .compile(); + testSetup = await TestSetup.create(); - const config = moduleRef.get(ConfigService); - forbiddenNoteId = config.get('appConfig').forbiddenNoteIds[0]; - uploadPath = config.get('mediaConfig').backend.filesystem.uploadPath; - app = moduleRef.createNestApplication(); - await app.init(); - notesService = moduleRef.get(NotesService); - mediaService = moduleRef.get(MediaService); - const userService = moduleRef.get(UsersService); - user = await userService.createUser('hardcoded', 'Testy'); - user2 = await userService.createUser('hardcoded2', 'Max Mustermann'); + forbiddenNoteId = + testSetup.configService.get('appConfig').forbiddenNoteIds[0]; + uploadPath = + testSetup.configService.get('mediaConfig').backend.filesystem.uploadPath; + + await testSetup.app.init(); + + user = await testSetup.userService.createUser('hardcoded', 'Testy'); + user2 = await testSetup.userService.createUser( + 'hardcoded2', + 'Max Mustermann', + ); content = 'This is a test note.'; testImage = await fs.readFile('test/public-api/fixtures/test.png'); }); it('POST /notes', async () => { - const response = await request(app.getHttpServer()) - .post('/notes') + const response = await request(testSetup.app.getHttpServer()) + .post('/api/v2/notes') .set('Content-Type', 'text/markdown') .send(content) .expect('Content-Type', /json/) .expect(201); expect(response.body.metadata?.id).toBeDefined(); expect( - await notesService.getNoteContent( - await notesService.getNoteByIdOrAlias(response.body.metadata.id), + await testSetup.notesService.getNoteContent( + await testSetup.notesService.getNoteByIdOrAlias( + response.body.metadata.id, + ), ), ).toEqual(content); }); @@ -99,24 +61,24 @@ describe('Notes', () => { describe('GET /notes/{note}', () => { it('works with an existing note', async () => { // check if we can succefully get a note that exists - await notesService.createNote(content, 'test1', user); - const response = await request(app.getHttpServer()) - .get('/notes/test1') + await testSetup.notesService.createNote(content, 'test1', user); + const response = await request(testSetup.app.getHttpServer()) + .get('/api/v2/notes/test1') .expect('Content-Type', /json/) .expect(200); expect(response.body.content).toEqual(content); }); it('fails with an non-existing note', async () => { // check if a missing note correctly returns 404 - await request(app.getHttpServer()) - .get('/notes/i_dont_exist') + await request(testSetup.app.getHttpServer()) + .get('/api/v2/notes/i_dont_exist') .expect('Content-Type', /json/) .expect(404); }); it('fails with a forbidden note id', async () => { // check if a forbidden note correctly returns 400 - await request(app.getHttpServer()) - .get('/notes/forbiddenNoteId') + await request(testSetup.app.getHttpServer()) + .get('/api/v2/notes/forbiddenNoteId') .expect('Content-Type', /json/) .expect(400); }); @@ -124,23 +86,25 @@ describe('Notes', () => { describe('POST /notes/{note}', () => { it('works with a non-existing alias', async () => { - const response = await request(app.getHttpServer()) - .post('/notes/test2') + const response = await request(testSetup.app.getHttpServer()) + .post('/api/v2/notes/test2') .set('Content-Type', 'text/markdown') .send(content) .expect('Content-Type', /json/) .expect(201); expect(response.body.metadata?.id).toBeDefined(); return expect( - await notesService.getNoteContent( - await notesService.getNoteByIdOrAlias(response.body.metadata?.id), + await testSetup.notesService.getNoteContent( + await testSetup.notesService.getNoteByIdOrAlias( + response.body.metadata?.id, + ), ), ).toEqual(content); }); it('fails with a forbidden alias', async () => { - await request(app.getHttpServer()) - .post(`/notes/${forbiddenNoteId}`) + await request(testSetup.app.getHttpServer()) + .post(`/api/v2/notes/${forbiddenNoteId}`) .set('Content-Type', 'text/markdown') .send(content) .expect('Content-Type', /json/) @@ -148,8 +112,8 @@ describe('Notes', () => { }); it('fails with a existing alias', async () => { - await request(app.getHttpServer()) - .post('/notes/test2') + await request(testSetup.app.getHttpServer()) + .post('/api/v2/notes/test2') .set('Content-Type', 'text/markdown') .send(content) .expect('Content-Type', /json/) @@ -161,35 +125,55 @@ describe('Notes', () => { describe('works', () => { it('with an existing alias and keepMedia false', async () => { const noteId = 'test3'; - const note = await notesService.createNote(content, noteId, user); - await mediaService.saveFile(testImage, user, note); - await request(app.getHttpServer()) - .delete(`/notes/${noteId}`) + const note = await testSetup.notesService.createNote( + content, + noteId, + user, + ); + await testSetup.mediaService.saveFile(testImage, user, note); + await request(testSetup.app.getHttpServer()) + .delete(`/api/v2/notes/${noteId}`) .set('Content-Type', 'application/json') .send({ keepMedia: false, }) .expect(204); - await expect(notesService.getNoteByIdOrAlias(noteId)).rejects.toEqual( + await expect( + testSetup.notesService.getNoteByIdOrAlias(noteId), + ).rejects.toEqual( new NotInDBError(`Note with id/alias '${noteId}' not found.`), ); - expect(await mediaService.listUploadsByUser(user)).toHaveLength(0); + expect( + await testSetup.mediaService.listUploadsByUser(user), + ).toHaveLength(0); }); it('with an existing alias and keepMedia true', async () => { const noteId = 'test3a'; - const note = await notesService.createNote(content, noteId, user); - const url = await mediaService.saveFile(testImage, user, note); - await request(app.getHttpServer()) - .delete(`/notes/${noteId}`) + const note = await testSetup.notesService.createNote( + content, + noteId, + user, + ); + const url = await testSetup.mediaService.saveFile( + testImage, + user, + note, + ); + await request(testSetup.app.getHttpServer()) + .delete(`/api/v2/notes/${noteId}`) .set('Content-Type', 'application/json') .send({ keepMedia: true, }) .expect(204); - await expect(notesService.getNoteByIdOrAlias(noteId)).rejects.toEqual( + await expect( + testSetup.notesService.getNoteByIdOrAlias(noteId), + ).rejects.toEqual( new NotInDBError(`Note with id/alias '${noteId}' not found.`), ); - expect(await mediaService.listUploadsByUser(user)).toHaveLength(1); + expect( + await testSetup.mediaService.listUploadsByUser(user), + ).toHaveLength(1); // Remove /upload/ from path as we just need the filename. const fileName = url.replace('/uploads/', ''); // delete the file afterwards @@ -197,7 +181,11 @@ describe('Notes', () => { }); }); it('works with an existing alias with permissions', async () => { - const note = await notesService.createNote(content, 'test3', user); + const note = await testSetup.notesService.createNote( + content, + 'test3', + user, + ); const updateNotePermission = new NotePermissionsUpdateDto(); updateNotePermission.sharedToUsers = [ { @@ -206,8 +194,11 @@ describe('Notes', () => { }, ]; updateNotePermission.sharedToGroups = []; - await notesService.updateNotePermissions(note, updateNotePermission); - const updatedNote = await notesService.getNoteByIdOrAlias( + await testSetup.notesService.updateNotePermissions( + note, + updateNotePermission, + ); + const updatedNote = await testSetup.notesService.getNoteByIdOrAlias( note.aliases.filter((alias) => alias.primary)[0].name, ); expect(updatedNote.userPermissions).toHaveLength(1); @@ -218,19 +209,23 @@ describe('Notes', () => { user.username, ); expect(updatedNote.groupPermissions).toHaveLength(0); - await request(app.getHttpServer()).delete('/notes/test3').expect(204); - await expect(notesService.getNoteByIdOrAlias('test3')).rejects.toEqual( + await request(testSetup.app.getHttpServer()) + .delete('/api/v2/notes/test3') + .expect(204); + await expect( + testSetup.notesService.getNoteByIdOrAlias('test3'), + ).rejects.toEqual( new NotInDBError("Note with id/alias 'test3' not found."), ); }); it('fails with a forbidden alias', async () => { - await request(app.getHttpServer()) - .delete(`/notes/${forbiddenNoteId}`) + await request(testSetup.app.getHttpServer()) + .delete(`/api/v2/notes/${forbiddenNoteId}`) .expect(400); }); it('fails with a non-existing alias', async () => { - await request(app.getHttpServer()) - .delete('/notes/i_dont_exist') + await request(testSetup.app.getHttpServer()) + .delete('/api/v2/notes/i_dont_exist') .expect(404); }); }); @@ -238,29 +233,29 @@ describe('Notes', () => { describe('PUT /notes/{note}', () => { const changedContent = 'New note text'; it('works with existing alias', async () => { - await notesService.createNote(content, 'test4', user); - const response = await request(app.getHttpServer()) - .put('/notes/test4') + await testSetup.notesService.createNote(content, 'test4', user); + const response = await request(testSetup.app.getHttpServer()) + .put('/api/v2/notes/test4') .set('Content-Type', 'text/markdown') .send(changedContent) .expect(200); expect( - await notesService.getNoteContent( - await notesService.getNoteByIdOrAlias('test4'), + await testSetup.notesService.getNoteContent( + await testSetup.notesService.getNoteByIdOrAlias('test4'), ), ).toEqual(changedContent); expect(response.body.content).toEqual(changedContent); }); it('fails with a forbidden alias', async () => { - await request(app.getHttpServer()) - .put(`/notes/${forbiddenNoteId}`) + await request(testSetup.app.getHttpServer()) + .put(`/api/v2/notes/${forbiddenNoteId}`) .set('Content-Type', 'text/markdown') .send(changedContent) .expect(400); }); it('fails with a non-existing alias', async () => { - await request(app.getHttpServer()) - .put('/notes/i_dont_exist') + await request(testSetup.app.getHttpServer()) + .put('/api/v2/notes/i_dont_exist') .set('Content-Type', 'text/markdown') .expect('Content-Type', /json/) .expect(404); @@ -269,9 +264,9 @@ describe('Notes', () => { describe('GET /notes/{note}/metadata', () => { it('returns complete metadata object', async () => { - await notesService.createNote(content, 'test5', user); - const metadata = await request(app.getHttpServer()) - .get('/notes/test5/metadata') + await testSetup.notesService.createNote(content, 'test5', user); + const metadata = await request(testSetup.app.getHttpServer()) + .get('/api/v2/notes/test5/metadata') .expect(200); expect(typeof metadata.body.id).toEqual('string'); expect(metadata.body.aliases).toEqual(['test5']); @@ -294,30 +289,34 @@ describe('Notes', () => { }); it('fails with a forbidden alias', async () => { - await request(app.getHttpServer()) - .get(`/notes/${forbiddenNoteId}/metadata`) + await request(testSetup.app.getHttpServer()) + .get(`/api/v2/notes/${forbiddenNoteId}/metadata`) .expect(400); }); it('fails with non-existing alias', async () => { // check if a missing note correctly returns 404 - await request(app.getHttpServer()) - .get('/notes/i_dont_exist/metadata') + await request(testSetup.app.getHttpServer()) + .get('/api/v2/notes/i_dont_exist/metadata') .expect('Content-Type', /json/) .expect(404); }); it('has the correct update/create dates', async () => { // create a note - const note = await notesService.createNote(content, 'test5a', user); + const note = await testSetup.notesService.createNote( + content, + 'test5a', + user, + ); // save the creation time const createDate = (await note.revisions)[0].createdAt; // wait one second await new Promise((r) => setTimeout(r, 1000)); // update the note - await notesService.updateNote(note, 'More test content'); - const metadata = await request(app.getHttpServer()) - .get('/notes/test5a/metadata') + await testSetup.notesService.updateNote(note, 'More test content'); + const metadata = await request(testSetup.app.getHttpServer()) + .get('/api/v2/notes/test5a/metadata') .expect(200); expect(metadata.body.createTime).toEqual(createDate.toISOString()); expect(metadata.body.updateTime).not.toEqual(createDate.toISOString()); @@ -326,24 +325,24 @@ describe('Notes', () => { describe('GET /notes/{note}/revisions', () => { it('works with existing alias', async () => { - await notesService.createNote(content, 'test6', user); - const response = await request(app.getHttpServer()) - .get('/notes/test6/revisions') + await testSetup.notesService.createNote(content, 'test6', user); + const response = await request(testSetup.app.getHttpServer()) + .get('/api/v2/notes/test6/revisions') .expect('Content-Type', /json/) .expect(200); expect(response.body).toHaveLength(1); }); it('fails with a forbidden alias', async () => { - await request(app.getHttpServer()) - .get(`/notes/${forbiddenNoteId}/revisions`) + await request(testSetup.app.getHttpServer()) + .get(`/api/v2/notes/${forbiddenNoteId}/revisions`) .expect(400); }); it('fails with non-existing alias', async () => { // check if a missing note correctly returns 404 - await request(app.getHttpServer()) - .get('/notes/i_dont_exist/revisions') + await request(testSetup.app.getHttpServer()) + .get('/api/v2/notes/i_dont_exist/revisions') .expect('Content-Type', /json/) .expect(404); }); @@ -351,23 +350,27 @@ describe('Notes', () => { describe('GET /notes/{note}/revisions/{revision-id}', () => { it('works with an existing alias', async () => { - const note = await notesService.createNote(content, 'test7', user); - const revision = await notesService.getLatestRevision(note); - const response = await request(app.getHttpServer()) - .get(`/notes/test7/revisions/${revision.id}`) + const note = await testSetup.notesService.createNote( + content, + 'test7', + user, + ); + const revision = await testSetup.notesService.getLatestRevision(note); + const response = await request(testSetup.app.getHttpServer()) + .get(`/api/v2/notes/test7/revisions/${revision.id}`) .expect('Content-Type', /json/) .expect(200); expect(response.body.content).toEqual(content); }); it('fails with a forbidden alias', async () => { - await request(app.getHttpServer()) - .get(`/notes/${forbiddenNoteId}/revisions/1`) + await request(testSetup.app.getHttpServer()) + .get(`/api/v2/notes/${forbiddenNoteId}/revisions/1`) .expect(400); }); it('fails with non-existing alias', async () => { // check if a missing note correctly returns 404 - await request(app.getHttpServer()) - .get('/notes/i_dont_exist/revisions/1') + await request(testSetup.app.getHttpServer()) + .get('/api/v2/notes/i_dont_exist/revisions/1') .expect('Content-Type', /json/) .expect(404); }); @@ -375,21 +378,21 @@ describe('Notes', () => { describe('GET /notes/{note}/content', () => { it('works with an existing alias', async () => { - await notesService.createNote(content, 'test8', user); - const response = await request(app.getHttpServer()) - .get('/notes/test8/content') + await testSetup.notesService.createNote(content, 'test8', user); + const response = await request(testSetup.app.getHttpServer()) + .get('/api/v2/notes/test8/content') .expect(200); expect(response.text).toEqual(content); }); it('fails with a forbidden alias', async () => { - await request(app.getHttpServer()) - .get(`/notes/${forbiddenNoteId}/content`) + await request(testSetup.app.getHttpServer()) + .get(`/api/v2/notes/${forbiddenNoteId}/content`) .expect(400); }); it('fails with non-existing alias', async () => { // check if a missing note correctly returns 404 - await request(app.getHttpServer()) - .get('/notes/i_dont_exist/content') + await request(testSetup.app.getHttpServer()) + .get('/api/v2/notes/i_dont_exist/content') .expect('Content-Type', /text\/markdown/) .expect(404); }); @@ -399,21 +402,37 @@ describe('Notes', () => { it('works', async () => { const alias = 'test9'; const extraAlias = 'test10'; - const note1 = await notesService.createNote(content, alias, user); - const note2 = await notesService.createNote(content, extraAlias, user); - const httpServer = app.getHttpServer(); + const note1 = await testSetup.notesService.createNote( + content, + alias, + user, + ); + const note2 = await testSetup.notesService.createNote( + content, + extraAlias, + user, + ); + const httpServer = testSetup.app.getHttpServer(); const response = await request(httpServer) - .get(`/notes/${alias}/media/`) + .get(`/api/v2/notes/${alias}/media/`) .expect('Content-Type', /json/) .expect(200); expect(response.body).toHaveLength(0); const testImage = await fs.readFile('test/public-api/fixtures/test.png'); - const url0 = await mediaService.saveFile(testImage, user, note1); - const url1 = await mediaService.saveFile(testImage, user, note2); + const url0 = await testSetup.mediaService.saveFile( + testImage, + user, + note1, + ); + const url1 = await testSetup.mediaService.saveFile( + testImage, + user, + note2, + ); const responseAfter = await request(httpServer) - .get(`/notes/${alias}/media/`) + .get(`/api/v2/notes/${alias}/media/`) .expect('Content-Type', /json/) .expect(200); expect(responseAfter.body).toHaveLength(1); @@ -427,22 +446,26 @@ describe('Notes', () => { await fs.rmdir(uploadPath, { recursive: true }); }); it('fails, when note does not exist', async () => { - await request(app.getHttpServer()) - .get(`/notes/i_dont_exist/media/`) + await request(testSetup.app.getHttpServer()) + .get(`/api/v2/notes/i_dont_exist/media/`) .expect('Content-Type', /json/) .expect(404); }); it("fails, when user can't read note", async () => { const alias = 'test11'; - await notesService.createNote('This is a test note.', alias, user2); - await request(app.getHttpServer()) - .get(`/notes/${alias}/media/`) + await testSetup.notesService.createNote( + 'This is a test note.', + alias, + user2, + ); + await request(testSetup.app.getHttpServer()) + .get(`/api/v2/notes/${alias}/media/`) .expect('Content-Type', /json/) .expect(401); }); }); afterAll(async () => { - await app.close(); + await testSetup.app.close(); }); }); diff --git a/test/public-api/tokens.e2e-spec.ts b/test/public-api/tokens.e2e-spec.ts deleted file mode 100644 index 6ffb3e9e5..000000000 --- a/test/public-api/tokens.e2e-spec.ts +++ /dev/null @@ -1,130 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file) - * - * SPDX-License-Identifier: AGPL-3.0-only - */ -import { INestApplication } from '@nestjs/common'; -import { ConfigModule, ConfigService } from '@nestjs/config'; -import { Test } from '@nestjs/testing'; -import { TypeOrmModule } from '@nestjs/typeorm'; -import request from 'supertest'; - -import { PrivateApiModule } from '../../src/api/private/private-api.module'; -import { AuthModule } from '../../src/auth/auth.module'; -import { MockAuthGuard } from '../../src/auth/mock-auth.guard'; -import { TokenAuthGuard } from '../../src/auth/token.strategy'; -import { AuthConfig } from '../../src/config/auth.config'; -import appConfigMock from '../../src/config/mock/app.config.mock'; -import authConfigMock from '../../src/config/mock/auth.config.mock'; -import customizationConfigMock from '../../src/config/mock/customization.config.mock'; -import externalServicesConfigMock from '../../src/config/mock/external-services.config.mock'; -import mediaConfigMock from '../../src/config/mock/media.config.mock'; -import { GroupsModule } from '../../src/groups/groups.module'; -import { HistoryModule } from '../../src/history/history.module'; -import { IdentityService } from '../../src/identity/identity.service'; -import { LoggerModule } from '../../src/logger/logger.module'; -import { MediaModule } from '../../src/media/media.module'; -import { NotesModule } from '../../src/notes/notes.module'; -import { PermissionsModule } from '../../src/permissions/permissions.module'; -import { User } from '../../src/users/user.entity'; -import { UsersModule } from '../../src/users/users.module'; -import { UsersService } from '../../src/users/users.service'; -import { setupSessionMiddleware } from '../../src/utils/session'; - -describe('Tokens', () => { - let app: INestApplication; - let userService: UsersService; - let identityService: IdentityService; - let user: User; - let agent: request.SuperAgentTest; - let keyId: string; - - beforeAll(async () => { - const moduleRef = await Test.createTestingModule({ - imports: [ - ConfigModule.forRoot({ - isGlobal: true, - load: [ - appConfigMock, - authConfigMock, - mediaConfigMock, - customizationConfigMock, - externalServicesConfigMock, - ], - }), - PrivateApiModule, - NotesModule, - PermissionsModule, - GroupsModule, - TypeOrmModule.forRoot({ - type: 'sqlite', - database: './hedgedoc-e2e-private-me.sqlite', - autoLoadEntities: true, - synchronize: true, - dropSchema: true, - }), - LoggerModule, - AuthModule, - UsersModule, - MediaModule, - HistoryModule, - ], - }) - .overrideGuard(TokenAuthGuard) - .useClass(MockAuthGuard) - .compile(); - const config = moduleRef.get(ConfigService); - identityService = moduleRef.get(IdentityService); - app = moduleRef.createNestApplication(); - userService = moduleRef.get(UsersService); - user = await userService.createUser('hardcoded', 'Testy'); - await identityService.createLocalIdentity(user, 'test'); - const authConfig = config.get('authConfig') as AuthConfig; - setupSessionMiddleware(app, authConfig); - await app.init(); - agent = request.agent(app.getHttpServer()); - await agent - .post('/auth/local/login') - .send({ username: 'hardcoded', password: 'test' }) - .expect(201); - }); - - it(`POST /tokens`, async () => { - const tokenName = 'testToken'; - const response = await agent - .post('/tokens') - .send({ - label: tokenName, - }) - .expect('Content-Type', /json/) - .expect(201); - keyId = response.body.keyId; - expect(response.body.label).toBe(tokenName); - expect(response.body.validUntil).toBe(null); - expect(response.body.lastUsed).toBe(null); - expect(response.body.secret.length).toBe(84); - }); - - it(`GET /tokens`, async () => { - const tokenName = 'testToken'; - const response = await agent - .get('/tokens/') - .expect('Content-Type', /json/) - .expect(200); - expect(response.body[0].label).toBe(tokenName); - expect(response.body[0].validUntil).toBe(null); - expect(response.body[0].lastUsed).toBe(null); - expect(response.body[0].secret).not.toBeDefined(); - }); - it(`DELETE /tokens/:keyid`, async () => { - const response = await agent.delete('/tokens/' + keyId).expect(204); - expect(response.body).toStrictEqual({}); - }); - it(`GET /tokens 2`, async () => { - const response = await agent - .get('/tokens/') - .expect('Content-Type', /json/) - .expect(200); - expect(response.body).toStrictEqual([]); - }); -}); diff --git a/test/test-setup.ts b/test/test-setup.ts new file mode 100644 index 000000000..d283c9c45 --- /dev/null +++ b/test/test-setup.ts @@ -0,0 +1,115 @@ +/* + * SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ +import { ConfigModule, ConfigService } from '@nestjs/config'; +import { NestExpressApplication } from '@nestjs/platform-express'; +import { Test, TestingModule } from '@nestjs/testing'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { RouterModule, Routes } from 'nest-router'; + +import { PrivateApiModule } from '../src/api/private/private-api.module'; +import { PublicApiModule } from '../src/api/public/public-api.module'; +import { AuthModule } from '../src/auth/auth.module'; +import { MockAuthGuard } from '../src/auth/mock-auth.guard'; +import { TokenAuthGuard } from '../src/auth/token.strategy'; +import appConfigMock from '../src/config/mock/app.config.mock'; +import authConfigMock from '../src/config/mock/auth.config.mock'; +import customizationConfigMock from '../src/config/mock/customization.config.mock'; +import externalServicesConfigMock from '../src/config/mock/external-services.config.mock'; +import mediaConfigMock from '../src/config/mock/media.config.mock'; +import { GroupsModule } from '../src/groups/groups.module'; +import { HistoryModule } from '../src/history/history.module'; +import { HistoryService } from '../src/history/history.service'; +import { IdentityService } from '../src/identity/identity.service'; +import { LoggerModule } from '../src/logger/logger.module'; +import { MediaModule } from '../src/media/media.module'; +import { MediaService } from '../src/media/media.service'; +import { AliasService } from '../src/notes/alias.service'; +import { NotesModule } from '../src/notes/notes.module'; +import { NotesService } from '../src/notes/notes.service'; +import { PermissionsModule } from '../src/permissions/permissions.module'; +import { UsersModule } from '../src/users/users.module'; +import { UsersService } from '../src/users/users.service'; + +export class TestSetup { + moduleRef: TestingModule; + app: NestExpressApplication; + + userService: UsersService; + configService: ConfigService; + identityService: IdentityService; + notesService: NotesService; + mediaService: MediaService; + historyService: HistoryService; + aliasService: AliasService; + + public static async create(): Promise { + const testSetup = new TestSetup(); + const routes: Routes = [ + { + path: '/api/v2', + module: PublicApiModule, + }, + { + path: '/api/private', + module: PrivateApiModule, + }, + ]; + + testSetup.moduleRef = await Test.createTestingModule({ + imports: [ + RouterModule.forRoutes(routes), + TypeOrmModule.forRoot({ + type: 'sqlite', + database: ':memory:', + autoLoadEntities: true, + synchronize: true, + dropSchema: true, + }), + ConfigModule.forRoot({ + isGlobal: true, + load: [ + appConfigMock, + authConfigMock, + mediaConfigMock, + customizationConfigMock, + externalServicesConfigMock, + ], + }), + PublicApiModule, + PrivateApiModule, + NotesModule, + PermissionsModule, + GroupsModule, + LoggerModule, + AuthModule, + UsersModule, + MediaModule, + HistoryModule, + ], + }) + .overrideGuard(TokenAuthGuard) + .useClass(MockAuthGuard) + .compile(); + + testSetup.userService = testSetup.moduleRef.get(UsersService); + testSetup.configService = + testSetup.moduleRef.get(ConfigService); + testSetup.identityService = + testSetup.moduleRef.get(IdentityService); + testSetup.notesService = + testSetup.moduleRef.get(NotesService); + testSetup.mediaService = + testSetup.moduleRef.get(MediaService); + testSetup.historyService = + testSetup.moduleRef.get(HistoryService); + testSetup.aliasService = + testSetup.moduleRef.get(AliasService); + + testSetup.app = testSetup.moduleRef.createNestApplication(); + + return testSetup; + } +} diff --git a/tsconfig.test.json b/test/tsconfig.json similarity index 60% rename from tsconfig.test.json rename to test/tsconfig.json index 5787af45b..fa51fa43e 100644 --- a/tsconfig.test.json +++ b/test/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "./tsconfig.json", + "extends": "../tsconfig.json", "compilerOptions": { "strict": false } diff --git a/tsconfig.test.json.license b/test/tsconfig.json.license similarity index 100% rename from tsconfig.test.json.license rename to test/tsconfig.json.license