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

N21-1881 media licensing logic #4967

Merged
merged 27 commits into from
May 2, 2024
Merged
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
89084ad
- orchestration in media-available-line.uc.ts and tool-launch.uc.ts
IgorCapCoder Apr 25, 2024
b23ceac
N21-1880 provision user licenses when provision users
arnegns Apr 25, 2024
15fc2dc
Merge branch 'main' into N21-1880-provision-user-licenses
arnegns Apr 25, 2024
31594e3
Merge branch 'main' into N21-1881-media-licensing-logic
IgorCapCoder Apr 25, 2024
d6ec0d5
Merge remote-tracking branch 'origin/N21-1880-provision-user-licenses…
IgorCapCoder Apr 25, 2024
0d5027e
- licencing logic
IgorCapCoder Apr 25, 2024
0b62589
Merge branch 'main' into N21-1880-provision-user-licenses
arnegns Apr 26, 2024
1447297
N21-1880 fixes test with not found dependency
arnegns Apr 26, 2024
37bcdca
N21-1880 removes forwardref and provides own service instead
arnegns Apr 26, 2024
44557e7
Merge branch 'main' into N21-1880-provision-user-licenses
arnegns Apr 26, 2024
ba0b8ca
modules, loggable
IgorCapCoder Apr 26, 2024
e85f13a
Merge branch 'main' into N21-1881-media-licensing-logic
IgorCapCoder Apr 26, 2024
02da6ec
N21-1880 refactors schulconnex-client.module
arnegns Apr 26, 2024
af9d81d
Merge branch 'main' into N21-1880-provision-user-licenses
arnegns Apr 26, 2024
43fdc24
N21-1880 removoes unused classes and imports
arnegns Apr 26, 2024
10a27a3
N21-1880 adds missing tests
arnegns Apr 26, 2024
08c7556
unit tests
IgorCapCoder Apr 26, 2024
1af808e
Merge branch 'main' into N21-1881-media-licensing-logic
IgorCapCoder Apr 26, 2024
3aedf65
Merge branch 'N21-1880-provision-user-licenses' into N21-1881-media-l…
IgorCapCoder Apr 26, 2024
23b8fc1
Merge branch 'main' into N21-1881-media-licensing-logic
IgorCapCoder Apr 26, 2024
e9c7ce7
use config service, little fixes
IgorCapCoder Apr 26, 2024
406f7e1
spelling fix
IgorCapCoder Apr 29, 2024
e5e6fb7
api test
IgorCapCoder Apr 30, 2024
0b4fbe8
Merge branch 'main' into N21-1881-media-licensing-logic
IgorCapCoder Apr 30, 2024
e849f3d
requested changes
IgorCapCoder May 2, 2024
a49113a
Merge branch 'main' into N21-1881-media-licensing-logic
IgorCapCoder May 2, 2024
40bda75
move tool license check into own service
IgorCapCoder May 2, 2024
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
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import { EntityManager, ObjectId } from '@mikro-orm/mongodb';
import { type ServerConfig, serverConfig, ServerTestModule } from '@modules/server';
import { contextExternalToolEntityFactory } from '@modules/tool/context-external-tool/testing';
import { externalToolEntityFactory } from '@modules/tool/external-tool/testing';
import { schoolExternalToolEntityFactory } from '@modules/tool/school-external-tool/testing';
import { MediaUserLicenseEntity } from '@modules/user-license/entity';
import { mediaUserLicenseEntityFactory } from '@modules/user-license/testing';
import { HttpStatus, INestApplication } from '@nestjs/common';
import { Test, TestingModule } from '@nestjs/testing';
import { BoardExternalReferenceType } from '@shared/domain/domainobject';
Expand All @@ -11,9 +16,6 @@ import {
TestApiClient,
UserAndAccountTestFactory,
} from '@shared/testing';
import { contextExternalToolEntityFactory } from '@modules/tool/context-external-tool/testing';
import { externalToolEntityFactory } from '@modules/tool/external-tool/testing';
import { schoolExternalToolEntityFactory } from '@modules/tool/school-external-tool/testing';
import { MediaAvailableLineResponse, type MediaBoardResponse, MediaLineResponse } from '../dto';

const baseRouteName = '/media-boards';
Expand Down Expand Up @@ -469,5 +471,95 @@ describe('Media Board (API)', () => {
});
});
});

describe('when a licensing feature is enabled', () => {
const setup = async () => {
const config: ServerConfig = serverConfig();
config.FEATURE_MEDIA_SHELF_ENABLED = true;
config.FEATURE_SCHULCONNEX_MEDIA_LICENSE_ENABLED = true;

const { studentAccount, studentUser } = UserAndAccountTestFactory.buildStudent();

const externalTool = externalToolEntityFactory.build();
const licensedUnusedExternalTool = externalToolEntityFactory.build({ medium: { mediumId: 'mediumId' } });
const unusedExternalTool = externalToolEntityFactory.build({ medium: { mediumId: 'notLicensedByUser' } });
const schoolExternalTool = schoolExternalToolEntityFactory.build({
tool: externalTool,
school: studentUser.school,
});
const licensedUnusedSchoolExternalTool = schoolExternalToolEntityFactory.build({
tool: licensedUnusedExternalTool,
school: studentUser.school,
});
const unusedSchoolExternalTool = schoolExternalToolEntityFactory.build({
tool: unusedExternalTool,
school: studentUser.school,
});
const contextExternalTool = contextExternalToolEntityFactory.build({
schoolTool: schoolExternalTool,
});

const mediaBoard = mediaBoardNodeFactory.buildWithId({
context: {
id: studentUser.id,
type: BoardExternalReferenceType.User,
},
});
const mediaLine = mediaLineNodeFactory.buildWithId({ parent: mediaBoard });
const mediaElement = mediaExternalToolElementNodeFactory.buildWithId({
parent: mediaLine,
contextExternalTool,
});

const userLicense: MediaUserLicenseEntity = mediaUserLicenseEntityFactory.build({
user: studentUser,
mediumId: 'mediumId',
});

await em.persistAndFlush([
studentAccount,
studentUser,
externalTool,
licensedUnusedExternalTool,
unusedExternalTool,
schoolExternalTool,
licensedUnusedSchoolExternalTool,
unusedSchoolExternalTool,
contextExternalTool,
mediaBoard,
mediaLine,
mediaElement,
userLicense,
]);
em.clear();

const studentClient = await testApiClient.login(studentAccount);

return {
studentClient,
mediaBoard,
licensedUnusedExternalTool,
licensedUnusedSchoolExternalTool,
};
};

it('should return the available media line with only the licensed element', async () => {
const { studentClient, mediaBoard, licensedUnusedExternalTool, licensedUnusedSchoolExternalTool } =
await setup();

const response = await studentClient.get(`${mediaBoard.id}/media-available-line`);

expect(response.status).toEqual(HttpStatus.OK);
expect(response.body).toEqual<MediaAvailableLineResponse>({
elements: [
{
schoolExternalToolId: licensedUnusedSchoolExternalTool.id,
name: licensedUnusedExternalTool.name,
description: licensedUnusedExternalTool.description,
},
],
});
});
});
});
});
3 changes: 2 additions & 1 deletion apps/server/src/modules/board/media-board-api.module.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { AuthorizationModule } from '@modules/authorization';
import { UserLicenseModule } from '@modules/user-license';
import { Module } from '@nestjs/common';
import { LoggerModule } from '@src/core/logger';
import { ToolModule } from '../tool';
Expand All @@ -8,7 +9,7 @@ import { MediaBoardModule } from './media-board.module';
import { MediaAvailableLineUc, MediaBoardUc, MediaElementUc, MediaLineUc } from './uc';

@Module({
imports: [BoardModule, LoggerModule, AuthorizationModule, MediaBoardModule, ToolModule],
imports: [BoardModule, LoggerModule, AuthorizationModule, MediaBoardModule, ToolModule, UserLicenseModule],
controllers: [MediaBoardController, MediaLineController, MediaElementController],
providers: [MediaBoardUc, MediaLineUc, MediaElementUc, MediaAvailableLineUc],
})
Expand Down
1 change: 1 addition & 0 deletions apps/server/src/modules/board/media-board.config.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export interface MediaBoardConfig {
FEATURE_MEDIA_SHELF_ENABLED: boolean;
FEATURE_SCHULCONNEX_MEDIA_LICENSE_ENABLED: boolean;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest';
import { ObjectId } from '@mikro-orm/mongodb';
import { AuthorizationService } from '@modules/authorization';
import { ExternalTool } from '@modules/tool/external-tool/domain';
import { SchoolExternalTool } from '@modules/tool/school-external-tool/domain';
import { MediaUserLicense, mediaUserLicenseFactory, UserLicenseService } from '@modules/user-license';
import { ConfigService } from '@nestjs/config';
import { Test, TestingModule } from '@nestjs/testing';
import { FeatureDisabledLoggableException } from '@shared/common/loggable-exception';
Expand All @@ -24,7 +26,6 @@ import {
setupEntities,
userFactory,
} from '@shared/testing';
import { SchoolExternalTool } from '@modules/tool/school-external-tool/domain';
import type { MediaBoardConfig } from '../../media-board.config';
import { BoardDoAuthorizableService, MediaAvailableLineService, MediaBoardService } from '../../service';
import { MediaAvailableLineUc } from './media-available-line.uc';
Expand All @@ -38,6 +39,7 @@ describe(MediaAvailableLineUc.name, () => {
let mediaAvailableLineService: DeepMocked<MediaAvailableLineService>;
let configService: DeepMocked<ConfigService<MediaBoardConfig, true>>;
let mediaBoardService: DeepMocked<MediaBoardService>;
let userLicenseService: DeepMocked<UserLicenseService>;

beforeAll(async () => {
await setupEntities();
Expand Down Expand Up @@ -65,6 +67,10 @@ describe(MediaAvailableLineUc.name, () => {
provide: MediaBoardService,
useValue: createMock<MediaBoardService>(),
},
{
provide: UserLicenseService,
useValue: createMock<UserLicenseService>(),
},
],
}).compile();

Expand All @@ -74,6 +80,7 @@ describe(MediaAvailableLineUc.name, () => {
mediaAvailableLineService = module.get(MediaAvailableLineService);
configService = module.get(ConfigService);
mediaBoardService = module.get(MediaBoardService);
userLicenseService = module.get(UserLicenseService);
});

afterAll(async () => {
Expand All @@ -88,6 +95,7 @@ describe(MediaAvailableLineUc.name, () => {
describe('when the user request the available line', () => {
const setup = () => {
configService.get.mockReturnValueOnce(true);
configService.get.mockReturnValueOnce(false);

const user: User = userFactory.build();
const mediaExternalToolElement: MediaExternalToolElement = mediaExternalToolElementFactory.build();
Expand Down Expand Up @@ -204,9 +212,177 @@ describe(MediaAvailableLineUc.name, () => {
});
});

describe('when licensing feature flag is enabled', () => {
describe('when tool has no mediumId', () => {
const setup = () => {
configService.get.mockReturnValue(true);

const user: User = userFactory.build();
const mediaExternalToolElement: MediaExternalToolElement = mediaExternalToolElementFactory.build();
const mediaBoard: MediaBoard = mediaBoardFactory.addChild(mediaExternalToolElement).build();
const mediaAvailableLineElement: MediaAvailableLineElement = mediaAvailableLineElementFactory.build();
const mediaAvailableLine: MediaAvailableLine = mediaAvailableLineFactory
.withElement(mediaAvailableLineElement)
.build();
const externalTool1: ExternalTool = externalToolFactory.build();
const externalTool2: ExternalTool = externalToolFactory.build();
const schoolExternalTool1: SchoolExternalTool = schoolExternalToolFactory.build({ toolId: externalTool1.id });
const schoolExternalTool2: SchoolExternalTool = schoolExternalToolFactory.build({ toolId: externalTool2.id });
const boardDoAuthorizable: BoardDoAuthorizable = boardDoAuthorizableFactory.build();

userLicenseService.getMediaUserLicensesForUser.mockResolvedValue([]);

mediaBoardService.findById.mockResolvedValueOnce(mediaBoard);
authorizationService.getUserWithPermissions.mockResolvedValueOnce(user);
boardDoAuthorizableService.getBoardAuthorizable.mockResolvedValueOnce(boardDoAuthorizable);
mediaAvailableLineService.getUnusedAvailableSchoolExternalTools.mockResolvedValueOnce([
schoolExternalTool1,
schoolExternalTool2,
]);
mediaAvailableLineService.getAvailableExternalToolsForSchool.mockResolvedValueOnce([
externalTool1,
externalTool2,
]);
mediaAvailableLineService.matchTools.mockReturnValueOnce([
[externalTool1, schoolExternalTool1],
[externalTool2, schoolExternalTool2],
]);
mediaAvailableLineService.createMediaAvailableLine.mockReturnValueOnce(mediaAvailableLine);

return {
user,
mediaBoard,
mediaAvailableLineElement,
};
};

it('should return media line', async () => {
const { user, mediaBoard, mediaAvailableLineElement } = setup();

const line: MediaAvailableLine = await uc.getMediaAvailableLine(user.id, mediaBoard.id);

expect(line).toEqual({
elements: [
{
schoolExternalToolId: mediaAvailableLineElement.schoolExternalToolId,
name: mediaAvailableLineElement.name,
description: mediaAvailableLineElement.description,
logoUrl: mediaAvailableLineElement.logoUrl,
},
],
});
});
});

describe('when license exist', () => {
const setup = () => {
configService.get.mockReturnValue(true);

const user: User = userFactory.build();
const mediaExternalToolElement: MediaExternalToolElement = mediaExternalToolElementFactory.build();
const mediaBoard: MediaBoard = mediaBoardFactory.addChild(mediaExternalToolElement).build();
const mediaAvailableLineElement: MediaAvailableLineElement = mediaAvailableLineElementFactory.build();
const mediaAvailableLine: MediaAvailableLine = mediaAvailableLineFactory
.withElement(mediaAvailableLineElement)
.build();
const externalTool1: ExternalTool = externalToolFactory.build({ medium: { mediumId: 'mediumId' } });
const externalTool2: ExternalTool = externalToolFactory.build();
const schoolExternalTool1: SchoolExternalTool = schoolExternalToolFactory.build({ toolId: externalTool1.id });
const schoolExternalTool2: SchoolExternalTool = schoolExternalToolFactory.build({ toolId: externalTool2.id });
const boardDoAuthorizable: BoardDoAuthorizable = boardDoAuthorizableFactory.build();

const mediaUserlicense: MediaUserLicense = mediaUserLicenseFactory.build();
mediaUserlicense.mediumId = 'mediumId';

userLicenseService.getMediaUserLicensesForUser.mockResolvedValue([mediaUserlicense]);

mediaBoardService.findById.mockResolvedValueOnce(mediaBoard);
authorizationService.getUserWithPermissions.mockResolvedValueOnce(user);
boardDoAuthorizableService.getBoardAuthorizable.mockResolvedValueOnce(boardDoAuthorizable);
mediaAvailableLineService.getUnusedAvailableSchoolExternalTools.mockResolvedValueOnce([
schoolExternalTool1,
schoolExternalTool2,
]);
mediaAvailableLineService.getAvailableExternalToolsForSchool.mockResolvedValueOnce([
externalTool1,
externalTool2,
]);
mediaAvailableLineService.matchTools.mockReturnValueOnce([
[externalTool1, schoolExternalTool1],
[externalTool2, schoolExternalTool2],
]);
mediaAvailableLineService.createMediaAvailableLine.mockReturnValueOnce(mediaAvailableLine);

return {
user,
mediaBoard,
mediaAvailableLineElement,
};
};

it('should return the available line', async () => {
const { user, mediaBoard, mediaAvailableLineElement } = setup();

const line: MediaAvailableLine = await uc.getMediaAvailableLine(user.id, mediaBoard.id);

expect(line).toEqual({
elements: [
{
schoolExternalToolId: mediaAvailableLineElement.schoolExternalToolId,
name: mediaAvailableLineElement.name,
description: mediaAvailableLineElement.description,
logoUrl: mediaAvailableLineElement.logoUrl,
},
],
});
});
});

describe('when license does not exist', () => {
const setup = () => {
configService.get.mockReturnValue(true);

const user: User = userFactory.build();
const mediaExternalToolElement: MediaExternalToolElement = mediaExternalToolElementFactory.build();
const mediaBoard: MediaBoard = mediaBoardFactory.addChild(mediaExternalToolElement).build();
const mediaAvailableLine: MediaAvailableLine = mediaAvailableLineFactory.build();
const externalTool1: ExternalTool = externalToolFactory.build({ medium: { mediumId: 'mediumId' } });
const schoolExternalTool1: SchoolExternalTool = schoolExternalToolFactory.build({ toolId: externalTool1.id });
const boardDoAuthorizable: BoardDoAuthorizable = boardDoAuthorizableFactory.build();

const mediaUserlicense: MediaUserLicense = mediaUserLicenseFactory.build();

userLicenseService.getMediaUserLicensesForUser.mockResolvedValue([mediaUserlicense]);

mediaBoardService.findById.mockResolvedValueOnce(mediaBoard);
authorizationService.getUserWithPermissions.mockResolvedValueOnce(user);
boardDoAuthorizableService.getBoardAuthorizable.mockResolvedValueOnce(boardDoAuthorizable);
mediaAvailableLineService.getUnusedAvailableSchoolExternalTools.mockResolvedValueOnce([schoolExternalTool1]);
mediaAvailableLineService.getAvailableExternalToolsForSchool.mockResolvedValueOnce([externalTool1]);
mediaAvailableLineService.matchTools.mockReturnValueOnce([[externalTool1, schoolExternalTool1]]);
mediaAvailableLineService.createMediaAvailableLine.mockReturnValueOnce(mediaAvailableLine);

return {
user,
mediaBoard,
};
};

it('should show empty avalable line', async () => {
const { user, mediaBoard } = setup();

const line: MediaAvailableLine = await uc.getMediaAvailableLine(user.id, mediaBoard.id);

expect(line).toEqual({
elements: [],
});
});
});
});

describe('when the feature is disabled', () => {
const setup = () => {
configService.get.mockReturnValueOnce(false);
configService.get.mockReturnValue(false);

const userId = new ObjectId().toHexString();
const mediaBoardId = new ObjectId().toHexString();
Expand Down
Loading
Loading