Skip to content

Commit

Permalink
Merge branch 'BC-5629-batch-deletion-mechanism' of https://github.com…
Browse files Browse the repository at this point in the history
…/hpi-schul-cloud/schulcloud-server into BC-5629-batch-deletion-mechanism
  • Loading branch information
bn-pass committed Nov 8, 2023
2 parents a1b117b + e9d5559 commit 5dd40a7
Show file tree
Hide file tree
Showing 75 changed files with 8,951 additions and 171 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/push.yml
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ jobs:
uses: github/codeql-action/upload-sarif@v2
with:
sarif_file: 'trivy-results.sarif'

end-to-end-tests:
needs:
- build_and_push
Expand Down
2 changes: 1 addition & 1 deletion ansible/roles/schulcloud-server-h5p/tasks/main.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
- name: H5pEditorService
- name: H5PEditorProvider
kubernetes.core.k8s:
kubeconfig: ~/.kube/config
namespace: "{{ NAMESPACE }}"
Expand Down
1 change: 1 addition & 0 deletions apps/server/src/apps/h5p-editor.app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ async function bootstrap() {
const nestExpress = express();

const nestExpressAdapter = new ExpressAdapter(nestExpress);

const nestApp = await NestFactory.create(H5PEditorModule, nestExpressAdapter);
// WinstonLogger
nestApp.useLogger(await nestApp.resolve(LegacyLogger));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
import { DeepMocked, createMock } from '@golevelup/ts-jest';
import { H5PAjaxEndpoint } from '@lumieducation/h5p-server';
import { EntityManager } from '@mikro-orm/core';
import { HttpStatus, INestApplication } from '@nestjs/common';
import { Test } from '@nestjs/testing';
import { S3ClientAdapter } from '@shared/infra/s3-client';
import { TestApiClient, UserAndAccountTestFactory } from '@shared/testing';
import { H5PEditorTestModule } from '../../h5p-editor-test.module';
import { H5P_CONTENT_S3_CONNECTION, H5P_LIBRARIES_S3_CONNECTION } from '../../h5p-editor.config';

describe('H5PEditor Controller (api)', () => {
let app: INestApplication;
let em: EntityManager;
let testApiClient: TestApiClient;

let ajaxEndpoint: DeepMocked<H5PAjaxEndpoint>;

beforeAll(async () => {
const module = await Test.createTestingModule({
imports: [H5PEditorTestModule],
})
.overrideProvider(H5P_CONTENT_S3_CONNECTION)
.useValue(createMock<S3ClientAdapter>())
.overrideProvider(H5P_LIBRARIES_S3_CONNECTION)
.useValue(createMock<S3ClientAdapter>())
.overrideProvider(H5PAjaxEndpoint)
.useValue(createMock<H5PAjaxEndpoint>())
.compile();

app = module.createNestApplication();
await app.init();
em = app.get(EntityManager);
ajaxEndpoint = app.get(H5PAjaxEndpoint);
testApiClient = new TestApiClient(app, 'h5p-editor');
});

afterEach(() => {
jest.resetAllMocks();
});

afterAll(async () => {
await app.close();
});

describe('when calling AJAX GET', () => {
describe('when user not exists', () => {
it('should respond with unauthorized exception', async () => {
const response = await testApiClient.get('ajax');

expect(response.statusCode).toEqual(HttpStatus.UNAUTHORIZED);
expect(response.body).toEqual({
type: 'UNAUTHORIZED',
title: 'Unauthorized',
message: 'Unauthorized',
code: 401,
});
});
});

describe('when user is logged in', () => {
const createStudent = () => UserAndAccountTestFactory.buildStudent();

const setup = async () => {
const { studentAccount, studentUser } = createStudent();

await em.persistAndFlush([studentAccount, studentUser]);
em.clear();

const loggedInClient = await testApiClient.login(studentAccount);

return { loggedInClient, studentUser };
};

it('should call H5PAjaxEndpoint', async () => {
const {
loggedInClient,
studentUser: { id },
} = await setup();

const dummyResponse = {
apiVersion: { major: 1, minor: 1 },
details: [],
libraries: [],
outdated: false,
recentlyUsed: [],
user: 'DummyUser',
};

ajaxEndpoint.getAjax.mockResolvedValueOnce(dummyResponse);

const response = await loggedInClient.get(`ajax?action=content-type-cache`);

expect(response.statusCode).toEqual(HttpStatus.OK);
expect(response.body).toEqual(dummyResponse);
expect(ajaxEndpoint.getAjax).toHaveBeenCalledWith(
'content-type-cache',
undefined, // MachineName
undefined, // MajorVersion
undefined, // MinorVersion
'de', // Language
expect.objectContaining({ id })
);
});
});

describe('when calling AJAX POST', () => {
describe('when user not exists', () => {
it('should respond with unauthorized exception', async () => {
const response = await testApiClient.post('ajax');

expect(response.statusCode).toEqual(HttpStatus.UNAUTHORIZED);
expect(response.body).toEqual({
type: 'UNAUTHORIZED',
title: 'Unauthorized',
message: 'Unauthorized',
code: 401,
});
});
});

describe('when user is logged in', () => {
const createStudent = () => UserAndAccountTestFactory.buildStudent();

const setup = async () => {
const { studentAccount, studentUser } = createStudent();

await em.persistAndFlush([studentAccount, studentUser]);
em.clear();

const loggedInClient = await testApiClient.login(studentAccount);

return { loggedInClient, studentUser };
};

it('should call H5PAjaxEndpoint', async () => {
const {
loggedInClient,
studentUser: { id },
} = await setup();

const dummyResponse = [
{
majorVersion: 1,
minorVersion: 2,
metadataSettings: {},
name: 'Dummy Library',
restricted: false,
runnable: true,
title: 'Dummy Library',
tutorialUrl: '',
uberName: 'dummyLibrary-1.1',
},
];

const dummyBody = { contentId: 'id', field: 'field', libraries: ['dummyLibrary-1.0'], libraryParameters: '' };

ajaxEndpoint.postAjax.mockResolvedValueOnce(dummyResponse);

const response = await loggedInClient.post(`ajax?action=libraries`, dummyBody);

expect(response.statusCode).toEqual(HttpStatus.CREATED);
expect(response.body).toEqual(dummyResponse);
expect(ajaxEndpoint.postAjax).toHaveBeenCalledWith(
'libraries',
dummyBody,
'de',
expect.objectContaining({ id }),
undefined,
undefined,
undefined,
undefined,
undefined
);
});
});
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { DeepMocked, createMock } from '@golevelup/ts-jest/lib/mocks';
import { EntityManager, ObjectId } from '@mikro-orm/mongodb';
import { ExecutionContext, INestApplication } from '@nestjs/common';
import { Test } from '@nestjs/testing';
import { Permission } from '@shared/domain';
import { S3ClientAdapter } from '@shared/infra/s3-client';
import { cleanupCollections, mapUserToCurrentUser, roleFactory, schoolFactory, userFactory } from '@shared/testing';
import { ICurrentUser } from '@src/modules/authentication';
import { JwtAuthGuard } from '@src/modules/authentication/guard/jwt-auth.guard';
import { Request } from 'express';
import request from 'supertest';
import { H5PEditorTestModule } from '../../h5p-editor-test.module';
import { H5P_CONTENT_S3_CONNECTION, H5P_LIBRARIES_S3_CONNECTION } from '../../h5p-editor.config';
import { H5PEditorUc } from '../../uc/h5p.uc';

class API {
constructor(private app: INestApplication) {
this.app = app;
}

async deleteH5pContent(contentId: string) {
return request(this.app.getHttpServer()).post(`/h5p-editor/delete/${contentId}`);
}
}

const setup = () => {
const contentId = new ObjectId(0).toString();
const notExistingContentId = new ObjectId(1).toString();
const badContentId = '';

return { contentId, notExistingContentId, badContentId };
};

describe('H5PEditor Controller (api)', () => {
let app: INestApplication;
let api: API;
let em: EntityManager;
let currentUser: ICurrentUser;
let h5PEditorUc: DeepMocked<H5PEditorUc>;

beforeAll(async () => {
const module = await Test.createTestingModule({
imports: [H5PEditorTestModule],
})
.overrideGuard(JwtAuthGuard)
.useValue({
canActivate(context: ExecutionContext) {
const req: Request = context.switchToHttp().getRequest();
req.user = currentUser;
return true;
},
})
.overrideProvider(H5P_CONTENT_S3_CONNECTION)
.useValue(createMock<S3ClientAdapter>())
.overrideProvider(H5P_LIBRARIES_S3_CONNECTION)
.useValue(createMock<S3ClientAdapter>())
.overrideProvider(H5PEditorUc)
.useValue(createMock<H5PEditorUc>())
.compile();

app = module.createNestApplication();
await app.init();
h5PEditorUc = module.get(H5PEditorUc);

api = new API(app);
em = module.get(EntityManager);
});

afterAll(async () => {
await app.close();
});

describe('delete h5p content', () => {
beforeEach(async () => {
await cleanupCollections(em);
const school = schoolFactory.build();
const roles = roleFactory.buildList(1, {
permissions: [Permission.FILESTORAGE_CREATE, Permission.FILESTORAGE_VIEW],
});
const user = userFactory.build({ school, roles });

await em.persistAndFlush([user, school]);
em.clear();

currentUser = mapUserToCurrentUser(user);
});
describe('with valid request params', () => {
it('should return 200 status', async () => {
const { contentId } = setup();

h5PEditorUc.deleteH5pContent.mockResolvedValueOnce(true);
const response = await api.deleteH5pContent(contentId);
expect(response.status).toEqual(201);
});
});
describe('with bad request params', () => {
it('should return 500 status', async () => {
const { notExistingContentId } = setup();

h5PEditorUc.deleteH5pContent.mockRejectedValueOnce(new Error('Could not delete H5P content'));
const response = await api.deleteH5pContent(notExistingContentId);
expect(response.status).toEqual(500);
});
});
});
});
Loading

0 comments on commit 5dd40a7

Please sign in to comment.