From 1190cb4e455c97425d46f603329cd028a7fb9177 Mon Sep 17 00:00:00 2001 From: Berend Sliedrecht <berend@animo.id> Date: Mon, 11 Dec 2023 14:50:46 +0100 Subject: [PATCH] feat(ssi): credentials module Signed-off-by: Berend Sliedrecht <berend@animo.id> --- apps/shared/src/events/credentialEvents.ts | 99 +++++++++++ apps/shared/src/index.ts | 1 + .../src/agent/agent.service.ts | 15 +- .../anoncredsCredentials.controller.spec.ts | 98 +++++++++++ .../anoncredsCredentials.controller.ts | 59 +++++++ .../anoncredsCredentials.module.ts | 13 ++ .../anoncredsCredentials.service.ts | 91 ++++++++++ .../__tests__/connections.controller.spec.ts | 17 +- .../agent/connections/connections.service.ts | 7 +- .../credentialDefinitions.service.ts | 6 +- .../src/agent/dids/dids.service.ts | 12 +- .../src/agent/schemas/schemas.service.ts | 6 +- .../src/agent/tenants/tenants.service.ts | 2 +- apps/ssi-abstraction/src/app.module.ts | 2 + .../test/anoncredsCredentials.e2e-spec.ts | 157 ++++++++++++++++++ apps/ssi-abstraction/test/jest.config.js | 2 +- 16 files changed, 552 insertions(+), 35 deletions(-) create mode 100644 apps/shared/src/events/credentialEvents.ts create mode 100644 apps/ssi-abstraction/src/agent/anoncredsCredentials/__tests__/anoncredsCredentials.controller.spec.ts create mode 100644 apps/ssi-abstraction/src/agent/anoncredsCredentials/anoncredsCredentials.controller.ts create mode 100644 apps/ssi-abstraction/src/agent/anoncredsCredentials/anoncredsCredentials.module.ts create mode 100644 apps/ssi-abstraction/src/agent/anoncredsCredentials/anoncredsCredentials.service.ts create mode 100644 apps/ssi-abstraction/test/anoncredsCredentials.e2e-spec.ts diff --git a/apps/shared/src/events/credentialEvents.ts b/apps/shared/src/events/credentialEvents.ts new file mode 100644 index 0000000..32ed92a --- /dev/null +++ b/apps/shared/src/events/credentialEvents.ts @@ -0,0 +1,99 @@ +import type { BaseEventInput } from './baseEvents.js'; + +import { + CredentialExchangeRecord, + JsonTransformer, +} from '@aries-framework/core'; + +import { BaseEvent } from './baseEvents.js'; + +export type EventDidcommAnonCredsCredentialsGetAllInput = BaseEventInput; +export class EventDidcommAnonCredsCredentialsGetAll extends BaseEvent< + Array<CredentialExchangeRecord> +> { + public static token = 'didcomm.anoncreds.credentials.getAll'; + + public get instance() { + return this.data.map((d) => + JsonTransformer.fromJSON(d, CredentialExchangeRecord), + ); + } + + public static fromEvent(e: EventDidcommAnonCredsCredentialsGetAll) { + return new EventDidcommAnonCredsCredentialsGetAll( + e.data, + e.tenantId, + e.id, + e.type, + e.timestamp, + ); + } +} + +export type EventDidcommAnonCredsCredentialsGetByIdInput = BaseEventInput<{ + credentialRecordId: string; +}>; +export class EventDidcommAnonCredsCredentialsGetById extends BaseEvent<CredentialExchangeRecord | null> { + public static token = 'didcomm.anoncreds.credentials.getById'; + + public get instance() { + return this.data + ? JsonTransformer.fromJSON(this.data, CredentialExchangeRecord) + : null; + } + + public static fromEvent(e: EventDidcommAnonCredsCredentialsGetById) { + return new EventDidcommAnonCredsCredentialsGetById( + e.data, + e.tenantId, + e.id, + e.type, + e.timestamp, + ); + } +} + +export type EventDidcommAnonCredsCredentialsOfferInput = BaseEventInput<{ + connectionId: string; + credentialDefinitionId: string; + attributes: Array<{ name: string; value: string; mimeType?: string }>; +}>; +export class EventDidcommAnonCredsCredentialsOffer extends BaseEvent<CredentialExchangeRecord> { + public static token = 'didcomm.anoncreds.credentials.offer'; + + public get instance() { + return JsonTransformer.fromJSON(this.data, CredentialExchangeRecord); + } + + public static fromEvent(e: EventDidcommAnonCredsCredentialsOffer) { + return new EventDidcommAnonCredsCredentialsOffer( + e.data, + e.tenantId, + e.id, + e.type, + e.timestamp, + ); + } +} + +export type EventDidcommAnonCredsCredentialsOfferToSelfInput = Omit< + EventDidcommAnonCredsCredentialsOfferInput, + 'connectionId' +>; +export class EventDidcommAnonCredsCredentialsOfferToSelf extends BaseEvent<CredentialExchangeRecord> { + public static token = 'didcomm.anoncreds.credentials.offerToSelf'; + + public get instance() { + return JsonTransformer.fromJSON(this.data, CredentialExchangeRecord); + } + + public static fromEvent(e: EventDidcommAnonCredsCredentialsOfferToSelf) { + return new EventDidcommAnonCredsCredentialsOfferToSelf( + e.data, + e.tenantId, + e.id, + e.type, + e.timestamp, + ); + } +} diff --git a/apps/shared/src/index.ts b/apps/shared/src/index.ts index 8fa24d3..aae2837 100644 --- a/apps/shared/src/index.ts +++ b/apps/shared/src/index.ts @@ -9,3 +9,4 @@ export * from './events/didEvents.js'; export * from './events/tenantEvents.js'; export * from './events/schemaEvents.js'; export * from './events/credentialDefinitionEvents.js'; +export * from './events/credentialEvents.js'; diff --git a/apps/ssi-abstraction/src/agent/agent.service.ts b/apps/ssi-abstraction/src/agent/agent.service.ts index 9df1a70..b1d9bd9 100644 --- a/apps/ssi-abstraction/src/agent/agent.service.ts +++ b/apps/ssi-abstraction/src/agent/agent.service.ts @@ -3,7 +3,11 @@ import type { InitConfig } from '@aries-framework/core'; import type { IndyVdrPoolConfig } from '@aries-framework/indy-vdr'; import type { OnApplicationShutdown } from '@nestjs/common'; -import { AnonCredsModule } from '@aries-framework/anoncreds'; +import { + AnonCredsCredentialFormatService, + AnonCredsModule, + LegacyIndyCredentialFormatService, +} from '@aries-framework/anoncreds'; import { AnonCredsRsModule } from '@aries-framework/anoncreds-rs'; import { AskarModule } from '@aries-framework/askar'; import { @@ -19,6 +23,7 @@ import { LogLevel, PeerDidRegistrar, PeerDidResolver, + V2CredentialProtocol, WebDidResolver, } from '@aries-framework/core'; import { @@ -94,6 +99,14 @@ export class AgentService implements OnApplicationShutdown { }), credentials: new CredentialsModule({ autoAcceptCredentials: autoAcceptCredential, + credentialProtocols: [ + new V2CredentialProtocol({ + credentialFormats: [ + new AnonCredsCredentialFormatService(), + new LegacyIndyCredentialFormatService(), + ], + }), + ], }), anoncredsRs: new AnonCredsRsModule({ anoncreds }), diff --git a/apps/ssi-abstraction/src/agent/anoncredsCredentials/__tests__/anoncredsCredentials.controller.spec.ts b/apps/ssi-abstraction/src/agent/anoncredsCredentials/__tests__/anoncredsCredentials.controller.spec.ts new file mode 100644 index 0000000..4652f57 --- /dev/null +++ b/apps/ssi-abstraction/src/agent/anoncredsCredentials/__tests__/anoncredsCredentials.controller.spec.ts @@ -0,0 +1,98 @@ +import { + CredentialExchangeRecord, + CredentialState, +} from '@aries-framework/core'; +import { Test } from '@nestjs/testing'; + +import { mockConfigModule } from '../../../config/__tests__/mockConfig.js'; +import { AgentModule } from '../../agent.module.js'; +import { AnonCredsCredentialsController } from '../anoncredsCredentials.controller.js'; +import { AnonCredsCredentialsService } from '../anoncredsCredentials.service.js'; + +describe('AnonCredsCredentialsController', () => { + let credentialsController: AnonCredsCredentialsController; + let credentialsService: AnonCredsCredentialsService; + + beforeEach(async () => { + const moduleRef = await Test.createTestingModule({ + imports: [mockConfigModule(), AgentModule], + controllers: [AnonCredsCredentialsController], + providers: [AnonCredsCredentialsService], + }).compile(); + + credentialsService = moduleRef.get(AnonCredsCredentialsService); + credentialsController = moduleRef.get(AnonCredsCredentialsController); + }); + + describe('get all', () => { + it('should get all the credential records of the agent', async () => { + const result: Array<CredentialExchangeRecord> = []; + jest.spyOn(credentialsService, 'getAll').mockResolvedValue(result); + + const event = await credentialsController.getAll({ + tenantId: 'some-id', + }); + + expect(event.data).toStrictEqual(result); + }); + }); + + describe('get by id', () => { + it('should get a credential record by id', async () => { + const result: CredentialExchangeRecord | null = null; + jest.spyOn(credentialsService, 'getById').mockResolvedValue(result); + + const event = await credentialsController.getById({ + tenantId: 'some-id', + credentialRecordId: 'some-id', + }); + + expect(event.data).toStrictEqual(result); + }); + }); + + describe('offer', () => { + it('should offer a credential', async () => { + const result: CredentialExchangeRecord = new CredentialExchangeRecord({ + state: CredentialState.Done, + threadId: 'some-id', + protocolVersion: 'v2', + }); + jest.spyOn(credentialsService, 'offer').mockResolvedValue(result); + + const event = await credentialsController.offer({ + tenantId: 'some-id', + connectionId: 'some-id', + credentialDefinitionId: 'some-id', + attributes: [ + { name: 'Name', value: 'Berend', mimeType: 'application/text' }, + { name: 'Age', value: '25' }, + ], + }); + + expect(event.data).toStrictEqual(result); + }); + }); + + describe('offer to self', () => { + it('should offer a credential to self', async () => { + const result: CredentialExchangeRecord = new CredentialExchangeRecord({ + state: CredentialState.Done, + threadId: 'some-id', + protocolVersion: 'v2', + }); + jest.spyOn(credentialsService, 'offerToSelf').mockResolvedValue(result); + + const event = await credentialsController.offerToSelf({ + tenantId: 'some-id', + credentialDefinitionId: 'some-id', + attributes: [ + { name: 'Name', value: 'Berend', mimeType: 'application/text' }, + { name: 'Age', value: '25' }, + ], + }); + + expect(event.data).toStrictEqual(result); + }); + }); +}); diff --git a/apps/ssi-abstraction/src/agent/anoncredsCredentials/anoncredsCredentials.controller.ts b/apps/ssi-abstraction/src/agent/anoncredsCredentials/anoncredsCredentials.controller.ts new file mode 100644 index 0000000..1cef75b --- /dev/null +++ b/apps/ssi-abstraction/src/agent/anoncredsCredentials/anoncredsCredentials.controller.ts @@ -0,0 +1,59 @@ +import { Controller } from '@nestjs/common'; +import { MessagePattern } from '@nestjs/microservices'; +import { + EventDidcommAnonCredsCredentialsGetAll, + EventDidcommAnonCredsCredentialsGetAllInput, + EventDidcommAnonCredsCredentialsGetById, + EventDidcommAnonCredsCredentialsGetByIdInput, + EventDidcommAnonCredsCredentialsOffer, + EventDidcommAnonCredsCredentialsOfferInput, + EventDidcommAnonCredsCredentialsOfferToSelfInput, + EventDidcommAnonCredsCredentialsOfferToSelf, +} from '@ocm/shared'; + +import { AnonCredsCredentialsService } from './anoncredsCredentials.service.js'; + +@Controller('anoncredsCredentials') +export class AnonCredsCredentialsController { + public constructor(private credentialsService: AnonCredsCredentialsService) {} + + @MessagePattern(EventDidcommAnonCredsCredentialsGetAll.token) + public async getAll( + options: EventDidcommAnonCredsCredentialsGetAllInput, + ): Promise<EventDidcommAnonCredsCredentialsGetAll> { + return new EventDidcommAnonCredsCredentialsGetAll( + await this.credentialsService.getAll(options), + options.tenantId, + ); + } + + @MessagePattern(EventDidcommAnonCredsCredentialsGetById.token) + public async getById( + options: EventDidcommAnonCredsCredentialsGetByIdInput, + ): Promise<EventDidcommAnonCredsCredentialsGetById> { + return new EventDidcommAnonCredsCredentialsGetById( + await this.credentialsService.getById(options), + options.tenantId, + ); + } + + @MessagePattern(EventDidcommAnonCredsCredentialsOffer.token) + public async offer( + options: EventDidcommAnonCredsCredentialsOfferInput, + ): Promise<EventDidcommAnonCredsCredentialsOffer> { + return new EventDidcommAnonCredsCredentialsOffer( + await this.credentialsService.offer(options), + options.tenantId, + ); + } + + @MessagePattern(EventDidcommAnonCredsCredentialsOfferToSelf.token) + public async offerToSelf( + options: EventDidcommAnonCredsCredentialsOfferToSelfInput, + ): Promise<EventDidcommAnonCredsCredentialsOfferToSelf> { + return new EventDidcommAnonCredsCredentialsOfferToSelf( + await this.credentialsService.offerToSelf(options), + options.tenantId, + ); + } +} diff --git a/apps/ssi-abstraction/src/agent/anoncredsCredentials/anoncredsCredentials.module.ts b/apps/ssi-abstraction/src/agent/anoncredsCredentials/anoncredsCredentials.module.ts new file mode 100644 index 0000000..f0d9fc8 --- /dev/null +++ b/apps/ssi-abstraction/src/agent/anoncredsCredentials/anoncredsCredentials.module.ts @@ -0,0 +1,13 @@ +import { Module } from '@nestjs/common'; + +import { AgentModule } from '../agent.module.js'; + +import { AnonCredsCredentialsController } from './anoncredsCredentials.controller.js'; +import { AnonCredsCredentialsService } from './anoncredsCredentials.service.js'; + +@Module({ + imports: [AgentModule], + providers: [AnonCredsCredentialsService], + controllers: [AnonCredsCredentialsController], +}) +export class AnonCredsCredentialsModule {} diff --git a/apps/ssi-abstraction/src/agent/anoncredsCredentials/anoncredsCredentials.service.ts b/apps/ssi-abstraction/src/agent/anoncredsCredentials/anoncredsCredentials.service.ts new file mode 100644 index 0000000..3eb52b6 --- /dev/null +++ b/apps/ssi-abstraction/src/agent/anoncredsCredentials/anoncredsCredentials.service.ts @@ -0,0 +1,91 @@ +import type { + EventDidcommAnonCredsCredentialsGetAllInput, + EventDidcommAnonCredsCredentialsGetByIdInput, + EventDidcommAnonCredsCredentialsOfferInput, + EventDidcommAnonCredsCredentialsOfferToSelfInput, +} from '@ocm/shared'; + +import { + AutoAcceptCredential, + type CredentialExchangeRecord, +} from '@aries-framework/core'; +import { Injectable } from '@nestjs/common'; + +import { MetadataTokens } from '../../common/constants.js'; +import { WithTenantService } from '../withTenantService.js'; + +@Injectable() +export class AnonCredsCredentialsService { + public constructor(private withTenantService: WithTenantService) {} + + public async getAll({ + tenantId, + }: EventDidcommAnonCredsCredentialsGetAllInput): Promise< + Array<CredentialExchangeRecord> + > { + return this.withTenantService.invoke(tenantId, (t) => + t.credentials.getAll(), + ); + } + + public async getById({ + tenantId, + credentialRecordId, + }: EventDidcommAnonCredsCredentialsGetByIdInput): Promise<CredentialExchangeRecord | null> { + return this.withTenantService.invoke(tenantId, (t) => + t.credentials.findById(credentialRecordId), + ); + } + + public async offer({ + tenantId, + connectionId, + credentialDefinitionId, + attributes, + }: EventDidcommAnonCredsCredentialsOfferInput): Promise<CredentialExchangeRecord> { + return this.withTenantService.invoke(tenantId, (t) => + t.credentials.offerCredential({ + protocolVersion: 'v2', + connectionId, + credentialFormats: { + anoncreds: { credentialDefinitionId, attributes }, + }, + }), + ); + } + + public async offerToSelf({ + tenantId, + credentialDefinitionId, + attributes, + }: EventDidcommAnonCredsCredentialsOfferToSelfInput): Promise<CredentialExchangeRecord> { + return this.withTenantService.invoke(tenantId, async (t) => { + const connections = await t.connections.getAll(); + const connection = connections.find((c) => { + const metadata = c.metadata.get<{ withSelf: boolean }>( + MetadataTokens.GAIA_X_CONNECTION_METADATA_KEY, + ); + return metadata && metadata.withSelf === true; + }); + + if (!connection) { + throw new Error( + 'Cannot offer a credential to yourself as there is no connection', + ); + } + + if (!connection.isReady) { + throw new Error('Connection with yourself is not ready, yet'); + } + + return t.credentials.offerCredential({ + protocolVersion: 'v2', + autoAcceptCredential: AutoAcceptCredential.Always, + connectionId: connection.id, + credentialFormats: { + anoncreds: { credentialDefinitionId, attributes }, + }, + }); + }); + } +} diff --git a/apps/ssi-abstraction/src/agent/connections/__tests__/connections.controller.spec.ts b/apps/ssi-abstraction/src/agent/connections/__tests__/connections.controller.spec.ts index 235511f..f93c53d 100644 --- a/apps/ssi-abstraction/src/agent/connections/__tests__/connections.controller.spec.ts +++ b/apps/ssi-abstraction/src/agent/connections/__tests__/connections.controller.spec.ts @@ -30,11 +30,11 @@ describe('ConnectionsController', () => { const result: Array<ConnectionRecord> = []; jest.spyOn(connectionsService, 'getAll').mockResolvedValue(result); - const connectionsEvent = await connectionsController.getAll({ + const event = await connectionsController.getAll({ tenantId: 'some-id', }); - expect(connectionsEvent.data).toStrictEqual(result); + expect(event.data).toStrictEqual(result); }); }); @@ -43,12 +43,12 @@ describe('ConnectionsController', () => { const result: ConnectionRecord | null = null; jest.spyOn(connectionsService, 'getById').mockResolvedValue(result); - const connectionsEvent = await connectionsController.getById({ + const event = await connectionsController.getById({ id: 'id', tenantId: 'some-id', }); - expect(connectionsEvent.data).toStrictEqual(result); + expect(event.data).toStrictEqual(result); }); }); @@ -63,12 +63,11 @@ describe('ConnectionsController', () => { .spyOn(connectionsService, 'createConnectionWithSelf') .mockResolvedValue(result); - const connectionsEvent = - await connectionsController.createConnectionWithSelf({ - tenantId: 'some-id', - }); + const event = await connectionsController.createConnectionWithSelf({ + tenantId: 'some-id', + }); - expect(connectionsEvent.data).toStrictEqual(result); + expect(event.data).toStrictEqual(result); }); }); }); diff --git a/apps/ssi-abstraction/src/agent/connections/connections.service.ts b/apps/ssi-abstraction/src/agent/connections/connections.service.ts index 027474a..2e2b3c9 100644 --- a/apps/ssi-abstraction/src/agent/connections/connections.service.ts +++ b/apps/ssi-abstraction/src/agent/connections/connections.service.ts @@ -24,15 +24,13 @@ import { WithTenantService } from '../withTenantService.js'; @Injectable() export class ConnectionsService { - public agent: AppAgent; - public withTenantService: WithTenantService; + private agent: AppAgent; public constructor( agentService: AgentService, - withTenantService: WithTenantService, + private withTenantService: WithTenantService, ) { this.agent = agentService.agent; - this.withTenantService = withTenantService; } public async getAll({ @@ -104,6 +102,7 @@ export class ConnectionsService { MetadataTokens.GAIA_X_CONNECTION_METADATA_KEY, { trusted: true, + withSelf: true, }, ); diff --git a/apps/ssi-abstraction/src/agent/credentialDefinitions/credentialDefinitions.service.ts b/apps/ssi-abstraction/src/agent/credentialDefinitions/credentialDefinitions.service.ts index 32b2b61..53acf4e 100644 --- a/apps/ssi-abstraction/src/agent/credentialDefinitions/credentialDefinitions.service.ts +++ b/apps/ssi-abstraction/src/agent/credentialDefinitions/credentialDefinitions.service.ts @@ -12,11 +12,7 @@ import { WithTenantService } from '../withTenantService.js'; @Injectable() export class CredentialDefinitionsService { - public withTenantService: WithTenantService; - - public constructor(withTenantService: WithTenantService) { - this.withTenantService = withTenantService; - } + public constructor(private withTenantService: WithTenantService) {} public async getAll({ tenantId, diff --git a/apps/ssi-abstraction/src/agent/dids/dids.service.ts b/apps/ssi-abstraction/src/agent/dids/dids.service.ts index cf78072..7b0ad1e 100644 --- a/apps/ssi-abstraction/src/agent/dids/dids.service.ts +++ b/apps/ssi-abstraction/src/agent/dids/dids.service.ts @@ -9,16 +9,10 @@ import { WithTenantService } from '../withTenantService.js'; @Injectable() export class DidsService { - private withTenantService: WithTenantService; - private configService: ConfigService; - public constructor( - withTenantService: WithTenantService, - configService: ConfigService, - ) { - this.withTenantService = withTenantService; - this.configService = configService; - } + private withTenantService: WithTenantService, + private configService: ConfigService, + ) {} public async resolve({ did, tenantId }: EventDidsResolveInput) { return this.withTenantService.invoke(tenantId, async (t) => { diff --git a/apps/ssi-abstraction/src/agent/schemas/schemas.service.ts b/apps/ssi-abstraction/src/agent/schemas/schemas.service.ts index e72d2c4..d043590 100644 --- a/apps/ssi-abstraction/src/agent/schemas/schemas.service.ts +++ b/apps/ssi-abstraction/src/agent/schemas/schemas.service.ts @@ -12,11 +12,7 @@ import { WithTenantService } from '../withTenantService.js'; @Injectable() export class SchemasService { - public withTenantService: WithTenantService; - - public constructor(withTenantService: WithTenantService) { - this.withTenantService = withTenantService; - } + public constructor(private withTenantService: WithTenantService) {} public async getAll({ tenantId, diff --git a/apps/ssi-abstraction/src/agent/tenants/tenants.service.ts b/apps/ssi-abstraction/src/agent/tenants/tenants.service.ts index 784e1cf..483a64a 100644 --- a/apps/ssi-abstraction/src/agent/tenants/tenants.service.ts +++ b/apps/ssi-abstraction/src/agent/tenants/tenants.service.ts @@ -6,7 +6,7 @@ import { AgentService } from '../agent.service.js'; @Injectable() export class TenantsService { - public agent: AppAgent; + private agent: AppAgent; public constructor(agentService: AgentService) { this.agent = agentService.agent; diff --git a/apps/ssi-abstraction/src/app.module.ts b/apps/ssi-abstraction/src/app.module.ts index 876afc5..e9d5f47 100644 --- a/apps/ssi-abstraction/src/app.module.ts +++ b/apps/ssi-abstraction/src/app.module.ts @@ -5,6 +5,7 @@ import { TerminusModule } from '@nestjs/terminus'; import { HealthController } from '@ocm/shared'; import { AgentModule } from './agent/agent.module.js'; +import { AnonCredsCredentialsModule } from './agent/anoncredsCredentials/anoncredsCredentials.module.js'; import { ConnectionsModule } from './agent/connections/connections.module.js'; import { CredentialDefinitionsModule } from './agent/credentialDefinitions/credentialDefinitions.module.js'; import { SchemasModule } from './agent/schemas/schemas.module.js'; @@ -26,6 +27,7 @@ import { validationSchema } from './config/validation.js'; CredentialDefinitionsModule, DidsModule, SchemasModule, + AnonCredsCredentialsModule, TenantsModule, ], controllers: [HealthController], diff --git a/apps/ssi-abstraction/test/anoncredsCredentials.e2e-spec.ts b/apps/ssi-abstraction/test/anoncredsCredentials.e2e-spec.ts new file mode 100644 index 0000000..12250e6 --- /dev/null +++ b/apps/ssi-abstraction/test/anoncredsCredentials.e2e-spec.ts @@ -0,0 +1,157 @@ +import type { INestApplication } from '@nestjs/common'; +import type { ClientProxy } from '@nestjs/microservices'; +import type { + EventDidcommAnonCredsCredentialsGetAllInput, + EventDidcommAnonCredsCredentialsGetByIdInput, + EventDidcommAnonCredsCredentialsOfferToSelfInput, +} from '@ocm/shared'; + +import { AutoAcceptCredential } from '@aries-framework/core'; +import { ClientsModule, Transport } from '@nestjs/microservices'; +import { Test } from '@nestjs/testing'; +import { + EventDidcommAnonCredsCredentialsGetAll, + EventDidcommAnonCredsCredentialsGetById, + EventDidcommAnonCredsCredentialsOfferToSelf, +} from '@ocm/shared'; +import { firstValueFrom } from 'rxjs'; + +import { AgentModule } from '../src/agent/agent.module.js'; +import { AnonCredsCredentialsModule } from '../src/agent/anoncredsCredentials/anoncredsCredentials.module.js'; +import { ConnectionsModule } from '../src/agent/connections/connections.module.js'; +import { ConnectionsService } from '../src/agent/connections/connections.service.js'; +import { CredentialDefinitionsModule } from '../src/agent/credentialDefinitions/credentialDefinitions.module.js'; +import { CredentialDefinitionsService } from '../src/agent/credentialDefinitions/credentialDefinitions.service.js'; +import { DidsModule } from '../src/agent/dids/dids.module.js'; +import { DidsService } from '../src/agent/dids/dids.service.js'; +import { SchemasModule } from '../src/agent/schemas/schemas.module.js'; +import { SchemasService } from '../src/agent/schemas/schemas.service.js'; +import { TenantsModule } from '../src/agent/tenants/tenants.module.js'; +import { TenantsService } from '../src/agent/tenants/tenants.service.js'; +import { mockConfigModule } from '../src/config/__tests__/mockConfig.js'; + +describe('Credentials', () => { + const TOKEN = 'CREDENTIALS_CLIENT_SERVICE'; + let app: INestApplication; + let client: ClientProxy; + let tenantId: string; + + let issuerDid: string; + let credentialDefinitionId: string; + + beforeAll(async () => { + const moduleRef = await Test.createTestingModule({ + imports: [ + mockConfigModule(3004, true), + AgentModule, + ConnectionsModule, + SchemasModule, + CredentialDefinitionsModule, + AnonCredsCredentialsModule, + TenantsModule, + DidsModule, + ClientsModule.register([{ name: TOKEN, transport: Transport.NATS }]), + ], + }).compile(); + + app = moduleRef.createNestApplication(); + + app.connectMicroservice({ transport: Transport.NATS }); + + await app.startAllMicroservices(); + await app.init(); + + client = app.get(TOKEN); + await client.connect(); + + const tenantsService = app.get(TenantsService); + const { id } = await tenantsService.create(TOKEN); + tenantId = id; + + const connectionsService = app.get(ConnectionsService); + await connectionsService.createConnectionWithSelf({ tenantId }); + + const didsService = app.get(DidsService); + const [did] = await didsService.registerDidIndyFromSeed({ + tenantId, + seed: '12312367897123300000000000000000', + }); + issuerDid = did; + + const schemaService = app.get(SchemasService); + const { schemaId } = await schemaService.register({ + issuerDid, + tenantId, + name: 'test-schema-name', + version: `1.${new Date().getTime()}`, + attributeNames: ['Name', 'Age'], + }); + + const credentialDefinitionService = app.get(CredentialDefinitionsService); + const { credentialDefinitionId: cdi } = + await credentialDefinitionService.register({ + tenantId, + issuerDid, + schemaId, + tag: `default-${new Date().getTime()}`, + }); + + credentialDefinitionId = cdi; + }); + + afterAll(async () => { + await app.close(); + client.close(); + }); + + it(EventDidcommAnonCredsCredentialsGetAll.token, async () => { + const response$ = client.send< + EventDidcommAnonCredsCredentialsGetAll, + EventDidcommAnonCredsCredentialsGetAllInput + >(EventDidcommAnonCredsCredentialsGetAll.token, { tenantId }); + const response = await firstValueFrom(response$); + const eventInstance = + EventDidcommAnonCredsCredentialsGetAll.fromEvent(response); + + expect(eventInstance.instance).toEqual(expect.arrayContaining([])); + }); + + it(EventDidcommAnonCredsCredentialsGetById.token, async () => { + const response$ = client.send< + EventDidcommAnonCredsCredentialsGetById, + EventDidcommAnonCredsCredentialsGetByIdInput + >(EventDidcommAnonCredsCredentialsGetById.token, { + tenantId, + credentialRecordId: 'some-id', + }); + const response = await firstValueFrom(response$); + const eventInstance = + EventDidcommAnonCredsCredentialsGetById.fromEvent(response); + + expect(eventInstance.instance).toEqual(null); + }); + + it(EventDidcommAnonCredsCredentialsOfferToSelf.token, async () => { + const attributes = [ + { name: 'Name', value: 'Berend' }, + { name: 'Age', value: '25' }, + ]; + + const response$ = client.send< + EventDidcommAnonCredsCredentialsOfferToSelf, + EventDidcommAnonCredsCredentialsOfferToSelfInput + >(EventDidcommAnonCredsCredentialsOfferToSelf.token, { + tenantId, + credentialDefinitionId, + attributes, + }); + + const response = await firstValueFrom(response$); + const eventInstance = + EventDidcommAnonCredsCredentialsOfferToSelf.fromEvent(response); + + expect(eventInstance.instance).toMatchObject({ + autoAcceptCredential: AutoAcceptCredential.Always, + }); + }); +}); diff --git a/apps/ssi-abstraction/test/jest.config.js b/apps/ssi-abstraction/test/jest.config.js index fb2852f..b18652e 100644 --- a/apps/ssi-abstraction/test/jest.config.js +++ b/apps/ssi-abstraction/test/jest.config.js @@ -3,7 +3,7 @@ import config from '../jest.config.js'; /** @type {import('jest').Config} */ export default { ...config, - testTimeout: 24000, + testTimeout: 36000, rootDir: '.', testRegex: '.*\\.e2e-spec\\.ts$', }; -- GitLab