diff --git a/apps/shared/src/events/credentialEvents.ts b/apps/shared/src/events/credentialEvents.ts new file mode 100644 index 0000000000000000000000000000000000000000..32ed92aaffc305cd5011a7be238647b62f3fb62f --- /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 d05de3dc2617f6cef96f953cd79974fa6c8d641b..75e86d9e7b8d4faa6c0a6a1c6170161cd730cd10 100644 --- a/apps/shared/src/index.ts +++ b/apps/shared/src/index.ts @@ -9,6 +9,7 @@ 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'; export * from './dto/pagination-params.dto.js'; export * from './dto/multitenancy-params.dto.js'; diff --git a/apps/ssi-abstraction/src/agent/agent.service.ts b/apps/ssi-abstraction/src/agent/agent.service.ts index 9df1a7034eb1dec7eb9352566ea01366f26e7786..b1d9bd9279c2175eae5386fe50ecdccc14996a9e 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 0000000000000000000000000000000000000000..4652f579ba2f4f2a7134aa3c9a0a3696c054ef24 --- /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 0000000000000000000000000000000000000000..1cef75b483beddd9ff786a9b095c3402a2349e33 --- /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 0000000000000000000000000000000000000000..f0d9fc853c27167135f2117f831ebd6e4bfab4f2 --- /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 0000000000000000000000000000000000000000..3eb52b6e61e023f4deb42ebefbc8ff500af4cc99 --- /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 235511f24936f994d0a963d8b5c720d20268e972..f93c53d39f79d47a4dd4d54c4939b3ce6f4dd478 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 027474a5f46376e3b49ee813c5d1d7ab068135da..2e2b3c975f3feca342a34176c29625110cff7c93 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 d250a3eb3a81aeeae365fcd5e00062d13f7b0f98..f03939ce7db535a955d5061364450e474aceeea9 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 cf7807236f57ca373f86fe84a05b95dba260dda5..7b0ad1ec1682b95964a3e21d9ae619bca4787478 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 e72d2c4c24de6f9770496cac2a517976bc17de15..d043590040d1dfc16111c17dac62ab982f848794 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 784e1cfd6cf67ed07e7cd0f7bb4bd1a1e580663d..483a64aeed5a89c952e85b3d0ead4aacd7d1a65e 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 876afc5e240475e99fb9ca9649fe4af9087b52dd..e9d5f472538697a0aa1e24ba13d0b04308db05a6 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 0000000000000000000000000000000000000000..12250e66b47bdff7786577932365182282c60236 --- /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 fb2852ff232ad5916c44d175d873f979029b4635..b18652e5576d07dcebc25d580d3112d35d4bebca 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$', };