diff --git a/apps/shared/src/index.ts b/apps/shared/src/index.ts index 36edf136298f8c16bbcaea2801132030521a0951..3f9cf437a33fe63a98811a6df3f5139accbecc03 100644 --- a/apps/shared/src/index.ts +++ b/apps/shared/src/index.ts @@ -18,5 +18,6 @@ export * from './dto/pagination-params.dto.js'; export * from './dto/multitenancy-params.dto.js'; export * from './modules/health/health.module.js'; +export * from './modules/tsa/index.js'; export * from './interceptors/response-format.interceptor.js'; diff --git a/apps/shared/src/modules/tsa/__tests__/tsa.service.spec.ts b/apps/shared/src/modules/tsa/__tests__/tsa.service.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..9ee7c3e209be7439830d57628fbcbe12887be61f --- /dev/null +++ b/apps/shared/src/modules/tsa/__tests__/tsa.service.spec.ts @@ -0,0 +1,60 @@ +import type { TestingModule } from '@nestjs/testing'; +import type { AxiosResponse } from 'axios' assert { 'resolution-mode': 'require' }; + +import { HttpService } from '@nestjs/axios'; +import { Test } from '@nestjs/testing'; +import { of } from 'rxjs'; + +import { TSAService } from '../tsa.service.js'; + +describe('TSA Service', () => { + const httpServiceMock = { + post: jest.fn(), + } as unknown as jest.Mocked<HttpService>; + + let service: TSAService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ + { provide: HttpService, useValue: httpServiceMock }, + TSAService, + ], + }).compile(); + + service = module.get<TSAService>(TSAService); + + jest.clearAllMocks(); + }); + + it('should do something', () => { + expect(service).toBeDefined(); + expect(service).toBeInstanceOf(TSAService); + }); + + it('should evaluate a policy', async () => { + const expectedResult = {}; + const expectedResponse = { + data: expectedResult, + } as AxiosResponse; + + httpServiceMock.post.mockReturnValueOnce(of(expectedResponse)); + + const result = await service.evaluatePolicy('policies/xfsc/didresolve/1.0'); + + expect(result).toStrictEqual(expectedResult); + }); + + it('should handle string response', async () => { + const expectedResult = '{}'; + const expectedResponse = { + data: expectedResult, + } as AxiosResponse; + + httpServiceMock.post.mockReturnValueOnce(of(expectedResponse)); + + const result = await service.evaluatePolicy('policies/xfsc/didresolve/1.0'); + + expect(result).toEqual(JSON.parse(expectedResult)); + }); +}); diff --git a/apps/shared/src/modules/tsa/index.ts b/apps/shared/src/modules/tsa/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..14af3a7dfbb0b73ccfc9e97bb7d3304a6421ad55 --- /dev/null +++ b/apps/shared/src/modules/tsa/index.ts @@ -0,0 +1,2 @@ +export * from './tsa.module.js'; +export * from './tsa.service.js'; diff --git a/apps/shared/src/modules/tsa/interfaces/tsa-module-options.interface.ts b/apps/shared/src/modules/tsa/interfaces/tsa-module-options.interface.ts new file mode 100644 index 0000000000000000000000000000000000000000..b06a2385d00ae0e1239e4daa7b89baedcc297c8f --- /dev/null +++ b/apps/shared/src/modules/tsa/interfaces/tsa-module-options.interface.ts @@ -0,0 +1,3 @@ +export interface TSAModuleOptions { + tsaBaseUrl: string; +} diff --git a/apps/shared/src/modules/tsa/tsa.module-definition.ts b/apps/shared/src/modules/tsa/tsa.module-definition.ts new file mode 100644 index 0000000000000000000000000000000000000000..086768bb1b7a72a5e8615ed2115884fb4fc26725 --- /dev/null +++ b/apps/shared/src/modules/tsa/tsa.module-definition.ts @@ -0,0 +1,10 @@ +import type { TSAModuleOptions } from './interfaces/tsa-module-options.interface.js'; + +import { ConfigurableModuleBuilder } from '@nestjs/common'; + +export const { + ConfigurableModuleClass, + MODULE_OPTIONS_TOKEN, + OPTIONS_TYPE, + ASYNC_OPTIONS_TYPE, +} = new ConfigurableModuleBuilder<TSAModuleOptions>().build(); diff --git a/apps/shared/src/modules/tsa/tsa.module.ts b/apps/shared/src/modules/tsa/tsa.module.ts new file mode 100644 index 0000000000000000000000000000000000000000..06ed0c3a683b30faf7f16803c23a57415dbf7c1c --- /dev/null +++ b/apps/shared/src/modules/tsa/tsa.module.ts @@ -0,0 +1,24 @@ +import type { TSAModuleOptions } from './interfaces/tsa-module-options.interface.js'; + +import { HttpModule } from '@nestjs/axios'; +import { Module } from '@nestjs/common'; + +import { + ConfigurableModuleClass, + MODULE_OPTIONS_TOKEN, +} from './tsa.module-definition.js'; +import { TSAService } from './tsa.service.js'; + +@Module({ + imports: [ + HttpModule.registerAsync({ + inject: [MODULE_OPTIONS_TOKEN], + useFactory: (moduleOptions: TSAModuleOptions) => ({ + baseURL: moduleOptions.tsaBaseUrl, + }), + }), + ], + providers: [TSAService], + exports: [TSAService], +}) +export class TSAModule extends ConfigurableModuleClass {} diff --git a/apps/shared/src/modules/tsa/tsa.service.ts b/apps/shared/src/modules/tsa/tsa.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..49dce0dba402e1ccfd04bb644317593c572d7ea4 --- /dev/null +++ b/apps/shared/src/modules/tsa/tsa.service.ts @@ -0,0 +1,30 @@ +import { HttpService } from '@nestjs/axios'; +import { Injectable } from '@nestjs/common'; +import { firstValueFrom } from 'rxjs'; + +@Injectable() +export class TSAService { + public constructor(private readonly http: HttpService) {} + + /** + * Evaluates the given policy. + * The policy should be specified in the format `repository/group/policy/version`. + * + * @param policy - The policy to evaluate. The format is `repository/group/policy/version`, + * @example `policies/xfsc/didresolve/1.0` + */ + public async evaluatePolicy( + policy: `${string}/${string}/${string}/${string}`, + input?: Record<string, unknown>, + ) { + const { data } = await firstValueFrom( + this.http.post(`/policy/${policy}/evaluation`, input), + ); + + if (typeof data === 'string') { + return JSON.parse(data); + } + + return data; + } +}