Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ISSUE-988]: Unit tests for registry and credential services to increase coverage #332

Merged
merged 9 commits into from
Jul 3, 2024
9 changes: 7 additions & 2 deletions services/identity-service/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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/(.*)$": "<rootDir>/$1"
}
}
}
90 changes: 90 additions & 0 deletions services/identity-service/src/app.controller.spec.ts
Original file line number Diff line number Diff line change
@@ -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>(AppController);
prismaHealthIndicator = module.get<PrismaHealthIndicator>(PrismaHealthIndicator);
vaultHealthIndicator = module.get<VaultHealthIndicator>(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);
});
});
});
81 changes: 81 additions & 0 deletions services/identity-service/src/auth/auth.guard.spec.ts
Original file line number Diff line number Diff line change
@@ -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>(AuthGuard);
reflector = module.get<Reflector>(Reflector);
configService = module.get<ConfigService>(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 };
});
});
2 changes: 1 addition & 1 deletion services/identity-service/src/auth/auth.guard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
28 changes: 27 additions & 1 deletion services/identity-service/src/did/did.controller.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import { ConfigService } from '@nestjs/config';

describe('DidController', () => {
let controller: DidController;
const content = {
let content: any;
const defaultContent = {
"content": [
{
"alsoKnownAs": [
Expand Down Expand Up @@ -43,6 +44,11 @@ describe('DidController', () => {
controller = module.get<DidController>(DidController);
});

beforeEach(async () => {
content = JSON.parse(JSON.stringify(defaultContent));
jest.restoreAllMocks();
})

it('should be defined', () => {
expect(controller).toBeDefined();
});
Expand All @@ -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++) {
Expand Down
66 changes: 58 additions & 8 deletions services/identity-service/src/did/did.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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>(DidService);
});

beforeEach(async () => {
doc = JSON.parse(JSON.stringify(defaultDoc));
jest.restoreAllMocks();
})

it('should be defined', () => {
expect(service).toBeDefined();
});
Expand All @@ -55,22 +58,62 @@ 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();
expect(result.verificationMethod[0].publicKeyMultibase).toBeDefined();
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;
const resolvedDid = await service.resolveDID(didToResolve);
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", () => {
Expand Down Expand Up @@ -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");
// });

});
6 changes: 4 additions & 2 deletions services/identity-service/src/did/did.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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];
}
Expand Down Expand Up @@ -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`);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down

This file was deleted.

Loading
Loading