Skip to content

Commit

Permalink
N21-2103 adapt vidis sync code to use vidis client
Browse files Browse the repository at this point in the history
  • Loading branch information
GordonNicholasCap committed Dec 20, 2024
1 parent 2fe346b commit 650cb18
Show file tree
Hide file tree
Showing 26 changed files with 482 additions and 581 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ data:
NODE_OPTIONS: "--max-old-space-size=3072"
NEST_LOG_LEVEL: "error"
EXIT_ON_ERROR: "true"
VIDIS_API_CLIENT_BASE_URL: "{{ VIDIS_API_CLIENT_BASE_URL }}"
2 changes: 0 additions & 2 deletions apps/server/src/infra/sync/media-licenses/response/index.ts

This file was deleted.

This file was deleted.

This file was deleted.

1 change: 1 addition & 0 deletions apps/server/src/infra/sync/media-licenses/service/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { VidisSyncService } from './vidis-sync.service';
export { VidisFetchService } from './vidis-fetch.service';
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
import { DefaultEncryptionService, EncryptionService, SymetricKeyEncryptionService } from '@infra/encryption';
import { IDMBetreiberApiInterface, PageOfferDTO, VidisClientFactory } from '@infra/vidis-client';
import { MediaSourceDataFormat } from '@modules/media-source';
import { MediaSourceBasicAuthConfig } from '@modules/media-source/domain';
import { MediaSourceBasicAuthConfigNotFoundLoggableException } from '@modules/media-source/loggable';
import { mediaSourceFactory } from '@modules/media-source/testing';
import { AxiosErrorLoggable } from '@src/core/error/loggable';
import { axiosErrorFactory, axiosResponseFactory } from '@shared/testing';
import { Test, TestingModule } from '@nestjs/testing';
import { createMock, DeepMocked } from '@golevelup/ts-jest';
import { AxiosResponse } from 'axios';
import { vidisPageOfferFactory } from '../testing';
import { VidisFetchService } from './vidis-fetch.service';

describe(VidisFetchService.name, () => {
let module: TestingModule;
let service: VidisFetchService;
let vidisClientFactory: DeepMocked<VidisClientFactory>;
let encryptionService: DeepMocked<SymetricKeyEncryptionService>;

beforeAll(async () => {
module = await Test.createTestingModule({
providers: [
VidisFetchService,
{
provide: VidisClientFactory,
useValue: createMock<VidisClientFactory>(),
},
{
provide: DefaultEncryptionService,
useValue: createMock<EncryptionService>(),
},
],
}).compile();

service = module.get(VidisFetchService);
vidisClientFactory = module.get(VidisClientFactory);
encryptionService = module.get(DefaultEncryptionService);
});

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

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

describe('getOfferItemsFromVidis', () => {
describe('when the media source has basic auth config', () => {
describe('when vidis returns the offer items successfully', () => {
const setup = () => {
const mediaSource = mediaSourceFactory.withBasicAuthConfig().build();

const axiosResponse = axiosResponseFactory.build({
data: vidisPageOfferFactory.build(),
}) as AxiosResponse<PageOfferDTO>;

const vidisApiClientMock = createMock<IDMBetreiberApiInterface>();
vidisApiClientMock.getActivatedOffersByRegion.mockResolvedValueOnce(axiosResponse);
vidisClientFactory.createVidisClient.mockReturnValueOnce(vidisApiClientMock);

const decryptedUsername = 'un-decrypted';
const decryptedPassword = 'pw-decrypted';
encryptionService.decrypt.mockReturnValueOnce(decryptedUsername);
encryptionService.decrypt.mockReturnValueOnce(decryptedPassword);

return {
mediaSource,
vidisOfferItems: axiosResponse.data.items,
decryptedUsername,
decryptedPassword,
vidisApiClientMock,
};
};

it('should return the vidis offer items', async () => {
const { mediaSource, vidisOfferItems } = setup();

const result = await service.getOfferItemsFromVidis(mediaSource);

expect(result).toEqual(vidisOfferItems);
});

it('should decrypt the credentials from basic auth config', async () => {
const { mediaSource } = setup();

await service.getOfferItemsFromVidis(mediaSource);

expect(encryptionService.decrypt).toBeCalledTimes(2);
expect(encryptionService.decrypt).toBeCalledWith(mediaSource.basicAuthConfig?.username);
expect(encryptionService.decrypt).toBeCalledWith(mediaSource.basicAuthConfig?.password);
});

it('should create a vidis api client', async () => {
const { mediaSource, decryptedUsername, decryptedPassword } = setup();

await service.getOfferItemsFromVidis(mediaSource);

expect(vidisClientFactory.createVidisClient).toBeCalledWith({
username: decryptedUsername,
password: decryptedPassword,
});
});

it('should call the vidis endpoint for activated offer items', async () => {
const { mediaSource, vidisApiClientMock } = setup();

await service.getOfferItemsFromVidis(mediaSource);

expect(vidisApiClientMock.getActivatedOffersByRegion).toBeCalledWith('test-region');
});
});

describe('when vidis returns the no offer items', () => {
const setup = () => {
const mediaSource = mediaSourceFactory.withBasicAuthConfig().build();

const axiosResponse = axiosResponseFactory.build({
data: vidisPageOfferFactory.build({ items: undefined }),
}) as AxiosResponse<PageOfferDTO>;

const vidisApiClientMock = createMock<IDMBetreiberApiInterface>();
vidisApiClientMock.getActivatedOffersByRegion.mockResolvedValueOnce(axiosResponse);
vidisClientFactory.createVidisClient.mockReturnValueOnce(vidisApiClientMock);

const basicAuth = mediaSource.basicAuthConfig as MediaSourceBasicAuthConfig;
encryptionService.decrypt.mockReturnValueOnce(basicAuth.username);
encryptionService.decrypt.mockReturnValueOnce(basicAuth.password);

return {
mediaSource,
};
};

it('should return an empty array', async () => {
const { mediaSource } = setup();

const result = await service.getOfferItemsFromVidis(mediaSource);

expect(result.length).toEqual(0);
});
});

describe('when an axios error is thrown', () => {
const setup = () => {
const mediaSource = mediaSourceFactory.withBasicAuthConfig().build();

const axiosError = axiosErrorFactory.build();

const vidisApiClientMock = createMock<IDMBetreiberApiInterface>();
vidisApiClientMock.getActivatedOffersByRegion.mockRejectedValueOnce(axiosError);
vidisClientFactory.createVidisClient.mockReturnValueOnce(vidisApiClientMock);

const basicAuth = mediaSource.basicAuthConfig as MediaSourceBasicAuthConfig;
encryptionService.decrypt.mockReturnValueOnce(basicAuth.username);
encryptionService.decrypt.mockReturnValueOnce(basicAuth.password);

return {
mediaSource,
axiosError,
};
};

it('should throw a AxiosErrorLoggable', async () => {
const { mediaSource, axiosError } = setup();

const promise = service.getOfferItemsFromVidis(mediaSource);

await expect(promise).rejects.toThrow(new AxiosErrorLoggable(axiosError, 'VIDIS_GET_OFFER_ITEMS_FAILED'));
});
});

describe('when an unknown error is thrown', () => {
const setup = () => {
const mediaSource = mediaSourceFactory.withBasicAuthConfig().build();

const unknownError = new Error();

const vidisApiClientMock = createMock<IDMBetreiberApiInterface>();
vidisApiClientMock.getActivatedOffersByRegion.mockRejectedValueOnce(unknownError);
vidisClientFactory.createVidisClient.mockReturnValueOnce(vidisApiClientMock);

const basicAuth = mediaSource.basicAuthConfig as MediaSourceBasicAuthConfig;
encryptionService.decrypt.mockReturnValueOnce(basicAuth.username);
encryptionService.decrypt.mockReturnValueOnce(basicAuth.password);

return {
mediaSource,
unknownError,
};
};

it('should throw the unknown error', async () => {
const { mediaSource, unknownError } = setup();

const promise = service.getOfferItemsFromVidis(mediaSource);

await expect(promise).rejects.toThrow(unknownError);
});
});
});

describe('when the media source has no basic auth config ', () => {
const setup = () => {
const mediaSource = mediaSourceFactory.build({ basicAuthConfig: undefined });

return { mediaSource };
};

it('should throw an MediaSourceBasicAuthConfigNotFoundLoggableException', async () => {
const { mediaSource } = setup();

const promise = service.getOfferItemsFromVidis(mediaSource);

await expect(promise).rejects.toThrow(
new MediaSourceBasicAuthConfigNotFoundLoggableException(mediaSource.id, MediaSourceDataFormat.VIDIS)
);
});
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { Inject, Injectable } from '@nestjs/common';
import { DefaultEncryptionService, EncryptionService } from '@infra/encryption';
import { VidisClientFactory, IDMBetreiberApiInterface, PageOfferDTO, OfferDTO } from '@infra/vidis-client';
import { MediaSource, MediaSourceDataFormat } from '@modules/media-source';
import { MediaSourceBasicAuthConfigNotFoundLoggableException } from '@modules/media-source/loggable';
import { AxiosResponse, isAxiosError } from 'axios';
import { AxiosErrorLoggable } from '@src/core/error/loggable';

@Injectable()
export class VidisFetchService {
constructor(
private readonly vidisClientFactory: VidisClientFactory,
@Inject(DefaultEncryptionService) private readonly encryptionService: EncryptionService
) {}

public async getOfferItemsFromVidis(mediaSource: MediaSource): Promise<OfferDTO[]> {
if (!mediaSource.basicAuthConfig) {
throw new MediaSourceBasicAuthConfigNotFoundLoggableException(mediaSource.id, MediaSourceDataFormat.VIDIS);
}

const vidisClient: IDMBetreiberApiInterface = this.vidisClientFactory.createVidisClient();

// TODO: env var
const region = 'test-region';
const decryptedUsername = this.encryptionService.decrypt(mediaSource.basicAuthConfig.username);
const decryptedPassword = this.encryptionService.decrypt(mediaSource.basicAuthConfig.password);
const basicAuthEncoded = btoa(`${decryptedUsername}:${decryptedPassword}`);

try {
const axiosResponse: AxiosResponse<PageOfferDTO> = await vidisClient.getActivatedOffersByRegion(
region,
undefined,
undefined,
{
headers: { Authorization: `Basic ${basicAuthEncoded}` },
}
);
const offerItems: OfferDTO[] = axiosResponse.data.items ?? [];

return offerItems;
} catch (error: unknown) {
if (isAxiosError(error)) {
throw new AxiosErrorLoggable(error, 'VIDIS_GET_OFFER_ITEMS_FAILED');
} else {
throw error;
}
}
}
}
Loading

0 comments on commit 650cb18

Please sign in to comment.