diff --git a/packages/core/src/modules/indy/repository/AnonCredsCredentialDefinitionRecord.ts b/packages/core/src/modules/indy/repository/AnonCredsCredentialDefinitionRecord.ts new file mode 100644 index 0000000000..730262447f --- /dev/null +++ b/packages/core/src/modules/indy/repository/AnonCredsCredentialDefinitionRecord.ts @@ -0,0 +1,39 @@ +import type { CredDef } from 'indy-sdk' + +import { BaseRecord } from '../../../storage/BaseRecord' +import { didFromCredentialDefinitionId } from '../../../utils/did' + +export interface AnonCredsCredentialDefinitionRecordProps { + credentialDefinition: CredDef +} + +export type DefaultAnonCredsCredentialDefinitionTags = { + credentialDefinitionId: string + issuerDid: string + schemaId: string + tag: string +} + +export class AnonCredsCredentialDefinitionRecord extends BaseRecord { + public static readonly type = 'AnonCredsCredentialDefinitionRecord' + public readonly type = AnonCredsCredentialDefinitionRecord.type + public readonly credentialDefinition!: CredDef + + public constructor(props: AnonCredsCredentialDefinitionRecordProps) { + super() + + if (props) { + this.credentialDefinition = props.credentialDefinition + } + } + + public getTags() { + return { + ...this._tags, + credentialDefinitionId: this.credentialDefinition.id, + issuerDid: didFromCredentialDefinitionId(this.credentialDefinition.id), + schemaId: this.credentialDefinition.schemaId, + tag: this.credentialDefinition.tag, + } + } +} diff --git a/packages/core/src/modules/indy/repository/AnonCredsCredentialDefinitionRepository.ts b/packages/core/src/modules/indy/repository/AnonCredsCredentialDefinitionRepository.ts new file mode 100644 index 0000000000..706c7e2cac --- /dev/null +++ b/packages/core/src/modules/indy/repository/AnonCredsCredentialDefinitionRepository.ts @@ -0,0 +1,27 @@ +import type { AgentContext } from '../../../agent/context/AgentContext' + +import { EventEmitter } from '../../../agent/EventEmitter' +import { InjectionSymbols } from '../../../constants' +import { injectable, inject } from '../../../plugins' +import { Repository } from '../../../storage/Repository' +import { StorageService } from '../../../storage/StorageService' + +import { AnonCredsCredentialDefinitionRecord } from './AnonCredsCredentialDefinitionRecord' + +@injectable() +export class AnonCredsCredentialDefinitionRepository extends Repository { + public constructor( + @inject(InjectionSymbols.StorageService) storageService: StorageService, + eventEmitter: EventEmitter + ) { + super(AnonCredsCredentialDefinitionRecord, storageService, eventEmitter) + } + + public async getByCredentialDefinitionId(agentContext: AgentContext, credentialDefinitionId: string) { + return this.getSingleByQuery(agentContext, { credentialDefinitionId }) + } + + public async findByCredentialDefinitionId(agentContext: AgentContext, credentialDefinitionId: string) { + return this.findSingleByQuery(agentContext, { credentialDefinitionId }) + } +} diff --git a/packages/core/src/modules/indy/repository/AnonCredsSchemaRecord.ts b/packages/core/src/modules/indy/repository/AnonCredsSchemaRecord.ts new file mode 100644 index 0000000000..1c369626cf --- /dev/null +++ b/packages/core/src/modules/indy/repository/AnonCredsSchemaRecord.ts @@ -0,0 +1,39 @@ +import type { Schema } from 'indy-sdk' + +import { BaseRecord } from '../../../storage/BaseRecord' +import { didFromSchemaId } from '../../../utils/did' + +export interface AnonCredsSchemaRecordProps { + schema: Schema +} + +export type DefaultAnonCredsSchemaTags = { + schemaId: string + schemaIssuerDid: string + schemaName: string + schemaVersion: string +} + +export class AnonCredsSchemaRecord extends BaseRecord { + public static readonly type = 'AnonCredsSchemaRecord' + public readonly type = AnonCredsSchemaRecord.type + public readonly schema!: Schema + + public constructor(props: AnonCredsSchemaRecordProps) { + super() + + if (props) { + this.schema = props.schema + } + } + + public getTags() { + return { + ...this._tags, + schemaId: this.schema.id, + schemaIssuerDid: didFromSchemaId(this.schema.id), + schemaName: this.schema.name, + schemaVersion: this.schema.version, + } + } +} diff --git a/packages/core/src/modules/indy/repository/AnonCredsSchemaRepository.ts b/packages/core/src/modules/indy/repository/AnonCredsSchemaRepository.ts new file mode 100644 index 0000000000..311931f1f6 --- /dev/null +++ b/packages/core/src/modules/indy/repository/AnonCredsSchemaRepository.ts @@ -0,0 +1,27 @@ +import type { AgentContext } from '../../../agent/context/AgentContext' + +import { EventEmitter } from '../../../agent/EventEmitter' +import { InjectionSymbols } from '../../../constants' +import { injectable, inject } from '../../../plugins' +import { Repository } from '../../../storage/Repository' +import { StorageService } from '../../../storage/StorageService' + +import { AnonCredsSchemaRecord } from './AnonCredsSchemaRecord' + +@injectable() +export class AnonCredsSchemaRepository extends Repository { + public constructor( + @inject(InjectionSymbols.StorageService) storageService: StorageService, + eventEmitter: EventEmitter + ) { + super(AnonCredsSchemaRecord, storageService, eventEmitter) + } + + public async getBySchemaId(agentContext: AgentContext, schemaId: string) { + return this.getSingleByQuery(agentContext, { schemaId: schemaId }) + } + + public async findBySchemaId(agentContext: AgentContext, schemaId: string) { + return await this.findSingleByQuery(agentContext, { schemaId: schemaId }) + } +} diff --git a/packages/core/src/modules/ledger/LedgerModule.ts b/packages/core/src/modules/ledger/LedgerModule.ts index 9262511c8c..b6fb73e8ca 100644 --- a/packages/core/src/modules/ledger/LedgerModule.ts +++ b/packages/core/src/modules/ledger/LedgerModule.ts @@ -1,23 +1,37 @@ import type { DependencyManager } from '../../plugins' import type { IndyPoolConfig } from './IndyPool' -import type { CredentialDefinitionTemplate, SchemaTemplate } from './services' -import type { NymRole } from 'indy-sdk' +import type { SchemaTemplate, CredentialDefinitionTemplate } from './services' +import type { CredDef, NymRole, Schema } from 'indy-sdk' import { AgentContext } from '../../agent' import { AriesFrameworkError } from '../../error' +import { IndySdkError } from '../../error/IndySdkError' import { injectable, module } from '../../plugins' +import { isIndyError } from '../../utils/indyError' +import { AnonCredsCredentialDefinitionRepository } from '../indy/repository/AnonCredsCredentialDefinitionRepository' +import { AnonCredsSchemaRepository } from '../indy/repository/AnonCredsSchemaRepository' -import { IndyLedgerService, IndyPoolService } from './services' +import { generateCredentialDefinitionId, generateSchemaId } from './ledgerUtil' +import { IndyPoolService, IndyLedgerService } from './services' @module() @injectable() export class LedgerModule { private ledgerService: IndyLedgerService private agentContext: AgentContext - - public constructor(ledgerService: IndyLedgerService, agentContext: AgentContext) { + private anonCredsCredentialDefinitionRepository: AnonCredsCredentialDefinitionRepository + private anonCredsSchemaRepository: AnonCredsSchemaRepository + + public constructor( + ledgerService: IndyLedgerService, + agentContext: AgentContext, + anonCredsCredentialDefinitionRepository: AnonCredsCredentialDefinitionRepository, + anonCredsSchemaRepository: AnonCredsSchemaRepository + ) { this.ledgerService = ledgerService this.agentContext = agentContext + this.anonCredsCredentialDefinitionRepository = anonCredsCredentialDefinitionRepository + this.anonCredsSchemaRepository = anonCredsSchemaRepository } public setPools(poolConfigs: IndyPoolConfig[]) { @@ -45,18 +59,47 @@ export class LedgerModule { return this.ledgerService.getPublicDid(this.agentContext, did) } - public async registerSchema(schema: SchemaTemplate) { + public async getSchema(id: string) { + return this.ledgerService.getSchema(this.agentContext, id) + } + + public async registerSchema(schema: SchemaTemplate): Promise { const did = this.agentContext.wallet.publicDid?.did if (!did) { throw new AriesFrameworkError('Agent has no public DID.') } + const schemaId = generateSchemaId(did, schema.name, schema.version) + + // Try find the schema in the wallet + const schemaRecord = await this.anonCredsSchemaRepository.findBySchemaId(this.agentContext, schemaId) + // Schema in wallet + if (schemaRecord) return schemaRecord.schema + + const schemaFromLedger = await this.findBySchemaIdOnLedger(schemaId) + if (schemaFromLedger) return schemaFromLedger return this.ledgerService.registerSchema(this.agentContext, did, schema) } - public async getSchema(id: string) { - return this.ledgerService.getSchema(this.agentContext, id) + private async findBySchemaIdOnLedger(schemaId: string) { + try { + return await this.ledgerService.getSchema(this.agentContext, schemaId) + } catch (e) { + if (e instanceof IndySdkError && isIndyError(e.cause, 'LedgerNotFound')) return null + + throw e + } + } + + private async findByCredentialDefinitionIdOnLedger(credentialDefinitionId: string): Promise { + try { + return await this.ledgerService.getCredentialDefinition(this.agentContext, credentialDefinitionId) + } catch (e) { + if (e instanceof IndySdkError && isIndyError(e.cause, 'LedgerNotFound')) return null + + throw e + } } public async registerCredentialDefinition( @@ -68,7 +111,30 @@ export class LedgerModule { throw new AriesFrameworkError('Agent has no public DID.') } - return this.ledgerService.registerCredentialDefinition(this.agentContext, did, { + // Construct credential definition ID + const credentialDefinitionId = generateCredentialDefinitionId( + did, + credentialDefinitionTemplate.schema.seqNo, + credentialDefinitionTemplate.tag + ) + + // Check if the credential exists in wallet. If so, return it + const credentialDefinitionRecord = await this.anonCredsCredentialDefinitionRepository.findByCredentialDefinitionId( + this.agentContext, + credentialDefinitionId + ) + if (credentialDefinitionRecord) return credentialDefinitionRecord.credentialDefinition + + // Check for the credential on the ledger. + const credentialDefinitionOnLedger = await this.findByCredentialDefinitionIdOnLedger(credentialDefinitionId) + if (credentialDefinitionOnLedger) { + throw new AriesFrameworkError( + `No credential definition record found and credential definition ${credentialDefinitionId} already exists on the ledger.` + ) + } + + // Register the credential + return await this.ledgerService.registerCredentialDefinition(this.agentContext, did, { ...credentialDefinitionTemplate, signatureType: 'CL', }) diff --git a/packages/core/src/modules/ledger/__tests__/LedgerModule.test.ts b/packages/core/src/modules/ledger/__tests__/LedgerModule.test.ts new file mode 100644 index 0000000000..e33c6e3c41 --- /dev/null +++ b/packages/core/src/modules/ledger/__tests__/LedgerModule.test.ts @@ -0,0 +1,372 @@ +import type { AgentContext } from '../../../agent/context/AgentContext' +import type { IndyPoolConfig } from '../IndyPool' +import type { CredentialDefinitionTemplate } from '../services/IndyLedgerService' +import type * as Indy from 'indy-sdk' + +import { getAgentConfig, getAgentContext, mockFunction, mockProperty } from '../../../../tests/helpers' +import { AriesFrameworkError } from '../../../error/AriesFrameworkError' +import { IndyWallet } from '../../../wallet/IndyWallet' +import { AnonCredsCredentialDefinitionRecord } from '../../indy/repository/AnonCredsCredentialDefinitionRecord' +import { AnonCredsCredentialDefinitionRepository } from '../../indy/repository/AnonCredsCredentialDefinitionRepository' +import { AnonCredsSchemaRecord } from '../../indy/repository/AnonCredsSchemaRecord' +import { AnonCredsSchemaRepository } from '../../indy/repository/AnonCredsSchemaRepository' +import { LedgerModule } from '../LedgerModule' +import { generateCredentialDefinitionId, generateSchemaId } from '../ledgerUtil' +import { IndyLedgerService } from '../services/IndyLedgerService' + +jest.mock('../services/IndyLedgerService') +const IndyLedgerServiceMock = IndyLedgerService as jest.Mock + +jest.mock('../../indy/repository/AnonCredsCredentialDefinitionRepository') +const AnonCredsCredentialDefinitionRepositoryMock = + AnonCredsCredentialDefinitionRepository as jest.Mock +jest.mock('../../indy/repository/AnonCredsSchemaRepository') +const AnonCredsSchemaRepositoryMock = AnonCredsSchemaRepository as jest.Mock + +const did = 'Y5bj4SjCiTM9PgeheKAiXx' + +const schemaId = 'abcd' + +const schema: Indy.Schema = { + id: schemaId, + attrNames: ['hello', 'world'], + name: 'awesomeSchema', + version: '1', + ver: '1', + seqNo: 99, +} + +const credentialDefinition = { + schema: 'abcde', + tag: 'someTag', + signatureType: 'CL', + supportRevocation: true, +} + +const credDef: Indy.CredDef = { + id: 'abcde', + schemaId: schema.id, + type: 'CL', + tag: 'someTag', + value: { + primary: credentialDefinition as Record, + revocation: true, + }, + ver: '1', +} + +const credentialDefinitionTemplate: Omit = { + schema: schema, + tag: 'someTag', + supportRevocation: true, +} + +const revocRegDef: Indy.RevocRegDef = { + id: 'abcde', + revocDefType: 'CL_ACCUM', + tag: 'someTag', + credDefId: 'abcde', + value: { + issuanceType: 'ISSUANCE_BY_DEFAULT', + maxCredNum: 3, + tailsHash: 'abcde', + tailsLocation: 'xyz', + publicKeys: ['abcde', 'fghijk'], + }, + ver: 'abcde', +} + +const schemaIdGenerated = generateSchemaId(did, schema.name, schema.version) + +const credentialDefinitionId = generateCredentialDefinitionId( + did, + credentialDefinitionTemplate.schema.seqNo, + credentialDefinitionTemplate.tag +) + +const pools: IndyPoolConfig[] = [ + { + id: 'sovrinMain', + isProduction: true, + genesisTransactions: 'xxx', + transactionAuthorAgreement: { version: '1', acceptanceMechanism: 'accept' }, + }, +] + +describe('LedgerModule', () => { + let wallet: IndyWallet + let ledgerService: IndyLedgerService + let anonCredsCredentialDefinitionRepository: AnonCredsCredentialDefinitionRepository + let anonCredsSchemaRepository: AnonCredsSchemaRepository + let ledgerModule: LedgerModule + let agentContext: AgentContext + + const contextCorrelationId = 'mock' + const agentConfig = getAgentConfig('LedgerModuleTest', { + indyLedgers: pools, + }) + + beforeEach(async () => { + wallet = new IndyWallet(agentConfig.agentDependencies, agentConfig.logger) + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + await wallet.createAndOpen(agentConfig.walletConfig!) + }) + + afterEach(async () => { + await wallet.delete() + }) + + beforeEach(async () => { + ledgerService = new IndyLedgerServiceMock() + + agentContext = getAgentContext({ + wallet, + agentConfig, + contextCorrelationId, + }) + + anonCredsCredentialDefinitionRepository = new AnonCredsCredentialDefinitionRepositoryMock() + anonCredsSchemaRepository = new AnonCredsSchemaRepositoryMock() + + ledgerModule = new LedgerModule( + ledgerService, + agentContext, + anonCredsCredentialDefinitionRepository, + anonCredsSchemaRepository + ) + }) + + describe('LedgerModule', () => { + // Connect to pools + describe('connectToPools', () => { + it('should connect to all pools', async () => { + mockFunction(ledgerService.connectToPools).mockResolvedValue([1, 2, 4]) + await expect(ledgerModule.connectToPools()).resolves + expect(ledgerService.connectToPools).toHaveBeenCalled() + }) + }) + + // Register public did + describe('registerPublicDid', () => { + it('should register a public DID', async () => { + mockFunction(ledgerService.registerPublicDid).mockResolvedValueOnce(did) + mockProperty(wallet, 'publicDid', { did: did, verkey: 'abcde' }) + await expect(ledgerModule.registerPublicDid(did, 'abcde', 'someAlias')).resolves.toEqual(did) + expect(ledgerService.registerPublicDid).toHaveBeenCalledWith( + agentContext, + did, + did, + 'abcde', + 'someAlias', + undefined + ) + }) + + it('should throw an error if the DID cannot be registered because there is no public did', async () => { + const did = 'Y5bj4SjCiTM9PgeheKAiXx' + mockProperty(wallet, 'publicDid', undefined) + await expect(ledgerModule.registerPublicDid(did, 'abcde', 'someAlias')).rejects.toThrowError( + AriesFrameworkError + ) + }) + }) + + // Get public DID + describe('getPublicDid', () => { + it('should return the public DID if there is one', async () => { + const nymResponse: Indy.GetNymResponse = { did: 'Y5bj4SjCiTM9PgeheKAiXx', verkey: 'abcde', role: 'STEWARD' } + mockProperty(wallet, 'publicDid', { did: nymResponse.did, verkey: nymResponse.verkey }) + mockFunction(ledgerService.getPublicDid).mockResolvedValueOnce(nymResponse) + await expect(ledgerModule.getPublicDid(nymResponse.did)).resolves.toEqual(nymResponse) + expect(ledgerService.getPublicDid).toHaveBeenCalledWith(agentContext, nymResponse.did) + }) + }) + + // Get schema + describe('getSchema', () => { + it('should return the schema by id if there is one', async () => { + mockFunction(ledgerService.getSchema).mockResolvedValueOnce(schema) + await expect(ledgerModule.getSchema(schemaId)).resolves.toEqual(schema) + expect(ledgerService.getSchema).toHaveBeenCalledWith(agentContext, schemaId) + }) + + it('should throw an error if no schema for the id exists', async () => { + mockFunction(ledgerService.getSchema).mockRejectedValueOnce( + new AriesFrameworkError('Error retrieving schema abcd from ledger 1') + ) + await expect(ledgerModule.getSchema(schemaId)).rejects.toThrowError(AriesFrameworkError) + expect(ledgerService.getSchema).toHaveBeenCalledWith(agentContext, schemaId) + }) + }) + + describe('registerSchema', () => { + it('should throw an error if there is no public DID', async () => { + mockProperty(wallet, 'publicDid', undefined) + await expect(ledgerModule.registerSchema({ ...schema, attributes: ['hello', 'world'] })).rejects.toThrowError( + AriesFrameworkError + ) + }) + + it('should return the schema from anonCreds when it already exists', async () => { + mockProperty(wallet, 'publicDid', { did: did, verkey: 'abcde' }) + mockFunction(anonCredsSchemaRepository.findBySchemaId).mockResolvedValueOnce( + new AnonCredsSchemaRecord({ schema: schema }) + ) + await expect(ledgerModule.registerSchema({ ...schema, attributes: ['hello', 'world'] })).resolves.toEqual( + schema + ) + expect(anonCredsSchemaRepository.findBySchemaId).toHaveBeenCalledWith(agentContext, schemaIdGenerated) + }) + + it('should return the schema from the ledger when it already exists', async () => { + mockProperty(wallet, 'publicDid', { did: did, verkey: 'abcde' }) + jest + // eslint-disable-next-line @typescript-eslint/no-explicit-any + .spyOn(LedgerModule.prototype as any, 'findBySchemaIdOnLedger') + .mockResolvedValueOnce(new AnonCredsSchemaRecord({ schema: schema })) + await expect( + ledgerModule.registerSchema({ ...schema, attributes: ['hello', 'world'] }) + ).resolves.toHaveProperty('schema', schema) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + expect(jest.spyOn(LedgerModule.prototype as any, 'findBySchemaIdOnLedger')).toHaveBeenCalledWith( + schemaIdGenerated + ) + }) + + it('should return the schema after registering it', async () => { + mockProperty(wallet, 'publicDid', { did: did, verkey: 'abcde' }) + mockFunction(ledgerService.registerSchema).mockResolvedValueOnce(schema) + await expect(ledgerModule.registerSchema({ ...schema, attributes: ['hello', 'world'] })).resolves.toEqual( + schema + ) + expect(ledgerService.registerSchema).toHaveBeenCalledWith(agentContext, did, { + ...schema, + attributes: ['hello', 'world'], + }) + }) + }) + + describe('registerCredentialDefinition', () => { + it('should throw an error if there si no public DID', async () => { + mockProperty(wallet, 'publicDid', undefined) + await expect(ledgerModule.registerCredentialDefinition(credentialDefinitionTemplate)).rejects.toThrowError( + AriesFrameworkError + ) + }) + + it('should return the credential definition from the wallet if it already exists', async () => { + mockProperty(wallet, 'publicDid', { did: did, verkey: 'abcde' }) + const anonCredsCredentialDefinitionRecord: AnonCredsCredentialDefinitionRecord = + new AnonCredsCredentialDefinitionRecord({ + credentialDefinition: credDef, + }) + mockFunction(anonCredsCredentialDefinitionRepository.findByCredentialDefinitionId).mockResolvedValueOnce( + anonCredsCredentialDefinitionRecord + ) + await expect(ledgerModule.registerCredentialDefinition(credentialDefinitionTemplate)).resolves.toHaveProperty( + 'value.primary', + credentialDefinition + ) + expect(anonCredsCredentialDefinitionRepository.findByCredentialDefinitionId).toHaveBeenCalledWith( + agentContext, + credentialDefinitionId + ) + }) + + it('should throw an exception if the definition already exists on the ledger', async () => { + mockProperty(wallet, 'publicDid', { did: did, verkey: 'abcde' }) + jest + // eslint-disable-next-line @typescript-eslint/no-explicit-any + .spyOn(LedgerModule.prototype as any, 'findByCredentialDefinitionIdOnLedger') + .mockResolvedValueOnce({ credentialDefinition: credentialDefinition }) + await expect(ledgerModule.registerCredentialDefinition(credentialDefinitionTemplate)).rejects.toThrowError( + AriesFrameworkError + ) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + expect(jest.spyOn(LedgerModule.prototype as any, 'findByCredentialDefinitionIdOnLedger')).toHaveBeenCalledWith( + credentialDefinitionId + ) + }) + + it('should register the credential successfully if it is neither in the wallet and neither on the ledger', async () => { + mockProperty(wallet, 'publicDid', { did: did, verkey: 'abcde' }) + mockFunction(ledgerService.registerCredentialDefinition).mockResolvedValueOnce(credDef) + await expect(ledgerModule.registerCredentialDefinition(credentialDefinitionTemplate)).resolves.toEqual(credDef) + expect(ledgerService.registerCredentialDefinition).toHaveBeenCalledWith(agentContext, did, { + ...credentialDefinitionTemplate, + signatureType: 'CL', + }) + }) + }) + + describe('getCredentialDefinition', () => { + it('should return the credential definition given the id', async () => { + mockProperty(wallet, 'publicDid', { did: did, verkey: 'abcde' }) + mockFunction(ledgerService.getCredentialDefinition).mockResolvedValue(credDef) + await expect(ledgerModule.getCredentialDefinition(credDef.id)).resolves.toEqual(credDef) + expect(ledgerService.getCredentialDefinition).toHaveBeenCalledWith(agentContext, credDef.id) + }) + + it('should throw an error if there is no credential definition for the given id', async () => { + mockProperty(wallet, 'publicDid', { did: did, verkey: 'abcde' }) + mockFunction(ledgerService.getCredentialDefinition).mockRejectedValueOnce(new AriesFrameworkError('')) + await expect(ledgerModule.getCredentialDefinition(credDef.id)).rejects.toThrowError(AriesFrameworkError) + expect(ledgerService.getCredentialDefinition).toHaveBeenCalledWith(agentContext, credDef.id) + }) + }) + + describe('getRevocationRegistryDefinition', () => { + it('should return the ParseRevocationRegistryDefinitionTemplate for a valid revocationRegistryDefinitionId', async () => { + const parseRevocationRegistryDefinitionTemplate = { + revocationRegistryDefinition: revocRegDef, + revocationRegistryDefinitionTxnTime: 12345678, + } + mockFunction(ledgerService.getRevocationRegistryDefinition).mockResolvedValue( + parseRevocationRegistryDefinitionTemplate + ) + await expect(ledgerModule.getRevocationRegistryDefinition(revocRegDef.id)).resolves.toBe( + parseRevocationRegistryDefinitionTemplate + ) + expect(ledgerService.getRevocationRegistryDefinition).toHaveBeenLastCalledWith(agentContext, revocRegDef.id) + }) + + it('should throw an error if the ParseRevocationRegistryDefinitionTemplate does not exists', async () => { + mockFunction(ledgerService.getRevocationRegistryDefinition).mockRejectedValueOnce(new AriesFrameworkError('')) + await expect(ledgerModule.getRevocationRegistryDefinition('abcde')).rejects.toThrowError(AriesFrameworkError) + expect(ledgerService.getRevocationRegistryDefinition).toHaveBeenCalledWith(agentContext, revocRegDef.id) + }) + }) + + describe('getRevocationRegistryDelta', () => { + it('should return the ParseRevocationRegistryDeltaTemplate', async () => { + const revocRegDelta = { + value: { + prevAccum: 'prev', + accum: 'accum', + issued: [1, 2, 3], + revoked: [4, 5, 6], + }, + ver: 'ver', + } + const parseRevocationRegistryDeltaTemplate = { + revocationRegistryDelta: revocRegDelta, + deltaTimestamp: 12345678, + } + + mockFunction(ledgerService.getRevocationRegistryDelta).mockResolvedValueOnce( + parseRevocationRegistryDeltaTemplate + ) + await expect(ledgerModule.getRevocationRegistryDelta('12345')).resolves.toEqual( + parseRevocationRegistryDeltaTemplate + ) + expect(ledgerService.getRevocationRegistryDelta).toHaveBeenCalledTimes(1) + }) + + it('should throw an error if the delta cannot be obtained', async () => { + mockFunction(ledgerService.getRevocationRegistryDelta).mockRejectedValueOnce(new AriesFrameworkError('')) + await expect(ledgerModule.getRevocationRegistryDelta('abcde1234')).rejects.toThrowError(AriesFrameworkError) + expect(ledgerService.getRevocationRegistryDelta).toHaveBeenCalledTimes(1) + }) + }) + }) +}) diff --git a/packages/core/src/modules/ledger/__tests__/ledgerUtils.test.ts b/packages/core/src/modules/ledger/__tests__/ledgerUtils.test.ts new file mode 100644 index 0000000000..a27e788ed2 --- /dev/null +++ b/packages/core/src/modules/ledger/__tests__/ledgerUtils.test.ts @@ -0,0 +1,61 @@ +import type { LedgerRejectResponse, LedgerReqnackResponse } from 'indy-sdk' + +import * as LedgerUtil from '../ledgerUtil' + +describe('LedgerUtils', () => { + // IsLedgerRejectResponse + it('Should return true if the response op is: REJECT', () => { + const ledgerResponse: LedgerRejectResponse = { + op: 'REJECT', + reqId: 1, + reason: 'Why not', + identifier: '123456', + } + expect(LedgerUtil.isLedgerRejectResponse(ledgerResponse)).toEqual(true) + }) + it('Should return false if the response op is not: REJECT', () => { + const ledgerResponse: LedgerReqnackResponse = { + op: 'REQNACK', + reqId: 1, + reason: 'Why not', + identifier: '123456', + } + expect(LedgerUtil.isLedgerRejectResponse(ledgerResponse)).toEqual(false) + }) + + // isLedgerReqnackResponse + it('Should return true if the response op is: REQNACK', () => { + const ledgerResponse: LedgerReqnackResponse = { + op: 'REQNACK', + reqId: 1, + reason: 'Why not', + identifier: '123456', + } + expect(LedgerUtil.isLedgerReqnackResponse(ledgerResponse)).toEqual(true) + }) + it('Should return false if the response op is NOT: REQNACK', () => { + const ledgerResponse: LedgerRejectResponse = { + op: 'REJECT', + reqId: 1, + reason: 'Why not', + identifier: '123456', + } + expect(LedgerUtil.isLedgerReqnackResponse(ledgerResponse)).toEqual(false) + }) + + // generateSchemaId + it('Should return a valid schema ID given did name and version', () => { + const did = '12345', + name = 'backbench', + version = '420' + expect(LedgerUtil.generateSchemaId(did, name, version)).toEqual('12345:2:backbench:420') + }) + + // generateCredentialDefinitionId + it('Should return a valid schema ID given did name and version', () => { + const did = '12345', + seqNo = 420, + tag = 'someTag' + expect(LedgerUtil.generateCredentialDefinitionId(did, seqNo, tag)).toEqual('12345:3:CL:420:someTag') + }) +}) diff --git a/packages/core/src/modules/ledger/ledgerUtil.ts b/packages/core/src/modules/ledger/ledgerUtil.ts index 62e75f1e72..6c9cfb1cf8 100644 --- a/packages/core/src/modules/ledger/ledgerUtil.ts +++ b/packages/core/src/modules/ledger/ledgerUtil.ts @@ -7,3 +7,11 @@ export function isLedgerRejectResponse(response: Indy.LedgerResponse): response export function isLedgerReqnackResponse(response: Indy.LedgerResponse): response is Indy.LedgerReqnackResponse { return response.op === 'REQNACK' } + +export function generateSchemaId(did: string, name: string, version: string) { + return `${did}:2:${name}:${version}` +} + +export function generateCredentialDefinitionId(did: string, seqNo: number, tag: string) { + return `${did}:3:CL:${seqNo}:${tag}` +}