Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

THR-45 Consolidation H5P Editor/Player #4449

Closed
wants to merge 50 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
d67be9f
THR-1 h5p editor: temporary file storage (#4147)
ssmid Jun 7, 2023
c2c78db
THR-2 local content storage (#4140)
SteKrause Jun 7, 2023
6709414
THR-3 local library storage (#4133)
marode-cap Jun 7, 2023
bc2e26f
h5p editor: added deployment which uses existing template (#4136)
MajedAlaitwniCap Jun 7, 2023
1ef5891
Thr 5 h5p implementation endpoints (#4169)
SteKrause Jun 7, 2023
97c63cd
Merge remote-tracking branch 'origin/main' into THR-18-dev-feature-h5…
SteKrause Jun 7, 2023
c0935db
THR-7 ContentStorage: store data on S3 via S3ClientAdapter (#4198)
SteKrause Jul 13, 2023
4befc25
Merge remote-tracking branch 'origin/main' into THR-18-dev-feature-h5…
marode-cap Jul 28, 2023
f3aba94
Thr 20 create UI elements (#4190)
marode-cap Jul 31, 2023
f3c41b1
h5p editor: library storage s3 implementation
ssmid Jul 12, 2023
b7aa03d
h5p editor: library storage updated; unit tests; bugfixes
ssmid Jul 18, 2023
d7eac9d
change to correct Buckets / Key names
MajedAlaitwniCap Aug 1, 2023
82e2652
add config keys
MajedAlaitwniCap Aug 2, 2023
aa46e74
change default value for env var S3_REGION
SteKrause Aug 3, 2023
5f8d6e8
Fix API Validation
marode-cap Aug 8, 2023
752816a
THR-25 S3 implementation of temporary file storage (#4204)
ssmid Aug 11, 2023
f9c5914
Thr 27 h5p language parameter (#4283)
SteKrause Aug 15, 2023
840a75f
THR-42 fixes to library storage (#4332)
marode-cap Aug 18, 2023
cfdee9f
Merge remote-tracking branch 'origin/main' into THR-18-dev-feature-h5…
marode-cap Aug 21, 2023
54bc0f8
Merge remote-tracking branch 'origin/main' into THR-18-dev-feature-h5…
marode-cap Aug 21, 2023
56ebbc3
delete h5p-editor static files
SteKrause Aug 21, 2023
cf7917e
Merge branch 'main' into THR-18-dev-feature-h5p-editor
SteKrause Aug 21, 2023
ab50e1d
Remove deployment from main
marode-cap Aug 21, 2023
ef3b235
remove static server
marode-cap Aug 23, 2023
add6306
add LibraryStorage to uc tests
SteKrause Aug 24, 2023
702dd35
expect LanguageType in editor uc test
SteKrause Aug 24, 2023
6023e60
add missing language param at api tests
SteKrause Aug 25, 2023
fddebc1
add field name to response type IGetFileResponse
SteKrause Aug 25, 2023
47a4a24
Merge branch 'main' into THR-18-dev-feature-h5p-editor
SteKrause Aug 25, 2023
6f0fa49
change library entity constructor
SteKrause Aug 25, 2023
d2ea67f
refactor library entity constructor
SteKrause Aug 25, 2023
8cec885
add list of files (#4349)
SevenWaysDP Aug 29, 2023
1f6da9f
code Smells corrector
MajedAlaitwniCap Aug 29, 2023
96e0aa5
Merge branch 'THR-18-dev-feature-h5p-editor' of https://github.com/hp…
MajedAlaitwniCap Aug 29, 2023
491b739
Merge branch 'main' into THR-18-dev-feature-h5p-editor
MajedAlaitwniCap Aug 29, 2023
de771ee
THR-6 H5P editor authorization (#4364)
marode-cap Aug 30, 2023
46e7055
Document custom transform pipe
marode-cap Aug 31, 2023
22cfe6e
Fix PR comments
marode-cap Aug 31, 2023
181c5a7
BaseEntityWithTimestamps for entities
marode-cap Aug 31, 2023
f81e90c
Address more PR comments
marode-cap Aug 31, 2023
85eefd7
pr comments
marode-cap Aug 31, 2023
2f1f8c7
pr comments
marode-cap Aug 31, 2023
5a39ff8
Better file handling
marode-cap Aug 31, 2023
63e4403
Missing file
marode-cap Sep 4, 2023
06cc46e
Merge remote-tracking branch 'origin/main' into THR-18-dev-feature-h5…
SevenWaysDP Sep 7, 2023
75ff0b6
fix changes from s3 module
SevenWaysDP Sep 7, 2023
c9fd20e
h5p editor: change api and static files urls
ssmid Sep 18, 2023
3f9e279
Merge branch 'main' into THR-preview
ssmid Sep 18, 2023
0d3a031
h5p editor: temporarily dont map errors
ssmid Sep 22, 2023
6461c34
Merge branch 'main' into THR-preview
MajedAlaitwniCap Oct 12, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading