diff --git a/services/identity-service/package.json b/services/identity-service/package.json index 87b9f15a6..42a2e7529 100644 --- a/services/identity-service/package.json +++ b/services/identity-service/package.json @@ -94,9 +94,14 @@ "^.+\\.(t|j)s$": "ts-jest" }, "collectCoverageFrom": [ - "**/*.(t|j)s" + "**/*.(t|j)s", + "!**/*.module.ts", + "!**/main.ts" ], "coverageDirectory": "../coverage", - "testEnvironment": "node" + "testEnvironment": "node", + "moduleNameMapper": { + "^src/(.*)$": "/$1" + } } } diff --git a/services/identity-service/src/app.controller.spec.ts b/services/identity-service/src/app.controller.spec.ts new file mode 100644 index 000000000..55daa95a4 --- /dev/null +++ b/services/identity-service/src/app.controller.spec.ts @@ -0,0 +1,90 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { AppController } from './app.controller'; +import { + HealthCheckError, + HealthCheckService, + HealthIndicatorResult, + HttpHealthIndicator, + TerminusModule, +} from '@nestjs/terminus'; +import { PrismaHealthIndicator } from './utils/prisma.health'; +import { VaultHealthIndicator } from './utils/vault.health'; +import { HealthCheckExecutor } from '@nestjs/terminus/dist/health-check/health-check-executor.service'; +import { + ERROR_LOGGER, + getErrorLoggerProvider, +} from '@nestjs/terminus/dist/health-check/error-logger/error-logger.provider'; +import { getLoggerProvider, TERMINUS_LOGGER } from '@nestjs/terminus/dist/health-check/logger/logger.provider'; +import { HttpModule } from '@nestjs/axios'; +import { ServiceUnavailableException } from '@nestjs/common'; + +describe('AppController', () => { + let appController: AppController; + let prismaHealthIndicator: PrismaHealthIndicator; + let vaultHealthIndicator: VaultHealthIndicator; + + beforeAll(async () => { + const module: TestingModule = await Test.createTestingModule({ + imports: [ + HttpModule, + TerminusModule + ], + controllers: [AppController], + providers: [ + getLoggerProvider(), + getErrorLoggerProvider(), + HealthCheckExecutor, + HealthCheckError, + HealthCheckService, + HttpHealthIndicator, + { + provide: PrismaHealthIndicator, + useFactory: () => ({ + isHealthy: jest.fn(), + }), + }, + { + provide: VaultHealthIndicator, + useFactory: () => ({ + isHealthy: jest.fn(), + }), + }, + ], + }).compile(); + + appController = module.get(AppController); + prismaHealthIndicator = module.get(PrismaHealthIndicator); + vaultHealthIndicator = module.get(VaultHealthIndicator); + }); + + beforeEach(async () => { + jest.restoreAllMocks(); + }) + + describe('checkHealth', () => { + it('should return a health check result with all services healthy', async () => { + jest.spyOn(prismaHealthIndicator, 'isHealthy') + .mockResolvedValue(new Promise((resolve) => { + resolve({ db: { status: 'up' }} as HealthIndicatorResult); + })); + jest.spyOn(vaultHealthIndicator, 'isHealthy') + .mockResolvedValue(new Promise((resolve) => { + resolve({ vault: { status: 'up' }} as HealthIndicatorResult); + })); + const result = await appController.checkHealth(); + expect(result.status).toEqual('ok'); + expect(result.info.db.status).toEqual('up'); + expect(result.info.vault.status).toEqual('up'); + }); + + it('should return a health check result with one service unhealthy', async () => { + jest.spyOn(prismaHealthIndicator, 'isHealthy') + .mockRejectedValue(new HealthCheckError("Prisma health check failed", null)); + jest.spyOn(vaultHealthIndicator, 'isHealthy') + .mockResolvedValue(new Promise((resolve) => { + resolve({ vault: { status: 'up' }} as HealthIndicatorResult); + })); + await expect(appController.checkHealth()).rejects.toThrow(ServiceUnavailableException); + }); + }); +}); \ No newline at end of file diff --git a/services/identity-service/src/auth/auth.guard.spec.ts b/services/identity-service/src/auth/auth.guard.spec.ts new file mode 100644 index 000000000..8728547f4 --- /dev/null +++ b/services/identity-service/src/auth/auth.guard.spec.ts @@ -0,0 +1,81 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { AuthGuard } from './auth.guard'; +import { Reflector } from '@nestjs/core'; +import { ConfigService } from '@nestjs/config'; + +jest.mock('jwks-rsa', () => ({ + __esModule: true, + default: jest.fn(), +})); + +describe('AuthGuard', () => { + let guard: AuthGuard; + let reflector: Reflector; + let configService: ConfigService; + let originalEnv: NodeJS.ProcessEnv; + + beforeAll(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ + AuthGuard, + { + provide: Reflector, + useValue: { + get: jest.fn(), + }, + }, + { + provide: ConfigService, + useValue: { + get: jest.fn(), + }, + }, + ], + }).compile(); + + guard = module.get(AuthGuard); + reflector = module.get(Reflector); + configService = module.get(ConfigService); + }); + + beforeEach(async () => { + originalEnv = { ...process.env }; + process.env.ENABLE_AUTH = 'true'; + jest.restoreAllMocks(); + }) + + it('should be defined', () => { + expect(guard).toBeDefined(); + }); + + describe('canActivate', () => { + it('should return true if isPublic is set to true', async () => { + jest.spyOn(reflector, 'get').mockReturnValue(true); + const result = await guard.canActivate({ getHandler: jest.fn() }); + expect(result).toEqual(true); + }); + + it('should return true if ENABLE_AUTH is false', async () => { + process.env.ENABLE_AUTH = 'false'; + jest.spyOn(reflector, 'get').mockReturnValue(false); + jest.spyOn(configService, 'get').mockReturnValue('false'); + const result = await guard.canActivate({ getHandler: jest.fn() }); + expect(result).toEqual(true); + }); + + it('should return false if no Bearer token found', async () => { + jest.spyOn(reflector, 'get').mockReturnValue(false); + jest.spyOn(configService, 'get').mockReturnValue('true'); + const request = { headers: { authorization: 'InvalidToken' } }; + const result = await guard.canActivate({ getHandler: jest.fn(), switchToHttp: () => ({ getRequest: () => request }) }); + expect(result).toEqual(false); + }); + + // Add more test cases as needed to cover different scenarios + }); + + afterEach(() => { + // Restore the original process.env after the test + process.env = { ...originalEnv }; + }); +}); diff --git a/services/identity-service/src/auth/auth.guard.ts b/services/identity-service/src/auth/auth.guard.ts index ee814230e..ddb4ea669 100644 --- a/services/identity-service/src/auth/auth.guard.ts +++ b/services/identity-service/src/auth/auth.guard.ts @@ -38,7 +38,7 @@ export class AuthGuard implements CanActivate { if (process.env.ENABLE_AUTH === undefined) { this.logger.warn('ENABLE_AUTH is not set, defaulting to true'); - }; + } if (process.env.ENABLE_AUTH && process.env.ENABLE_AUTH.trim() === 'false') return true; const request = context.switchToHttp().getRequest(); diff --git a/services/identity-service/src/did/did.controller.spec.ts b/services/identity-service/src/did/did.controller.spec.ts index ec2d618ae..b006b43ba 100644 --- a/services/identity-service/src/did/did.controller.spec.ts +++ b/services/identity-service/src/did/did.controller.spec.ts @@ -8,7 +8,8 @@ import { ConfigService } from '@nestjs/config'; describe('DidController', () => { let controller: DidController; - const content = { + let content: any; + const defaultContent = { "content": [ { "alsoKnownAs": [ @@ -43,6 +44,11 @@ describe('DidController', () => { controller = module.get(DidController); }); + beforeEach(async () => { + content = JSON.parse(JSON.stringify(defaultContent)); + jest.restoreAllMocks(); + }) + it('should be defined', () => { expect(controller).toBeDefined(); }); @@ -55,6 +61,26 @@ describe('DidController', () => { expect(dids[0].verificationMethod).toBeDefined(); }); + it('should test throw DID generation Exception', async () => { + jest.spyOn((controller as any).didService, "generateDID") + .mockRejectedValue(new Error('generate failed')); + await expect(controller.generateDID(content)).rejects.toThrow(new InternalServerErrorException("generate failed")); + }); + + it('should test resolveDID', async () => { + jest.spyOn((controller as any).didService, "resolveDID") + .mockResolvedValue({ id: "1234"}); + const result = await controller.resolveDID("1234"); + expect(result.id).toEqual("1234"); + }); + + it('should test resolveWebDID', async () => { + jest.spyOn((controller as any).didService, "resolveWebDID") + .mockResolvedValue({ id: "1234"}); + const result = await controller.resolveWebDID("1234"); + expect(result.id).toEqual("1234"); + }); + it('should test bulk DID generation', async () => { const toGenerate = content; for (let i = 0; i < 10; i++) { diff --git a/services/identity-service/src/did/did.service.spec.ts b/services/identity-service/src/did/did.service.spec.ts index c154129cb..dead5270e 100644 --- a/services/identity-service/src/did/did.service.spec.ts +++ b/services/identity-service/src/did/did.service.spec.ts @@ -2,12 +2,13 @@ import { Test, TestingModule } from '@nestjs/testing'; import { DidService } from './did.service'; import { PrismaService } from '../utils/prisma.service'; import { VaultService } from '../utils/vault.service'; -import { GenerateDidDTO } from './dtos/GenerateDidRequest.dto'; +import { GenerateDidDTO, VerificationKeyType } from './dtos/GenerateDidRequest.dto'; import { ConfigService } from '@nestjs/config'; describe('DidService', () => { let service: DidService; - const doc: GenerateDidDTO = + let doc: GenerateDidDTO; + const defaultDoc: GenerateDidDTO = { "alsoKnownAs": [ "C4GT", @@ -29,17 +30,19 @@ describe('DidService', () => { "method": "C4GT" } - beforeEach(async () => { + beforeAll(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [DidService, PrismaService, VaultService, ConfigService], }).compile(); - // const app = module.createNestApplication(); - // await app.init(); - service = module.get(DidService); }); + beforeEach(async () => { + doc = JSON.parse(JSON.stringify(defaultDoc)); + jest.restoreAllMocks(); + }) + it('should be defined', () => { expect(service).toBeDefined(); }); @@ -55,8 +58,7 @@ describe('DidService', () => { }); it('should generate a DID with a default method', async () => { - const docWithoutMethod = doc; - delete docWithoutMethod.method; + delete doc.method; const result = await service.generateDID(doc); expect(result).toBeDefined(); expect(result.verificationMethod).toBeDefined(); @@ -64,6 +66,34 @@ describe('DidService', () => { expect(result.id.split(':')[1]).toEqual('rcw'); }); + it('should generate a web DID with a given base url and verification key', async () => { + doc.method = "web"; + doc.webDidBaseUrl = "https://registry.dev.example.com/identity"; + doc.keyPairType = VerificationKeyType.Ed25519VerificationKey2018; + const result = await service.generateDID(doc); + expect(result).toBeDefined(); + expect(result.id).toMatch(/did:web:registry.dev.example.com:*/i) + expect(result.verificationMethod).toBeDefined(); + expect(result.verificationMethod[0].type).toStrictEqual(VerificationKeyType.Ed25519VerificationKey2018.toString()); + }); + + it('should generate a web DID with RsaVerificationKey2018 verification key', async () => { + doc.method = "abc"; + doc.keyPairType = VerificationKeyType.RsaVerificationKey2018; + const result = await service.generateDID(doc); + expect(result).toBeDefined(); + expect(result.verificationMethod).toBeDefined(); + expect(result.verificationMethod[0].type).toStrictEqual(VerificationKeyType.RsaVerificationKey2018.toString()); + }); + + it('should generate a DID with a given ID', async () => { + doc.method = "web"; + doc.id = "did:web:abc.com:given:1234" + const result = await service.generateDID(doc); + expect(result).toBeDefined(); + expect(result.id).toMatch("did:web:abc.com:given:1234") + }); + it('resolve a DID', async () => { const result = await service.generateDID(doc); const didToResolve = result.id; @@ -71,6 +101,19 @@ describe('DidService', () => { expect(resolvedDid).toBeDefined(); expect(resolvedDid.id).toEqual(didToResolve); expect(resolvedDid).toEqual(result); + await expect(service.resolveDID("did:abc:efg:hij")).rejects + .toThrow("DID: did:abc:efg:hij not found"); + }); + + it('resolve a web DID for given id', async () => { + service.webDidPrefix = "did:web:abc.com:resolveweb:"; + doc.method = "web"; + const result = await service.generateDID(doc); + const didToResolve = result.id; + const resolvedDid = await service.resolveWebDID(didToResolve.split(service.webDidPrefix)[1]); + expect(resolvedDid).toBeDefined(); + expect(resolvedDid.id).toEqual(didToResolve); + expect(resolvedDid).toEqual(result); }); it("generate web did id test", () => { @@ -104,4 +147,11 @@ describe('DidService', () => { expect(() => service.getWebDidIdForId("abc")) .toThrow("Web did base url not found"); }); + + // it("throw exception when signature algorithm is not found", () => { + // service.signingAlgorithm = "EdDsa"; + // expect(() => service.generateDID(doc)) + // .toThrow("Signature algorithm not found"); + // }); + }); diff --git a/services/identity-service/src/did/did.service.ts b/services/identity-service/src/did/did.service.ts index 8e3012664..73db04f23 100644 --- a/services/identity-service/src/did/did.service.ts +++ b/services/identity-service/src/did/did.service.ts @@ -83,7 +83,7 @@ export class DidService { await this.init(); } if(!this.keys[signingAlgorithm]) { - throw new NotFoundException("Signature suite not supported") + throw new NotFoundException("Signature algorithm not found") } return this.keys[signingAlgorithm]; } @@ -191,7 +191,9 @@ export class DidService { if(!artifact && id?.startsWith("did:web") && !id?.startsWith(this.webDidPrefix)) { try { - return (await this.didResolver.resolve(id)).didDocument; + let doc = (await this.didResolver.resolve(id)).didDocument; + if(!doc) throw new Error("DID document is null"); + return doc; } catch (err) { Logger.error(`Error fetching DID: ${id} from web, ${err}`); throw new InternalServerErrorException(`Error fetching DID: ${id} from web`); diff --git a/services/identity-service/src/did/dtos/GenerateDidRequest.dto.ts b/services/identity-service/src/did/dtos/GenerateDidRequest.dto.ts index 5dc9a0aaa..c39200108 100644 --- a/services/identity-service/src/did/dtos/GenerateDidRequest.dto.ts +++ b/services/identity-service/src/did/dtos/GenerateDidRequest.dto.ts @@ -2,7 +2,7 @@ import { ApiProperty } from '@nestjs/swagger'; const { Service } = require('did-resolver'); type Service = typeof Service; -enum VerificationKeyType { +export enum VerificationKeyType { Ed25519VerificationKey2020 = "Ed25519VerificationKey2020", Ed25519VerificationKey2018 = "Ed25519VerificationKey2018", RsaVerificationKey2018 = "RsaVerificationKey2018" diff --git a/services/identity-service/src/did/dtos/GenerateDidResponse.dto.ts b/services/identity-service/src/did/dtos/GenerateDidResponse.dto.ts deleted file mode 100644 index 9648dc6b0..000000000 --- a/services/identity-service/src/did/dtos/GenerateDidResponse.dto.ts +++ /dev/null @@ -1 +0,0 @@ -export class GenerateDidResponse {} diff --git a/services/identity-service/src/utils/prisma.health.spec.ts b/services/identity-service/src/utils/prisma.health.spec.ts new file mode 100644 index 000000000..f9ce0391d --- /dev/null +++ b/services/identity-service/src/utils/prisma.health.spec.ts @@ -0,0 +1,40 @@ + +import { PrismaService } from './prisma.service'; +import { HealthCheckError } from '@nestjs/terminus'; +import { PrismaHealthIndicator } from 'src/utils/prisma.health'; + +describe('PrismaHealthIndicator', () => { + let prismaHealthIndicator: PrismaHealthIndicator; + let prismaService: PrismaService; + + beforeAll(() => { + prismaService = new PrismaService(); // Mock or use a real instance of PrismaService here + prismaHealthIndicator = new PrismaHealthIndicator(prismaService); + }); + + beforeEach(() => { + jest.restoreAllMocks(); + }) + + it('should be defined', () => { + expect(prismaHealthIndicator).toBeDefined(); + }); + + describe('isHealthy', () => { + it('should return healthy result if PrismaService query is successful', async () => { + jest.spyOn(prismaService, '$queryRaw').mockResolvedValueOnce([1]); // Mock successful query response + + const result = await prismaHealthIndicator.isHealthy('prisma'); + + expect(result).toEqual({ prisma: { status: 'up' } }); + expect(prismaService.$queryRaw).toHaveBeenCalledWith(expect.anything()); // Assert that $queryRaw was called + }); + + it('should throw HealthCheckError if PrismaService query fails', async () => { + jest.spyOn(prismaService, '$queryRaw').mockRejectedValueOnce(new Error('Prisma query failed')); // Mock failed query response + + await expect(prismaHealthIndicator.isHealthy('prisma')).rejects.toThrowError(HealthCheckError); + expect(prismaService.$queryRaw).toHaveBeenCalledWith(expect.anything()); // Assert that $queryRaw was called + }); + }); +}); diff --git a/services/identity-service/src/utils/prisma.service.spec.ts b/services/identity-service/src/utils/prisma.service.spec.ts new file mode 100644 index 000000000..facab91e3 --- /dev/null +++ b/services/identity-service/src/utils/prisma.service.spec.ts @@ -0,0 +1,36 @@ +import { INestApplication } from '@nestjs/common'; +import { PrismaService } from './prisma.service'; + +describe('PrismaService', () => { + let prismaService: PrismaService; + + beforeEach(() => { + prismaService = new PrismaService(); + }); + + afterEach(async () => { + await prismaService.$disconnect(); // Disconnect after each test to avoid connection leaks + }); + + it('should be defined', () => { + expect(prismaService).toBeDefined(); + }); + + describe('onModuleInit', () => { + it('should connect to the database', async () => { + await expect(prismaService.onModuleInit()).resolves.not.toThrow(); + }); + }); + + describe('enableShutdownHooks', () => { + it('should enable shutdown hooks for the application', async () => { + const mockApp = { close: jest.fn() } as unknown as INestApplication; // Mock INestApplication methods + await prismaService.enableShutdownHooks(mockApp); + + // @ts-ignore + process.emit("beforeExit"); + + expect(mockApp.close).toHaveBeenCalled(); // Assert that app.close() was called + }); + }); +}); diff --git a/services/identity-service/src/utils/vault.health.spec.ts b/services/identity-service/src/utils/vault.health.spec.ts new file mode 100644 index 000000000..9329dff3d --- /dev/null +++ b/services/identity-service/src/utils/vault.health.spec.ts @@ -0,0 +1,49 @@ +import { InternalServerErrorException, NotFoundException } from '@nestjs/common'; +import { HealthCheckError } from '@nestjs/terminus'; +import { VaultService } from './vault.service'; +import { VaultHealthIndicator } from 'src/utils/vault.health'; + +describe('VaultHealthIndicator', () => { + let vaultHealthIndicator: VaultHealthIndicator; + let vaultService: VaultService; + + beforeEach(() => { + vaultService = new VaultService(); // Mock or use a real instance of VaultService here + vaultHealthIndicator = new VaultHealthIndicator(vaultService); + }); + + it('should be defined', () => { + expect(vaultHealthIndicator).toBeDefined(); + }); + + describe('isHealthy', () => { + it('should return healthy result if Vault status is initialized and unsealed', async () => { + jest.spyOn(vaultService, 'checkStatus').mockResolvedValueOnce({ + status: { sealed: false, initialized: true }, + vault_config: {} + }); // Mock successful status check + + const result = await vaultHealthIndicator.isHealthy('vault'); + + expect(result).toEqual({ vault: { status: 'up' } }); + expect(vaultService.checkStatus).toHaveBeenCalled(); // Assert that checkStatus was called + }); + + it('should throw InternalServerErrorException if Vault is sealed or not initialized', async () => { + jest.spyOn(vaultService, 'checkStatus').mockResolvedValueOnce({ + status: { sealed: true, initialized: false }, + vault_config: {} + }); // Mock sealed and not initialized status + + await expect(vaultHealthIndicator.isHealthy('vault')).rejects.toThrow(HealthCheckError); + expect(vaultService.checkStatus).toHaveBeenCalled(); // Assert that checkStatus was called + }); + + it('should throw HealthCheckError if Vault status check fails', async () => { + jest.spyOn(vaultService, 'checkStatus').mockRejectedValueOnce(new NotFoundException()); // Mock status check failure + + await expect(vaultHealthIndicator.isHealthy('vault')).rejects.toThrowError(HealthCheckError); + expect(vaultService.checkStatus).toHaveBeenCalled(); // Assert that checkStatus was called + }); + }); +}); diff --git a/services/identity-service/src/utils/vault.health.ts b/services/identity-service/src/utils/vault.health.ts index 4e7c9b8b4..ccdc4c5c7 100644 --- a/services/identity-service/src/utils/vault.health.ts +++ b/services/identity-service/src/utils/vault.health.ts @@ -13,7 +13,7 @@ export class VaultHealthIndicator extends HealthIndicator { if (resp.status.sealed === false && resp.status.initialized === true) return this.getStatus(key, true); throw new InternalServerErrorException('Vault is not initialized or sealed'); } catch (err) { - throw new HealthCheckError("Prisma health check failed", err); + throw new HealthCheckError("Vault health check failed", err); } } } \ No newline at end of file diff --git a/services/identity-service/src/utils/vault.service.spec.ts b/services/identity-service/src/utils/vault.service.spec.ts new file mode 100644 index 000000000..b3c8ab6d8 --- /dev/null +++ b/services/identity-service/src/utils/vault.service.spec.ts @@ -0,0 +1,96 @@ +import { InternalServerErrorException, Logger } from '@nestjs/common'; +import { VaultService } from './vault.service'; +const Vault = require('hashi-vault-js'); + +jest.mock('hashi-vault-js'); + +describe('VaultService', () => { + let vaultService: VaultService; + let mockVault: any; + + beforeEach(() => { + vaultService = new VaultService(); + mockVault = new Vault(); + (vaultService as any).vault = mockVault; + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + it('should be defined', () => { + expect(vaultService).toBeDefined(); + }); + + describe('checkStatus', () => { + it('should return status and vault_config', async () => { + const status = { sealed: false, initialized: true }; + const vault_config = { example: 'config' }; + jest.spyOn(mockVault, 'healthCheck').mockResolvedValueOnce(status); + jest.spyOn(mockVault, 'readKVEngineConfig').mockResolvedValueOnce(vault_config); + + const result = await vaultService.checkStatus(); + + expect(result).toEqual({ status, vault_config }); + expect(mockVault.healthCheck).toHaveBeenCalled(); + expect(mockVault.readKVEngineConfig).toHaveBeenCalledWith((vaultService as any).token); + }); + + it('should handle errors and return default values', async () => { + jest.spyOn(mockVault, 'healthCheck').mockRejectedValueOnce(new Error('Health check failed')); + jest.spyOn(mockVault, 'readKVEngineConfig').mockRejectedValueOnce(new Error('Config read failed')); + const loggerErrorSpy = jest.spyOn(Logger, 'error').mockImplementation(); + + const result = await vaultService.checkStatus(); + + expect(result).toEqual({ status: undefined, vault_config: undefined }); + expect(loggerErrorSpy).toHaveBeenCalledTimes(2); + expect(loggerErrorSpy).toHaveBeenCalledWith('Error in checking vault status: Error: Health check failed'); + expect(loggerErrorSpy).toHaveBeenCalledWith('Error in checking vault config: Error: Config read failed'); + }); + }); + + describe('writePvtKey', () => { + it('should write private key to vault', async () => { + const secret = { key: 'value' }; + const name = 'example'; + const path = 'path/to/secret'; + + jest.spyOn(mockVault, 'createKVSecret').mockResolvedValueOnce(secret); + + const result = await vaultService.writePvtKey(secret, name, path); + + expect(result).toEqual(secret); + expect(mockVault.createKVSecret).toHaveBeenCalledWith((vaultService as any).token, `${path}/${name}`, secret); + }); + + it('should handle errors and throw InternalServerErrorException', async () => { + const secret = { key: 'value' }; + const name = 'example'; + const path = 'path/to/secret'; + + jest.spyOn(mockVault, 'createKVSecret').mockRejectedValueOnce(new Error('Write failed')); + const loggerErrorSpy = jest.spyOn(Logger, 'error').mockImplementation(); + + await expect(vaultService.writePvtKey(secret, name, path)).rejects.toThrowError(InternalServerErrorException); + + expect(mockVault.createKVSecret).toHaveBeenCalledWith((vaultService as any).token, `${path}/${name}`, secret); + expect(loggerErrorSpy).toHaveBeenCalledWith(new Error('Write failed')); + }); + }); + + describe('readPvtKey', () => { + it('should read private key from vault', async () => { + const name = 'example'; + const path = 'path/to/secret'; + const data = { key: 'value' }; + + jest.spyOn(mockVault, 'readKVSecret').mockResolvedValueOnce({ data }); + + const result = await vaultService.readPvtKey(name, path); + + expect(result).toEqual(data); + expect(mockVault.readKVSecret).toHaveBeenCalledWith((vaultService as any).token, `${path}/${name}`); + }); + }); +}); diff --git a/services/identity-service/src/vc/dtos/SignedVC.dto.ts b/services/identity-service/src/vc/dtos/SignedVC.dto.ts deleted file mode 100644 index 691c84eb3..000000000 --- a/services/identity-service/src/vc/dtos/SignedVC.dto.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { ApiProperty } from "@nestjs/swagger"; - -export class SignedVC { - - @ApiProperty({ description: 'Public Key of issuer.' }) - publicKey: JsonWebKey; - - @ApiProperty({ description: 'Signed VC' }) - signed: any -} \ No newline at end of file diff --git a/services/identity-service/src/vc/test-setup.ts b/services/identity-service/src/vc/test-setup.ts deleted file mode 100644 index 511497b5f..000000000 --- a/services/identity-service/src/vc/test-setup.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { PrismaClient } from '@prisma/client'; - -export async function setupTestValue() { - const prisma = new PrismaClient(); - - // Seed your test value using Prisma - await prisma.identity.create({ - data: { - id: 'did:test:123', - didDoc: JSON.stringify({}), - blockchainStatus: false, - } - }); - - // Close the Prisma connection - await prisma.$disconnect(); -} \ No newline at end of file diff --git a/services/identity-service/src/vc/vc.controller.spec.ts b/services/identity-service/src/vc/vc.controller.spec.ts new file mode 100644 index 000000000..2f2e511a7 --- /dev/null +++ b/services/identity-service/src/vc/vc.controller.spec.ts @@ -0,0 +1,67 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { VcController } from './vc.controller'; +import VcService from './vc.service'; +import { SignJsonDTO } from './dtos/Sign.dto'; +import { VerifyJsonDTO } from './dtos/Verify.dto'; + +describe('VcController', () => { + let controller: VcController; + let service: VcService; + + beforeAll(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [VcController], + providers: [ + { + provide: VcService, + useFactory: () => ({ + sign: jest.fn(), + verify: jest.fn() + }), + } + ], + }).compile(); + + controller = module.get(VcController); + service = module.get(VcService); + }); + + beforeEach(async () => { + jest.restoreAllMocks(); + }) + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); + + describe('sign', () => { + it('should sign an unsigned VC', async () => { + const body: SignJsonDTO = { + DID: 'exampleDID', + payload: { data: 'exampleData' }, + }; + + const signedVc = 'signedVC'; + jest.spyOn(service, 'sign').mockImplementation(async () => signedVc); + const result = await controller.sign(body); + expect(result).toEqual(signedVc); + expect(service.sign).toHaveBeenCalledWith(body.DID, body.payload); + }); + }); + + describe('verify', () => { + it('should verify a signed VC', async () => { + const body: VerifyJsonDTO = { + DID: 'exampleDID', + payload: { data: 'exampleData' }, + }; + + const verificationResult = true; + jest.spyOn(service, 'verify').mockImplementation(async () => verificationResult); + + const result = await controller.verify(body); + expect(result).toEqual(verificationResult); + expect(service.verify).toHaveBeenCalledWith(body.DID, body.payload); + }); + }); +}); diff --git a/services/identity-service/src/vc/vc.service.spec.ts b/services/identity-service/src/vc/vc.service.spec.ts index cc7b36ba7..7c8c29d25 100644 --- a/services/identity-service/src/vc/vc.service.spec.ts +++ b/services/identity-service/src/vc/vc.service.spec.ts @@ -3,7 +3,6 @@ import VcService from './vc.service'; import { PrismaService } from '../utils/prisma.service'; import { DidService } from '../did/did.service'; import { VaultService } from '../utils/vault.service'; -import { setupTestValue } from './test-setup'; describe('DidService', () => { let service: VcService; @@ -16,12 +15,7 @@ describe('DidService', () => { "name": "Hello!" } - // beforeAll(async () => { - // // Seed the test value before running the tests - // await setupTestValue(); - // }); - - beforeEach(async () => { + beforeAll(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [VcService, PrismaService, DidService, VaultService], }).compile(); @@ -34,6 +28,7 @@ describe('DidService', () => { method: 'test' }); signingDID = testDidDoc.id; + jest.restoreAllMocks(); }); it('should be defined', () => { diff --git a/services/identity-service/tsconfig.json b/services/identity-service/tsconfig.json index 31d05bc98..d4a74c590 100644 --- a/services/identity-service/tsconfig.json +++ b/services/identity-service/tsconfig.json @@ -17,6 +17,9 @@ "noImplicitAny": false, "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, - "noFallthroughCasesInSwitch": false + "noFallthroughCasesInSwitch": false, + "paths": { + "src/*": ["src/*"] + } } }