diff --git a/apps/server/src/modules/board/service/board-node.service.spec.ts b/apps/server/src/modules/board/service/board-node.service.spec.ts new file mode 100644 index 00000000000..903b7032a6d --- /dev/null +++ b/apps/server/src/modules/board/service/board-node.service.spec.ts @@ -0,0 +1,150 @@ +import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { Test, TestingModule } from '@nestjs/testing'; +import { setupEntities } from '@shared/testing'; +import { Card, ColumnBoard } from '../domain'; +import { BoardNodeRepo } from '../repo'; +import { columnBoardFactory, richTextElementFactory } from '../testing'; +import { BoardNodeService } from './board-node.service'; +import { BoardNodeDeleteHooksService, ContentElementUpdateService } from './internal'; + +describe(BoardNodeService.name, () => { + let module: TestingModule; + let service: BoardNodeService; + + let boardNodeRepo: DeepMocked; + let contentElementUpdateService: DeepMocked; + let boardNodeDeleteHooksService: DeepMocked; + + beforeAll(async () => { + module = await Test.createTestingModule({ + providers: [ + BoardNodeService, + { + provide: BoardNodeRepo, + useValue: createMock(), + }, + { + provide: ContentElementUpdateService, + useValue: createMock(), + }, + { + provide: BoardNodeDeleteHooksService, + useValue: createMock(), + }, + ], + }).compile(); + + service = module.get(BoardNodeService); + boardNodeRepo = module.get(BoardNodeRepo); + contentElementUpdateService = module.get(ContentElementUpdateService); + boardNodeDeleteHooksService = module.get(BoardNodeDeleteHooksService); + + await setupEntities(); + }); + + afterEach(() => { + jest.resetAllMocks(); + }); + + afterAll(async () => { + await module.close(); + }); + + describe('findById', () => { + const setup = () => { + const boardNode = columnBoardFactory.build(); + boardNodeRepo.findById.mockResolvedValueOnce(boardNode); + + return { boardNode }; + }; + + it('should use the repo', async () => { + const { boardNode } = setup(); + + await service.findById(boardNode.id, 2); + + expect(boardNodeRepo.findById).toHaveBeenCalledWith(boardNode.id, 2); + }); + + it('should return the repo result', async () => { + const { boardNode } = setup(); + + const result = await service.findById(boardNode.id, 2); + + expect(result).toBe(boardNode); + }); + }); + + describe('findByClassAndId', () => { + const setup = () => { + const boardNode = columnBoardFactory.build(); + boardNodeRepo.findById.mockResolvedValueOnce(boardNode); + + return { boardNode }; + }; + + it('should use the repo', async () => { + const { boardNode } = setup(); + + await service.findByClassAndId(ColumnBoard, boardNode.id); + + expect(boardNodeRepo.findById).toHaveBeenCalledWith(boardNode.id, undefined); + }); + + it('should return the repo result', async () => { + const { boardNode } = setup(); + + const result = await service.findByClassAndId(ColumnBoard, boardNode.id); + + expect(result).toBe(boardNode); + }); + + describe('when class doesnt match', () => { + it('should throw error', async () => { + const { boardNode } = setup(); + + await expect(service.findByClassAndId(Card, boardNode.id)).rejects.toThrowError(); + }); + }); + }); + + describe('findContentElementById', () => { + const setup = () => { + const element = richTextElementFactory.build(); + boardNodeRepo.findById.mockResolvedValueOnce(element); + + return { element }; + }; + + it('should use the repo', async () => { + const { element } = setup(); + + await service.findContentElementById(element.id); + + expect(boardNodeRepo.findById).toHaveBeenCalledWith(element.id, undefined); + }); + + it('should return the repo result', async () => { + const { element } = setup(); + + const result = await service.findContentElementById(element.id); + + expect(result).toBe(element); + }); + + describe('when node is not a content element', () => { + const setupNoneElement = () => { + const boardNode = columnBoardFactory.build(); + boardNodeRepo.findById.mockResolvedValueOnce(boardNode); + + return { boardNode }; + }; + + it('should throw error', async () => { + const { boardNode } = setupNoneElement(); + + await expect(service.findContentElementById(boardNode.id)).rejects.toThrowError(); + }); + }); + }); +}); diff --git a/apps/server/src/modules/board/service/board-node.service.ts b/apps/server/src/modules/board/service/board-node.service.ts index 76d17e74ce8..bd8092a2e78 100644 --- a/apps/server/src/modules/board/service/board-node.service.ts +++ b/apps/server/src/modules/board/service/board-node.service.ts @@ -97,10 +97,6 @@ export class BoardNodeService { const boardNodes = await this.boardNodeRepo.findByIds(ids, depth); const filteredNodes = boardNodes.filter((node) => node instanceof Constructor); - // if (filteredNodes.length !== ids.length) { - // throw new NotFoundException(`There is no '${Constructor.name}' with these ids`); - // } - return filteredNodes as T[]; } diff --git a/apps/server/src/modules/collaborative-text-editor/api/tests/get-collaborative-text-editor.api.spec.ts b/apps/server/src/modules/collaborative-text-editor/api/tests/get-collaborative-text-editor.api.spec.ts index b8a7f8be692..999c0217d6e 100644 --- a/apps/server/src/modules/collaborative-text-editor/api/tests/get-collaborative-text-editor.api.spec.ts +++ b/apps/server/src/modules/collaborative-text-editor/api/tests/get-collaborative-text-editor.api.spec.ts @@ -131,7 +131,7 @@ describe('Collaborative Text Editor Controller (API)', () => { const basePath = Configuration.get('ETHERPAD__PAD_URI') as string; const expectedPath = `${basePath}/${editorId}`; - const cookieExpiresMilliseconds = Number(Configuration.get('ETHERPAD_COOKIE_EXPIRES_SECONDS')) * 1000; + const cookieExpiresMilliseconds = Number(Configuration.get('ETHERPAD__COOKIE_EXPIRES_SECONDS')) * 1000; // Remove the last 8 characters from the string to prevent conflict between time of test and code execution const sessionCookieExpiryDate = new Date(Date.now() + cookieExpiresMilliseconds).toUTCString().slice(0, -8); @@ -202,7 +202,7 @@ describe('Collaborative Text Editor Controller (API)', () => { const basePath = Configuration.get('ETHERPAD__PAD_URI') as string; const expectedPath = `${basePath}/${editorId}`; - const cookieExpiresMilliseconds = Number(Configuration.get('ETHERPAD_COOKIE_EXPIRES_SECONDS')) * 1000; + const cookieExpiresMilliseconds = Number(Configuration.get('ETHERPAD__COOKIE_EXPIRES_SECONDS')) * 1000; // Remove the last 8 characters from the string to prevent conflict between time of test and code execution const sessionCookieExpiryDate = new Date(Date.now() + cookieExpiresMilliseconds).toUTCString().slice(0, -8); @@ -273,7 +273,7 @@ describe('Collaborative Text Editor Controller (API)', () => { const basePath = Configuration.get('ETHERPAD__PAD_URI') as string; const expectedPath = `${basePath}/${editorId}`; - const cookieExpiresMilliseconds = Number(Configuration.get('ETHERPAD_COOKIE_EXPIRES_SECONDS')) * 1000; + const cookieExpiresMilliseconds = Number(Configuration.get('ETHERPAD__COOKIE_EXPIRES_SECONDS')) * 1000; // Remove the last 8 characters from the string to prevent conflict between time of test and code execution const sessionCookieExpiryDate = new Date(Date.now() + cookieExpiresMilliseconds).toUTCString().slice(0, -8); diff --git a/apps/server/src/modules/collaborative-text-editor/collaborative-text-editor.config.ts b/apps/server/src/modules/collaborative-text-editor/collaborative-text-editor.config.ts index 718e41cca95..c0806b8ef21 100644 --- a/apps/server/src/modules/collaborative-text-editor/collaborative-text-editor.config.ts +++ b/apps/server/src/modules/collaborative-text-editor/collaborative-text-editor.config.ts @@ -1,5 +1,5 @@ export interface CollaborativeTextEditorConfig { - ETHERPAD_COOKIE_EXPIRES_SECONDS: number; - ETHERPAD_COOKIE_RELEASE_THRESHOLD: number; + ETHERPAD__COOKIE_EXPIRES_SECONDS: number; + ETHERPAD__COOKIE_RELEASE_THRESHOLD: number; ETHERPAD__PAD_URI: string; } diff --git a/apps/server/src/modules/collaborative-text-editor/config.ts b/apps/server/src/modules/collaborative-text-editor/config.ts index 1afc27483e9..c695241e89c 100644 --- a/apps/server/src/modules/collaborative-text-editor/config.ts +++ b/apps/server/src/modules/collaborative-text-editor/config.ts @@ -2,6 +2,6 @@ import { Configuration } from '@hpi-schul-cloud/commons/lib'; import { EtherpadClientConfig } from '@src/infra/etherpad-client'; export const etherpadClientConfig: EtherpadClientConfig = { - apiKey: Configuration.has('ETHERPAD_API_KEY') ? (Configuration.get('ETHERPAD_API_KEY') as string) : undefined, - basePath: Configuration.has('ETHERPAD_URI') ? (Configuration.get('ETHERPAD_URI') as string) : undefined, + apiKey: Configuration.has('ETHERPAD__API_KEY') ? (Configuration.get('ETHERPAD__API_KEY') as string) : undefined, + basePath: Configuration.has('ETHERPAD__URI') ? (Configuration.get('ETHERPAD__URI') as string) : undefined, }; diff --git a/apps/server/src/modules/collaborative-text-editor/service/collaborative-text-editor.service.ts b/apps/server/src/modules/collaborative-text-editor/service/collaborative-text-editor.service.ts index 18b832a76d7..477538a32b1 100644 --- a/apps/server/src/modules/collaborative-text-editor/service/collaborative-text-editor.service.ts +++ b/apps/server/src/modules/collaborative-text-editor/service/collaborative-text-editor.service.ts @@ -22,7 +22,7 @@ export class CollaborativeTextEditorService { params: GetCollaborativeTextEditorForParentParams ): Promise { const sessionExpiryDate = this.buildSessionExpiryDate(); - const durationThreshold = Number(this.configService.get('ETHERPAD_COOKIE_RELEASE_THRESHOLD')); + const durationThreshold = Number(this.configService.get('ETHERPAD__COOKIE_RELEASE_THRESHOLD')); const { parentId } = params; const groupId = await this.collaborativeTextEditorAdapter.getOrCreateGroupId(parentId); @@ -62,7 +62,7 @@ export class CollaborativeTextEditorService { } private buildSessionExpiryDate(): Date { - const cookieExpiresMilliseconds = Number(this.configService.get('ETHERPAD_COOKIE_EXPIRES_SECONDS')) * 1000; + const cookieExpiresMilliseconds = Number(this.configService.get('ETHERPAD__COOKIE_EXPIRES_SECONDS')) * 1000; const sessionCookieExpiryDate = new Date(Date.now() + cookieExpiresMilliseconds); return sessionCookieExpiryDate; diff --git a/apps/server/src/modules/server/admin-api.server.module.ts b/apps/server/src/modules/server/admin-api.server.module.ts index 823402b4b96..9207ec6465f 100644 --- a/apps/server/src/modules/server/admin-api.server.module.ts +++ b/apps/server/src/modules/server/admin-api.server.module.ts @@ -1,18 +1,18 @@ +import { Configuration } from '@hpi-schul-cloud/commons'; import { MikroOrmModule } from '@mikro-orm/nestjs'; -import { DynamicModule, Module } from '@nestjs/common'; +import { DeletionApiModule } from '@modules/deletion/deletion-api.module'; import { FileEntity } from '@modules/files/entity'; +import { LegacySchoolAdminApiModule } from '@modules/legacy-school/legacy-school-admin.api-module'; +import { UserAdminApiModule } from '@modules/user/user-admin-api.module'; +import { DynamicModule, Module } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; +import { CqrsModule } from '@nestjs/cqrs'; import { ALL_ENTITIES } from '@shared/domain/entity'; import { DB_PASSWORD, DB_URL, DB_USERNAME, createConfigModuleOptions } from '@src/config'; import { LoggerModule } from '@src/core/logger'; import { MongoDatabaseModuleOptions, MongoMemoryDatabaseModule } from '@src/infra/database'; -import { RabbitMQWrapperModule, RabbitMQWrapperTestModule } from '@src/infra/rabbitmq'; -import { CqrsModule } from '@nestjs/cqrs'; -import { DeletionApiModule } from '@modules/deletion/deletion-api.module'; -import { LegacySchoolAdminApiModule } from '@modules/legacy-school/legacy-school-admin.api-module'; -import { UserAdminApiModule } from '@modules/user/user-admin-api.module'; import { EtherpadClientModule } from '@src/infra/etherpad-client'; -import { Configuration } from '@hpi-schul-cloud/commons'; +import { RabbitMQWrapperModule, RabbitMQWrapperTestModule } from '@src/infra/rabbitmq'; import { serverConfig } from './server.config'; import { defaultMikroOrmOptions } from './server.module'; @@ -22,8 +22,8 @@ const serverModules = [ LegacySchoolAdminApiModule, UserAdminApiModule, EtherpadClientModule.register({ - apiKey: Configuration.has('ETHERPAD_API_KEY') ? (Configuration.get('ETHERPAD_API_KEY') as string) : undefined, - basePath: Configuration.has('ETHERPAD_URI') ? (Configuration.get('ETHERPAD_URI') as string) : undefined, + apiKey: Configuration.has('ETHERPAD__API_KEY') ? (Configuration.get('ETHERPAD__API_KEY') as string) : undefined, + basePath: Configuration.has('ETHERPAD__URI') ? (Configuration.get('ETHERPAD__URI') as string) : undefined, }), ]; diff --git a/apps/server/src/modules/server/server.config.ts b/apps/server/src/modules/server/server.config.ts index dd0659ffa56..a5d2b6ddf45 100644 --- a/apps/server/src/modules/server/server.config.ts +++ b/apps/server/src/modules/server/server.config.ts @@ -228,8 +228,8 @@ const config: ServerConfig = { FEATURE_NEXBOARD_COPY_ENABLED: Configuration.get('FEATURE_NEXBOARD_COPY_ENABLED') as boolean, FEATURE_ETHERPAD_ENABLED: Configuration.get('FEATURE_ETHERPAD_ENABLED') as boolean, ETHERPAD__PAD_URI: Configuration.get('ETHERPAD__PAD_URI') as string, - ETHERPAD_COOKIE_EXPIRES_SECONDS: Configuration.get('ETHERPAD_COOKIE_EXPIRES_SECONDS') as number, - ETHERPAD_COOKIE_RELEASE_THRESHOLD: Configuration.get('ETHERPAD_COOKIE_RELEASE_THRESHOLD') as number, + ETHERPAD__COOKIE_EXPIRES_SECONDS: Configuration.get('ETHERPAD__COOKIE_EXPIRES_SECONDS') as number, + ETHERPAD__COOKIE_RELEASE_THRESHOLD: Configuration.get('ETHERPAD__COOKIE_RELEASE_THRESHOLD') as number, I18N__AVAILABLE_LANGUAGES: (Configuration.get('I18N__AVAILABLE_LANGUAGES') as string).split(',') as LanguageType[], I18N__DEFAULT_LANGUAGE: Configuration.get('I18N__DEFAULT_LANGUAGE') as unknown as LanguageType, I18N__FALLBACK_LANGUAGE: Configuration.get('I18N__FALLBACK_LANGUAGE') as unknown as LanguageType, diff --git a/config/default.schema.json b/config/default.schema.json index 4121cf8577b..743bfe41711 100644 --- a/config/default.schema.json +++ b/config/default.schema.json @@ -783,57 +783,41 @@ "default": true, "description": "Etherpad feature enabled" }, - "ETHERPAD_API_KEY": { - "default": "", - "type": "string", - "description": "The etherpad api key for sending requests." - }, - "ETHERPAD_API_PATH": { - "type": "string", - "default": "/api/1", - "description": "The etherpad api path." - }, - "ETHERPAD_URI": { - "type": "string", - "default": "https://dbildungscloud.de/etherpad/api/1", - "description": "The etherpad api version uri." - }, - "ETHERPAD_OLD_PAD_URI": { - "type": "string", - "default": "https://etherpad.dbildungscloud.de/p", - "description": "The etherpad api version uri." - }, "ETHERPAD": { "type": "object", "description": "Etherpad settings", "required": ["PAD_URI"], "properties": { + "URI": { + "type": "string", + "description": "The etherpad api version uri." + }, "PAD_URI": { "type": "string", "format": "uri", "pattern": ".*(? { if (typeof context.data.oldPadId === 'undefined') { return context; } - const oldPadURI = Configuration.get('ETHERPAD_OLD_PAD_URI') || 'https://etherpad.schul-cloud.org/p'; + const oldPadURI = Configuration.get('ETHERPAD__OLD_PAD_URI') || 'https://etherpad.schul-cloud.org/p'; try { const lessonsService = context.app.service('/lessons'); const foundLessons = await lessonsService.find({ @@ -58,12 +58,12 @@ const before = { find: [disallow()], get: [disallow()], create: [ - globalHooks.hasPermission(['TOOL_CREATE', 'TOOL_CREATE_ETHERPAD']), - injectCourseId, - globalHooks.restrictToUsersOwnCourses, - getGroupData, - restrictOldPadsToCourse, - ], + globalHooks.hasPermission(['TOOL_CREATE', 'TOOL_CREATE_ETHERPAD']), + injectCourseId, + globalHooks.restrictToUsersOwnCourses, + getGroupData, + restrictOldPadsToCourse, + ], update: [disallow()], patch: [disallow()], remove: [disallow()], diff --git a/src/services/etherpad/utils/EtherpadClient.js b/src/services/etherpad/utils/EtherpadClient.js index f2348212cde..95e5f83ada8 100644 --- a/src/services/etherpad/utils/EtherpadClient.js +++ b/src/services/etherpad/utils/EtherpadClient.js @@ -12,20 +12,20 @@ const logger = require('../../../logger'); */ class EtherpadClient { constructor() { - if (Configuration.has('ETHERPAD_URI')) { - this.uri = () => Configuration.get('ETHERPAD_URI'); + if (Configuration.has('ETHERPAD__URI')) { + this.uri = () => Configuration.get('ETHERPAD__URI'); logger.info('Etherpad uri is set to=', this.uri()); } else { this.uri = null; logger.info('Etherpad uri is not defined'); } - if (Configuration.has('ETHERPAD_COOKIE_EXPIRES_SECONDS')) { - this.cookieExpiresSeconds = Configuration.get('ETHERPAD_COOKIE_EXPIRES_SECONDS'); + if (Configuration.has('ETHERPAD__COOKIE_EXPIRES_SECONDS')) { + this.cookieExpiresSeconds = Configuration.get('ETHERPAD__COOKIE_EXPIRES_SECONDS'); } else { this.cookieExpiresSeconds = 28800; } - if (Configuration.has('ETHERPAD_COOKIE_RELEASE_THRESHOLD')) { - this.cookieReleaseThreshold = Configuration.get('ETHERPAD_COOKIE_RELEASE_THRESHOLD'); + if (Configuration.has('ETHERPAD__COOKIE_RELEASE_THRESHOLD')) { + this.cookieReleaseThreshold = Configuration.get('ETHERPAD__COOKIE_RELEASE_THRESHOLD'); } else { this.cookieReleaseThreshold = 7200; } @@ -45,7 +45,7 @@ class EtherpadClient { method = 'POST', endpoint, formDef = { - apikey: Configuration.get('ETHERPAD_API_KEY'), + apikey: Configuration.get('ETHERPAD__API_KEY'), }, body, }, diff --git a/test/services/etherpad/MockServer.js b/test/services/etherpad/MockServer.js index 271ab31ae0c..5534db4b073 100644 --- a/test/services/etherpad/MockServer.js +++ b/test/services/etherpad/MockServer.js @@ -5,11 +5,7 @@ const { Configuration } = require('@hpi-schul-cloud/commons'); const logger = require('../../../src/logger'); // /api/1/ -module.exports = function MockServer( - url = 'http://localhost:58373', - path = Configuration.get('ETHERPAD_API_PATH'), - resolver -) { +module.exports = function MockServer(url = 'http://localhost:58373', path = '/api', resolver) { const app = express(); app.use(bodyParser.json()); // for parsing application/json app.use(bodyParser.urlencoded({ extended: true })); // support encoded bodies diff --git a/test/services/etherpad/index.test.js b/test/services/etherpad/index.test.js index 079d1b7aa32..ec9409b5e92 100644 --- a/test/services/etherpad/index.test.js +++ b/test/services/etherpad/index.test.js @@ -48,16 +48,16 @@ describe('Etherpad services', () => { logger.warning('freeport:', err); } - const API_PATH_CONFIG = Configuration.get('ETHERPAD_API_PATH'); + const API_PATH_CONFIG = `/api`; const mockUrl = `http://localhost:${port}${API_PATH_CONFIG}`; - Configuration.set('ETHERPAD_URI', mockUrl); - Configuration.set('ETHERPAD_API_KEY', 'someapikey'); + Configuration.set('ETHERPAD__URI', mockUrl); + Configuration.set('ETHERPAD__API_KEY', 'someapikey'); app = await appPromise(); server = await app.listen(0); nestServices = await setupNestServices(app); - const mock = await MockServer(mockUrl, Configuration.get('ETHERPAD_API_PATH')); + const mock = await MockServer(mockUrl, API_PATH_CONFIG); mockServer = mock.server; }); }); diff --git a/test/services/etherpad/permissions.test.js b/test/services/etherpad/permissions.test.js index f3d2fc7bc45..92db0090535 100644 --- a/test/services/etherpad/permissions.test.js +++ b/test/services/etherpad/permissions.test.js @@ -48,16 +48,16 @@ describe('Etherpad Permission Check: Teacher', () => { logger.warning('freeport:', err); } - const ethPath = Configuration.get('ETHERPAD_API_PATH'); + const ethPath = '/api'; const mockUrl = `http://localhost:${port}${ethPath}`; - Configuration.set('ETHERPAD_URI', mockUrl); - Configuration.set('ETHERPAD_API_KEY', 'someapikey'); + Configuration.set('ETHERPAD__URI', mockUrl); + Configuration.set('ETHERPAD__API_KEY', 'someapikey'); app = await appPromise(); server = await app.listen(0); nestServices = await setupNestServices(app); - const mock = MockServer(mockUrl, Configuration.get('ETHERPAD_API_PATH'), done); + const mock = MockServer(mockUrl, ethPath, done); mockServer = mock.server; }); }); diff --git a/test/services/etherpad/permissionsStudents.test.js b/test/services/etherpad/permissionsStudents.test.js index dee87a5333c..713f75d424d 100644 --- a/test/services/etherpad/permissionsStudents.test.js +++ b/test/services/etherpad/permissionsStudents.test.js @@ -40,16 +40,16 @@ describe('Etherpad Permission Check: Students', () => { logger.warning('freeport:', err); } - const ethPath = Configuration.get('ETHERPAD_API_PATH'); + const ethPath = '/api'; const mockUrl = `http://localhost:${port}${ethPath}`; - Configuration.set('ETHERPAD_URI', mockUrl); - Configuration.set('ETHERPAD_API_KEY', 'someapikey'); + Configuration.set('ETHERPAD__URI', mockUrl); + Configuration.set('ETHERPAD__API_KEY', 'someapikey'); app = await appPromise(); server = await app.listen(0); nestServices = await setupNestServices(app); - const mock = MockServer(mockUrl, Configuration.get('ETHERPAD_API_PATH'), done); + const mock = MockServer(mockUrl, ethPath, done); mockServer = mock.server; }); });