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-1285 User launches a CTL tool from a board card #4511

Merged
merged 8 commits into from
Nov 3, 2023
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { Test, TestingModule } from '@nestjs/testing';
import { contextExternalToolFactory, schoolExternalToolFactory } from '@shared/testing';
import { ContextExternalTool } from '../../../context-external-tool/domain';
import { SchoolExternalTool } from '../../../school-external-tool/domain';
import { AutoContextIdStrategy } from './auto-context-id.strategy';

describe(AutoContextIdStrategy.name, () => {
let module: TestingModule;
let strategy: AutoContextIdStrategy;

beforeAll(async () => {
module = await Test.createTestingModule({
providers: [AutoContextIdStrategy],
}).compile();

strategy = module.get(AutoContextIdStrategy);
});

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

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

describe('getValue', () => {
const setup = () => {
const contextId = 'contextId';

const schoolExternalTool: SchoolExternalTool = schoolExternalToolFactory.buildWithId();
const contextExternalTool: ContextExternalTool = contextExternalToolFactory.buildWithId({
schoolToolRef: {
schoolToolId: schoolExternalTool.id as string,
},
contextRef: {
id: contextId,
},
});

return {
contextId,
schoolExternalTool,
contextExternalTool,
};
};

it('should return the context id', () => {
const { contextId, schoolExternalTool, contextExternalTool } = setup();

const result: string | undefined = strategy.getValue(schoolExternalTool, contextExternalTool);

expect(result).toEqual(contextId);
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Injectable } from '@nestjs/common';
import { ContextExternalTool } from '../../../context-external-tool/domain';
import { SchoolExternalTool } from '../../../school-external-tool/domain';
import { AutoParameterStrategy } from './auto-parameter.strategy';

@Injectable()
export class AutoContextIdStrategy implements AutoParameterStrategy {
getValue(schoolExternalTool: SchoolExternalTool, contextExternalTool: ContextExternalTool): string | undefined {
return contextExternalTool.contextRef.id;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
import { createMock, DeepMocked } from '@golevelup/ts-jest';
import { ObjectId } from '@mikro-orm/mongodb';
import { ColumnBoardService } from '@modules/board';
import { CourseService } from '@modules/learnroom';
import { Test, TestingModule } from '@nestjs/testing';
import { BoardExternalReferenceType, ColumnBoard, Course } from '@shared/domain';
import {
columnBoardFactory,
contextExternalToolFactory,
courseFactory,
schoolExternalToolFactory,
setupEntities,
} from '@shared/testing';
import { ToolContextType } from '../../../common/enum';
import { ContextExternalTool } from '../../../context-external-tool/domain';
import { SchoolExternalTool } from '../../../school-external-tool/domain';
import { ParameterTypeNotImplementedLoggableException } from '../../error';
import { AutoContextNameStrategy } from './auto-context-name.strategy';

describe(AutoContextNameStrategy.name, () => {
let module: TestingModule;
let strategy: AutoContextNameStrategy;

let courseService: DeepMocked<CourseService>;
let columnBoardService: DeepMocked<ColumnBoardService>;

beforeAll(async () => {
await setupEntities();

module = await Test.createTestingModule({
providers: [
AutoContextNameStrategy,
{
provide: CourseService,
useValue: createMock<CourseService>(),
},
{
provide: ColumnBoardService,
useValue: createMock<ColumnBoardService>(),
},
],
}).compile();

strategy = module.get(AutoContextNameStrategy);
courseService = module.get(CourseService);
columnBoardService = module.get(ColumnBoardService);
});

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

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

describe('getValue', () => {
describe('when the tool context is "course"', () => {
const setup = () => {
const courseId = new ObjectId().toHexString();
const schoolExternalTool: SchoolExternalTool = schoolExternalToolFactory.build();
const contextExternalTool: ContextExternalTool = contextExternalToolFactory.build({
contextRef: {
id: courseId,
type: ToolContextType.COURSE,
},
});

const course: Course = courseFactory.buildWithId(
{
name: 'testName',
},
courseId
);

courseService.findById.mockResolvedValue(course);

return {
schoolExternalTool,
contextExternalTool,
course,
};
};

it('should return the course name', async () => {
const { schoolExternalTool, contextExternalTool, course } = setup();

const result: string | undefined = await strategy.getValue(schoolExternalTool, contextExternalTool);

expect(result).toEqual(course.name);
});
});

describe('when the tool context is "board element" and the board context is "course"', () => {
const setup = () => {
const boardElementId = new ObjectId().toHexString();
const schoolExternalTool: SchoolExternalTool = schoolExternalToolFactory.build();
const contextExternalTool: ContextExternalTool = contextExternalToolFactory.build({
contextRef: {
id: boardElementId,
type: ToolContextType.BOARD_ELEMENT,
},
});

const course: Course = courseFactory.buildWithId({
name: 'testName',
});

const columnBoard: ColumnBoard = columnBoardFactory.build({
context: {
id: course.id,
type: BoardExternalReferenceType.Course,
},
});

courseService.findById.mockResolvedValue(course);
columnBoardService.findById.mockResolvedValue(columnBoard);

return {
schoolExternalTool,
contextExternalTool,
course,
};
};

it('should return the course name', async () => {
const { schoolExternalTool, contextExternalTool, course } = setup();

const result: string | undefined = await strategy.getValue(schoolExternalTool, contextExternalTool);

expect(result).toEqual(course.name);
});
});

describe('when the tool context is "board element" and the board context is unknown', () => {
const setup = () => {
const boardElementId = new ObjectId().toHexString();
const schoolExternalTool: SchoolExternalTool = schoolExternalToolFactory.build();
const contextExternalTool: ContextExternalTool = contextExternalToolFactory.build({
contextRef: {
id: boardElementId,
type: ToolContextType.BOARD_ELEMENT,
},
});

const columnBoard: ColumnBoard = columnBoardFactory.build({
context: {
id: new ObjectId().toHexString(),
type: 'unknown' as unknown as BoardExternalReferenceType,
},
});

columnBoardService.findById.mockResolvedValue(columnBoard);

return {
schoolExternalTool,
contextExternalTool,
};
};

it('should return undefined', async () => {
const { schoolExternalTool, contextExternalTool } = setup();

const result: string | undefined = await strategy.getValue(schoolExternalTool, contextExternalTool);

expect(result).toBeUndefined();
});
});

describe('when a lookup for a context name is not implemented', () => {
const setup = () => {
const schoolExternalTool: SchoolExternalTool = schoolExternalToolFactory.build();
const contextExternalTool: ContextExternalTool = contextExternalToolFactory.build({
contextRef: {
type: 'unknownContext' as unknown as ToolContextType,
},
});

return {
schoolExternalTool,
contextExternalTool,
};
};

it('should throw a ParameterNotImplementedLoggableException', async () => {
const { schoolExternalTool, contextExternalTool } = setup();

await expect(strategy.getValue(schoolExternalTool, contextExternalTool)).rejects.toThrow(
ParameterTypeNotImplementedLoggableException
);
});
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { ColumnBoardService } from '@modules/board';
import { CourseService } from '@modules/learnroom';
import { Injectable } from '@nestjs/common';
import { BoardExternalReferenceType, ColumnBoard, Course, EntityId } from '@shared/domain';
import { CustomParameterType, ToolContextType } from '../../../common/enum';
import { ContextExternalTool } from '../../../context-external-tool/domain';
import { SchoolExternalTool } from '../../../school-external-tool/domain';
import { ParameterTypeNotImplementedLoggableException } from '../../error';
import { AutoParameterStrategy } from './auto-parameter.strategy';

@Injectable()
export class AutoContextNameStrategy implements AutoParameterStrategy {
constructor(private readonly courseService: CourseService, private readonly columnBoardService: ColumnBoardService) {}

async getValue(
schoolExternalTool: SchoolExternalTool,
contextExternalTool: ContextExternalTool
): Promise<string | undefined> {
switch (contextExternalTool.contextRef.type) {
case ToolContextType.COURSE: {
const courseValue: string = await this.getCourseValue(contextExternalTool.contextRef.id);

return courseValue;
}
case ToolContextType.BOARD_ELEMENT: {
const boardValue: string | undefined = await this.getBoardValue(contextExternalTool.contextRef.id);

return boardValue;
}
default: {
throw new ParameterTypeNotImplementedLoggableException(
`${CustomParameterType.AUTO_CONTEXTNAME}/${contextExternalTool.contextRef.type as string}`
);
}
}
}

private async getCourseValue(courseId: EntityId): Promise<string> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should think about introducing an interface e.g. "ExternalToolsNameProvider" which will be implemented by course and columnboard service and returns an independet object.
The interface should also provide a function to return the toolContextType.
This would decouple our strategy from the services.

The getValue function can use the nestjs reflector to get all implementations of the interface

const course: Course = await this.courseService.findById(courseId);

return course.name;
}

private async getBoardValue(boardId: EntityId): Promise<string | undefined> {
const board: ColumnBoard = await this.columnBoardService.findById(boardId);

if (board.context.type === BoardExternalReferenceType.Course) {
const courseName: string = await this.getCourseValue(board.context.id);

return courseName;
}

return undefined;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { ContextExternalTool } from '../../../context-external-tool/domain';
import { SchoolExternalTool } from '../../../school-external-tool/domain';

export interface AutoParameterStrategy {
getValue(
schoolExternalTool: SchoolExternalTool,
contextExternalTool: ContextExternalTool
): string | Promise<string | undefined> | undefined;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { Test, TestingModule } from '@nestjs/testing';
import { contextExternalToolFactory, schoolExternalToolFactory } from '@shared/testing';
import { ContextExternalTool } from '../../../context-external-tool/domain';
import { SchoolExternalTool } from '../../../school-external-tool/domain';
import { AutoSchoolIdStrategy } from './auto-school-id.strategy';

describe(AutoSchoolIdStrategy.name, () => {
let module: TestingModule;
let strategy: AutoSchoolIdStrategy;

beforeAll(async () => {
module = await Test.createTestingModule({
providers: [AutoSchoolIdStrategy],
}).compile();

strategy = module.get(AutoSchoolIdStrategy);
});

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

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

describe('getValue', () => {
const setup = () => {
const schoolId = 'schoolId';

const schoolExternalTool: SchoolExternalTool = schoolExternalToolFactory.buildWithId({
schoolId,
});
const contextExternalTool: ContextExternalTool = contextExternalToolFactory.buildWithId({
schoolToolRef: {
schoolToolId: schoolExternalTool.id as string,
schoolId,
},
});

return {
schoolId,
schoolExternalTool,
contextExternalTool,
};
};

it('should return the context id', () => {
const { schoolId, schoolExternalTool, contextExternalTool } = setup();

const result: string | undefined = strategy.getValue(schoolExternalTool, contextExternalTool);

expect(result).toEqual(schoolId);
});
});
});
Loading
Loading