From e570bd7995a383b51d0b81199a2d259cdda5cb8a Mon Sep 17 00:00:00 2001
From: Konstantin Tsabolov <konstantin.tsabolov@spherity.com>
Date: Mon, 29 Jan 2024 19:11:27 +0100
Subject: [PATCH] feat: add TSA module to shared lib

---
 apps/shared/src/index.ts                      |  1 +
 .../modules/tsa/__tests__/tsa.service.spec.ts | 60 +++++++++++++++++++
 apps/shared/src/modules/tsa/index.ts          |  2 +
 .../tsa-module-options.interface.ts           |  3 +
 .../src/modules/tsa/tsa.module-definition.ts  | 10 ++++
 apps/shared/src/modules/tsa/tsa.module.ts     | 24 ++++++++
 apps/shared/src/modules/tsa/tsa.service.ts    | 30 ++++++++++
 7 files changed, 130 insertions(+)
 create mode 100644 apps/shared/src/modules/tsa/__tests__/tsa.service.spec.ts
 create mode 100644 apps/shared/src/modules/tsa/index.ts
 create mode 100644 apps/shared/src/modules/tsa/interfaces/tsa-module-options.interface.ts
 create mode 100644 apps/shared/src/modules/tsa/tsa.module-definition.ts
 create mode 100644 apps/shared/src/modules/tsa/tsa.module.ts
 create mode 100644 apps/shared/src/modules/tsa/tsa.service.ts

diff --git a/apps/shared/src/index.ts b/apps/shared/src/index.ts
index 36edf13..3f9cf43 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 0000000..9ee7c3e
--- /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 0000000..14af3a7
--- /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 0000000..b06a238
--- /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 0000000..086768b
--- /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 0000000..06ed0c3
--- /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 0000000..49dce0d
--- /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;
+  }
+}
-- 
GitLab