Skip to content

Commit

Permalink
SPSH-826 Send Klassen to itsLearning (#581)
Browse files Browse the repository at this point in the history
  • Loading branch information
marode-cap authored Jul 11, 2024
1 parent 79bb37a commit e0ce79d
Show file tree
Hide file tree
Showing 6 changed files with 206 additions and 2 deletions.
31 changes: 31 additions & 0 deletions src/modules/itslearning/actions/create-group.action.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,37 @@ describe('CreateGroupAction', () => {

expect(action.buildRequest()).toBeDefined();
});

it('should return include extension data', () => {
const name: string = `${faker.word.adjective()} course`;
const action: CreateGroupAction = new CreateGroupAction({
id: faker.string.uuid(),
name: name,
parentId: faker.string.uuid(),
type: 'Course',
});

expect(action.buildRequest()).toMatchObject({
'ims:createGroupRequest': {
'ims:group': {
'ims2:extension': {
'ims1:extensionField': [
{
'ims1:fieldName': 'course',
'ims1:fieldType': 'String',
'ims1:fieldValue': name,
},
{
'ims1:fieldName': 'course/code',
'ims1:fieldType': 'String',
'ims1:fieldValue': name,
},
],
},
},
},
});
});
});

describe('parseBody', () => {
Expand Down
17 changes: 17 additions & 0 deletions src/modules/itslearning/actions/create-group.action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,22 @@ export class CreateGroupAction extends IMSESAction<CreateGroupResponseBody, void
}

public override buildRequest(): object {
let extension: object[] | undefined;
if (this.params.type === 'Course') {
extension = [
{
'ims1:fieldName': 'course',
'ims1:fieldType': 'String',
'ims1:fieldValue': this.params.name,
},
{
'ims1:fieldName': 'course/code',
'ims1:fieldType': 'String',
'ims1:fieldValue': this.params.name,
},
];
}

return {
'ims:createGroupRequest': {
'@_xmlns:ims': IMS_GROUP_MAN_MESS_SCHEMA,
Expand Down Expand Up @@ -57,6 +73,7 @@ export class CreateGroupAction extends IMSESAction<CreateGroupResponseBody, void
'ims2:descLong': this.params.longDescription,
'ims2:descFull': this.params.fullDescription,
},
'ims2:extension': extension && { 'ims1:extensionField': extension },
},
},
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { ConfigService } from '@nestjs/config';
import { ItsLearningConfig, ServerConfig } from '../../../shared/config/index.js';
import { CreateGroupAction } from '../actions/create-group.action.js';
import { DomainError } from '../../../shared/error/domain.error.js';
import { KlasseCreatedEvent } from '../../../shared/events/klasse-created.event.js';

describe('ItsLearning Organisations Event Handler', () => {
let module: TestingModule;
Expand Down Expand Up @@ -240,4 +241,93 @@ describe('ItsLearning Organisations Event Handler', () => {
await sut.createSchuleEventHandler(event);
});
});

describe('createKlasseEventHandler', () => {
it('should log on success', async () => {
const event: KlasseCreatedEvent = new KlasseCreatedEvent(
faker.string.uuid(),
faker.string.alphanumeric(),
faker.string.uuid(),
);
itsLearningServiceMock.send.mockResolvedValueOnce({ ok: true, value: {} }); // ReadGroupAction
itsLearningServiceMock.send.mockResolvedValueOnce({
ok: true,
value: undefined,
}); // CreateGroupAction

await sut.createKlasseEventHandler(event);

expect(itsLearningServiceMock.send).toHaveBeenLastCalledWith(expect.any(CreateGroupAction));
expect(loggerMock.info).toHaveBeenLastCalledWith(`Klasse with ID ${event.id} created.`);
});

it('should skip event, if not enabled', async () => {
sut.ENABLED = false;
const event: KlasseCreatedEvent = new KlasseCreatedEvent(
faker.string.uuid(),
faker.string.alphanumeric(),
faker.string.uuid(),
);

await sut.createKlasseEventHandler(event);

expect(loggerMock.info).toHaveBeenCalledWith('Not enabled, ignoring event.');
expect(itsLearningServiceMock.send).not.toHaveBeenCalled();
});

it('should log error, if administriertVon is undefined', async () => {
const event: KlasseCreatedEvent = new KlasseCreatedEvent(
faker.string.uuid(),
faker.string.alphanumeric(),
undefined,
);

await sut.createKlasseEventHandler(event);

expect(loggerMock.error).toHaveBeenCalledWith('Klasse has no parent organisation. Aborting.');
});

it('should log error, if the klasse has no name', async () => {
const event: KlasseCreatedEvent = new KlasseCreatedEvent(
faker.string.uuid(),
undefined,
faker.string.uuid(),
);

await sut.createKlasseEventHandler(event);

expect(loggerMock.error).toHaveBeenCalledWith('Klasse has no name. Aborting.');
});

it('should log error, if the parent school does not exist', async () => {
const event: KlasseCreatedEvent = new KlasseCreatedEvent(
faker.string.uuid(),
faker.string.alphanumeric(),
faker.string.uuid(),
);
itsLearningServiceMock.send.mockResolvedValueOnce({ ok: false, error: createMock() }); // ReadGroupAction

await sut.createKlasseEventHandler(event);

expect(loggerMock.error).toHaveBeenCalledWith(
`Parent Organisation (${event.administriertVon}) does not exist in itsLearning.`,
);
});

it('should log error on failed creation', async () => {
const event: KlasseCreatedEvent = new KlasseCreatedEvent(
faker.string.uuid(),
faker.string.alphanumeric(),
faker.string.uuid(),
);
itsLearningServiceMock.send.mockResolvedValueOnce({ ok: true, value: {} }); // ReadGroupAction
itsLearningServiceMock.send.mockResolvedValueOnce({
ok: false,
error: createMock<DomainError>({ message: 'Error' }),
}); // CreateGroupAction

await sut.createKlasseEventHandler(event);
expect(loggerMock.error).toHaveBeenLastCalledWith('Could not create Klasse in itsLearning: Error');
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { OrganisationRepository } from '../../organisation/persistence/organisat
import { CreateGroupAction, CreateGroupParams } from '../actions/create-group.action.js';
import { GroupResponse, ReadGroupAction } from '../actions/read-group.action.js';
import { ItsLearningIMSESService } from '../itslearning.service.js';
import { KlasseCreatedEvent } from '../../../shared/events/klasse-created.event.js';

@Injectable()
export class ItsLearningOrganisationsEventHandler {
Expand Down Expand Up @@ -65,7 +66,7 @@ export class ItsLearningOrganisationsEventHandler {

const params: CreateGroupParams = {
id: organisation.id,
name: organisation.name ?? 'Unbenannte Schule',
name: `${organisation.kennung} (${organisation.name ?? 'Unbenannte Schule'})`,
type: 'School',
parentId: parent,
};
Expand All @@ -86,13 +87,61 @@ export class ItsLearningOrganisationsEventHandler {
const result: Result<void, DomainError> = await this.itsLearningService.send(action);

if (!result.ok) {
this.logger.error(`Could not create Schule in itsLearning: ${result.error.message}`);
return this.logger.error(`Could not create Schule in itsLearning: ${result.error.message}`);
}

this.logger.info(`Schule with ID ${organisation.id} created.`);
}
}

@EventHandler(KlasseCreatedEvent)
public async createKlasseEventHandler(event: KlasseCreatedEvent): Promise<void> {
this.logger.info(`Received KlasseCreatedEvent, ID: ${event.id}`);

if (!this.ENABLED) {
this.logger.info('Not enabled, ignoring event.');
return;
}

if (!event.administriertVon) {
return this.logger.error('Klasse has no parent organisation. Aborting.');
}

if (!event.name) {
return this.logger.error('Klasse has no name. Aborting.');
}

{
// Check if parent exists in itsLearning
const readAction: ReadGroupAction = new ReadGroupAction(event.administriertVon);
const result: Result<GroupResponse, DomainError> = await this.itsLearningService.send(readAction);

if (!result.ok) {
// Parent school does not exist
return this.logger.error(
`Parent Organisation (${event.administriertVon}) does not exist in itsLearning.`,
);
}
}

const params: CreateGroupParams = {
id: event.id,
name: event.name,
type: 'Course',
parentId: event.administriertVon,
};

const action: CreateGroupAction = new CreateGroupAction(params);

const result: Result<void, DomainError> = await this.itsLearningService.send(action);

if (!result.ok) {
return this.logger.error(`Could not create Klasse in itsLearning: ${result.error.message}`);
}

this.logger.info(`Klasse with ID ${event.id} created.`);
}

private async findParentId(organisation: Organisation<true>): Promise<OrganisationID> {
const [oeffentlich, ersatz]: [Organisation<true> | undefined, Organisation<true> | undefined] =
await this.organisationRepository.findRootDirectChildren();
Expand Down
5 changes: 5 additions & 0 deletions src/modules/organisation/persistence/organisation.repo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { OrganisationID } from '../../../shared/types/aggregate-ids.types.js';
import { SchuleCreatedEvent } from '../../../shared/events/schule-created.event.js';
import { EventService } from '../../../core/eventbus/index.js';
import { OrganisationsTyp } from '../domain/organisation.enums.js';
import { KlasseCreatedEvent } from '../../../shared/events/klasse-created.event.js';
import { ScopeOperator } from '../../../shared/persistence/scope.enums.js';

@Injectable()
Expand All @@ -32,6 +33,10 @@ export class OrganisationRepo {
await this.em.persistAndFlush(organisation);
if (organisationDo.typ === OrganisationsTyp.SCHULE) {
this.eventService.publish(new SchuleCreatedEvent(organisation.id));
} else if (organisationDo.typ === OrganisationsTyp.KLASSE) {
this.eventService.publish(
new KlasseCreatedEvent(organisation.id, organisation.name, organisation.administriertVon),
);
}

return this.mapper.map(organisation, OrganisationEntity, OrganisationDo);
Expand Down
12 changes: 12 additions & 0 deletions src/shared/events/klasse-created.event.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { BaseEvent } from './base-event.js';
import { OrganisationID } from '../types/index.js';

export class KlasseCreatedEvent extends BaseEvent {
public constructor(
public readonly id: OrganisationID,
public readonly name: string | undefined,
public readonly administriertVon: OrganisationID | undefined,
) {
super();
}
}

0 comments on commit e0ce79d

Please sign in to comment.