Skip to content

Commit

Permalink
refactor calendar service
Browse files Browse the repository at this point in the history
  • Loading branch information
micners committed Mar 29, 2024
1 parent 7ea6880 commit a04ef30
Show file tree
Hide file tree
Showing 3 changed files with 197 additions and 6 deletions.
116 changes: 113 additions & 3 deletions apps/server/src/infra/calendar/service/calendar.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,20 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest';
import { Configuration } from '@hpi-schul-cloud/commons/lib';
import { CalendarEventDto, CalendarService } from '@infra/calendar';
import { HttpService } from '@nestjs/axios';
import { InternalServerErrorException } from '@nestjs/common';
import { HttpStatus, InternalServerErrorException } from '@nestjs/common';
import { Test, TestingModule } from '@nestjs/testing';
import { axiosResponseFactory } from '@shared/testing';
import { AxiosResponse } from 'axios';
import { of, throwError } from 'rxjs';
import { Logger } from '@src/core/logger';
import { EventBus } from '@nestjs/cqrs';
import { EntityId } from '@shared/domain/types';
import {
DomainDeletionReportBuilder,
DomainName,
DomainOperationReportBuilder,
OperationType,
} from '@src/modules/deletion';
import { CalendarEvent } from '../interface/calendar-event.interface';
import { CalendarMapper } from '../mapper/calendar.mapper';

Expand All @@ -26,7 +35,6 @@ describe('CalendarServiceSpec', () => {
return null;
}
});

module = await Test.createTestingModule({
providers: [
CalendarService,
Expand All @@ -38,6 +46,16 @@ describe('CalendarServiceSpec', () => {
provide: CalendarMapper,
useValue: createMock<CalendarMapper>(),
},
{
provide: Logger,
useValue: createMock<Logger>(),
},
{
provide: EventBus,
useValue: {
publish: jest.fn(),
},
},
],
}).compile();
service = module.get(CalendarService);
Expand Down Expand Up @@ -82,7 +100,7 @@ describe('CalendarServiceSpec', () => {
});

it('should throw if event cannot be found, because of invalid parameters', async () => {
const error = 'error';
const error = 'error1';
httpService.get.mockReturnValue(throwError(() => error));

// Act & Assert
Expand All @@ -91,4 +109,96 @@ describe('CalendarServiceSpec', () => {
);
});
});

describe('deleteEventsByScopeId', () => {
describe('when calling the delete events method', () => {
const setup = () => {
httpService.delete.mockReturnValue(
of(
axiosResponseFactory.build({
data: '',
status: HttpStatus.NO_CONTENT,
statusText: 'NO_CONTENT',
})
)
);
};

it('should call axios delete method', async () => {
setup();
await service.deleteEventsByScopeId('test');
expect(httpService.delete).toHaveBeenCalled();
});
});
describe('When calling the delete events method with scopeId which does not exist', () => {
it('should throw error if cannot delete a events', async () => {
const error = 'error';
httpService.delete.mockReturnValue(throwError(() => error));

await expect(service.deleteEventsByScopeId('invalid eventId')).rejects.toThrowError(
InternalServerErrorException
);
});
});
describe('when calling the delete events method', () => {
const setupError = () => {
httpService.delete.mockReturnValueOnce(
of(
axiosResponseFactory.build({
data: '',
status: HttpStatus.CONFLICT,
})
)
);
};

it('should throw error if cannot delete a events cause of invalid response Http status', async () => {
setupError();
await expect(service.deleteEventsByScopeId('scopeId')).rejects.toThrow(
new Error('invalid HTTP status code in a response from the server instead of 204')
);
});
});

describe('when calling the deleteUserEvent events method', () => {
const setup = () => {
httpService.delete.mockReturnValue(
of(
axiosResponseFactory.build({
data: '',
status: HttpStatus.NO_CONTENT,
statusText: 'NO_CONTENT',
})
)
);
const userId: EntityId = '1';

const expectedResult = DomainDeletionReportBuilder.build(DomainName.CALENDAR, [
DomainOperationReportBuilder.build(OperationType.DELETE, Number.NaN, [userId]),
]);

return {
expectedResult,
userId,
};
};

it('should call service.deleteEventsByScopeId with userId', async () => {
const { userId } = setup();
const spy = jest.spyOn(service, 'deleteEventsByScopeId');

await service.deleteUserData(userId);

expect(spy).toHaveBeenCalledWith(userId);
});

it('should return domainOperation object with information about deleted user', async () => {
const { expectedResult, userId } = setup();

const result = await service.deleteUserData(userId);

expect(result).toEqual(expectedResult);
});
});
});
});
86 changes: 83 additions & 3 deletions apps/server/src/infra/calendar/service/calendar.service.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,85 @@
import { Configuration } from '@hpi-schul-cloud/commons/lib';
import { HttpService } from '@nestjs/axios';
import { Injectable, InternalServerErrorException } from '@nestjs/common';
import { EntityId } from '@shared/domain/types';
import { ErrorUtils } from '@src/core/error/utils';
import { AxiosRequestConfig, AxiosResponse } from 'axios';
import { firstValueFrom, Observable } from 'rxjs';
import { URL, URLSearchParams } from 'url';
import { Logger } from '@src/core/logger';
import { EventBus, IEventHandler } from '@nestjs/cqrs';
import {
DataDeletedEvent,
DataDeletionDomainOperationLoggable,
DeletionService,
DomainDeletionReport,
DomainDeletionReportBuilder,
DomainName,
DomainOperationReportBuilder,
OperationType,
StatusModel,
UserDeletedEvent,
} from '@src/modules/deletion';
import { EntityId } from '@shared/domain/types';
import { CalendarEventDto } from '../dto/calendar-event.dto';
import { CalendarEvent } from '../interface/calendar-event.interface';
import { CalendarMapper } from '../mapper/calendar.mapper';

@Injectable()
export class CalendarService {
export class CalendarService implements DeletionService, IEventHandler<UserDeletedEvent> {
private readonly baseURL: string;

private readonly timeoutMs: number;

constructor(private readonly httpService: HttpService, private readonly calendarMapper: CalendarMapper) {
constructor(
private readonly httpService: HttpService,
private readonly calendarMapper: CalendarMapper,
private readonly logger: Logger,
private readonly eventBus: EventBus
) {
this.logger.setContext(CalendarService.name);
this.baseURL = Configuration.get('CALENDAR_URI') as string;
this.timeoutMs = Configuration.get('REQUEST_OPTION__TIMEOUT_MS') as number;
}

public async handle({ deletionRequestId, targetRefId }: UserDeletedEvent): Promise<void> {
const dataDeleted = await this.deleteUserData(targetRefId);
await this.eventBus.publish(new DataDeletedEvent(deletionRequestId, dataDeleted));
}

public async deleteUserData(userId: EntityId): Promise<DomainDeletionReport> {
this.logger.info(
new DataDeletionDomainOperationLoggable(
'Deleting data from Calendar Service',
DomainName.CALENDAR,
userId,
StatusModel.PENDING
)
);

if (!userId) {
throw new InternalServerErrorException('User id is missing');
}

await this.deleteEventsByScopeId(userId);

const result = DomainDeletionReportBuilder.build(DomainName.CALENDAR, [
DomainOperationReportBuilder.build(OperationType.DELETE, Number.NaN, [userId]),
]);

this.logger.info(
new DataDeletionDomainOperationLoggable(
'Successfully removed user data from Calendar Service',
DomainName.CALENDAR,
userId,
StatusModel.FINISHED,
0,
Number.NaN
)
);

return result;
}

async findEvent(userId: EntityId, eventId: EntityId): Promise<CalendarEventDto> {
const params = new URLSearchParams();
params.append('event-id', eventId);
Expand All @@ -43,6 +102,27 @@ export class CalendarService {
});
}

async deleteEventsByScopeId(scopeId: EntityId): Promise<void> {
const request = this.httpService.delete(`/scopes/${scopeId}`, {
headers: {
Authorization: scopeId,
Accept: 'Application/json',
},
timeout: this.timeoutMs,
});

const resp = await firstValueFrom(request).catch((error) => {
throw new InternalServerErrorException(
null,
ErrorUtils.createHttpExceptionOptions(error, 'CalendarService:findEvent')
);
});

if (resp.status !== 204) {
throw new Error(`invalid HTTP status code in a response from the server instead of 204`);
}
}

private get(
path: string,
queryParams: URLSearchParams,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@ export const enum DomainName {
USER = 'user',
SUBMISSIONS = 'submissions',
NEWS = 'news',
CALENDAR = 'calendar',
}

0 comments on commit a04ef30

Please sign in to comment.