Skip to content

Commit

Permalink
test: add a test as an example of using did-comm mediation (#1355)
Browse files Browse the repository at this point in the history
* test: add a test as an example of using did-comm mediation

* chore: simplify test code
  • Loading branch information
mirceanis committed Mar 7, 2024
1 parent 5c858be commit ab16cbd
Show file tree
Hide file tree
Showing 8 changed files with 393 additions and 15 deletions.
360 changes: 360 additions & 0 deletions __tests__/mediation.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,360 @@
import { beforeAll, jest } from '@jest/globals'

// @ts-ignore
import express from 'express'
// import cors from 'cors'
import {
IDataStore,
IDataStoreORM,
IDIDManager,
IKeyManager,
IMessageHandler,
IResolver,
TAgent,
} from '../packages/core-types/src'
import { MessagingRouter, RequestWithAgentRouter } from '../packages/remote-server/src'

import { createAgent, IAgentOptions } from '../packages/core/src'

import { DIDResolverPlugin } from '../packages/did-resolver/src'
import { MessageHandler } from '../packages/message-handler/src'
import { KeyManager, MemoryKeyStore, MemoryPrivateKeyStore } from '../packages/key-manager/src'
import { DIDManager, MemoryDIDStore } from '../packages/did-manager/src'
import { getResolver as getDidPeerResolver, PeerDIDProvider } from '../packages/did-provider-peer/src'
import {
CoordinateMediation,
CoordinateMediationV3MediatorMessageHandler,
createV3DeliveryRequestMessage,
createV3MediateRequestMessage,
createV3RecipientQueryMessage,
createV3RecipientUpdateMessage,
DIDComm,
DIDCommMessageHandler,
IDIDComm,
PickupMediatorMessageHandler,
PickupRecipientMessageHandler,
RoutingMessageHandler,
UpdateAction,
} from '../packages/did-comm/src'
import {
IMediationManager,
MediationManagerPlugin,
MediationResponse,
PreMediationRequestPolicy,
RequesterDid,
} from '../packages/mediation-manager/src'
import { KeyManagementSystem } from '../packages/kms-local/src'
import { DataStoreJson } from '../packages/data-store-json/src'
import { KeyValueStore } from '../packages/kv-store/src'
import { Server } from 'http'
import { DELIVERY_MESSAGE_TYPE } from '../packages/did-comm/src/protocols/messagepickup-message-handler'

const MEDIATOR_PORT = 3333
jest.fn(() => Promise.resolve())
// minimum set of plugins for users
type UserAgentPlugins = IResolver & IKeyManager & IDIDManager & IMessageHandler & IDIDComm

// minimum set of plugins for mediator
type MediatorPlugins = UserAgentPlugins & IMediationManager & IDataStoreORM & IDataStore

const defaultKms = 'local'

function createMediatorAgent(options?: IAgentOptions): TAgent<MediatorPlugins> {
let memoryJsonStore = { notifyUpdate: () => Promise.resolve() }

let policyStore = new KeyValueStore<PreMediationRequestPolicy>({
namespace: 'mediation_policy',
store: new Map<string, PreMediationRequestPolicy>(),
})

let mediationStore = new KeyValueStore<MediationResponse>({
namespace: 'mediation_response',
store: new Map<string, MediationResponse>(),
})

let recipientDidStore = new KeyValueStore<RequesterDid>({
namespace: 'recipient_did',
store: new Map<string, RequesterDid>(),
})

return createAgent<MediatorPlugins>({
...options,
plugins: [
new DIDResolverPlugin({
...getDidPeerResolver(),
}),
new KeyManager({
store: new MemoryKeyStore(),
kms: {
[defaultKms]: new KeyManagementSystem(
new MemoryPrivateKeyStore(),
),
},
}),
new DIDManager({
store: new MemoryDIDStore(),
defaultProvider: 'did:peer',
providers: {
'did:peer': new PeerDIDProvider({ defaultKms }),
},
}),
new DataStoreJson(memoryJsonStore),
new MessageHandler({
messageHandlers: [
new DIDCommMessageHandler(),
// new TrustPingMessageHandler(),
new CoordinateMediationV3MediatorMessageHandler(),
new RoutingMessageHandler(),
new PickupMediatorMessageHandler(),
],
}),
new DIDComm(),
// @ts-ignore
new MediationManagerPlugin(true, policyStore, mediationStore, recipientDidStore),
...(options?.plugins || []),
],
})
}

function createUserAgent(options?: IAgentOptions): TAgent<UserAgentPlugins> {

return createAgent<UserAgentPlugins>({
...options,
plugins: [
new DIDResolverPlugin({
...getDidPeerResolver(),
}),
new KeyManager({
store: new MemoryKeyStore(),
kms: {
[defaultKms]: new KeyManagementSystem(new MemoryPrivateKeyStore()),
},
}),
new DIDManager({
store: new MemoryDIDStore(),
defaultProvider: 'did:peer',
providers: {
'did:peer': new PeerDIDProvider({ defaultKms }),
},
}),
// new DataStoreJson(memoryJsonStore),
new MessageHandler({
messageHandlers: [
new DIDCommMessageHandler(),
// new TrustPingMessageHandler(),
// new CoordinateMediationV3RecipientMessageHandler(), // not needed for did:peer:2
new PickupRecipientMessageHandler(),
],
}),
new DIDComm(),
],
})
}

/**
* This test suite demonstrates how to use the did-comm mediation to send messages between two agents.
*
*
*/
describe('did-comm mediation', () => {
let didCommEndpointServer: Server
let mediatorDID: string
let aliceDID: string
let aliceAgent: TAgent<UserAgentPlugins>
let bobDID: string
let bobAgent: TAgent<UserAgentPlugins>

beforeAll(async () => {
const mediator = createMediatorAgent()
const messagingPath = '/messaging'

const mediatorIdentifier = await mediator.didManagerCreate({
provider: 'did:peer',
options: {
num_algo: 2,
service: {
id: '#messaging1',
type: 'DIDCommMessaging',
serviceEndpoint: `http://localhost:${MEDIATOR_PORT}${messagingPath}`,
},
},
})
mediatorDID = mediatorIdentifier.did
console.log(`mediator DID: ${mediatorDID}`)

// start a mediator service
await new Promise((resolve) => {
const requestWithAgent = RequestWithAgentRouter({ agent: mediator })
// set up a server to receive HTTP messages and pipe them to the mediator as DIDComm messages
const app = express()
// app.use(cors()) // Use this for real servers
app.use(
messagingPath,
requestWithAgent,
MessagingRouter({
metaData: { type: 'mediator-incoming' },
save: false,
}),
)
didCommEndpointServer = app.listen(MEDIATOR_PORT, () => {
console.log(`Mediator listening on port ${MEDIATOR_PORT}`)
resolve(true)
})
})

aliceAgent = createUserAgent()
const aliceIdentifier = await aliceAgent.didManagerCreate({
provider: 'did:peer',
options: { num_algo: 2, service: { type: 'DIDCommMessaging', serviceEndpoint: mediatorDID } },
})
aliceDID = aliceIdentifier.did

bobAgent = createUserAgent()
const bobIdentifier = await bobAgent.didManagerCreate({
provider: 'did:peer',
options: { num_algo: 2 },
})
bobDID = bobIdentifier.did
})

afterAll(async () => {
try {
await new Promise((resolve, reject) => didCommEndpointServer?.close(resolve) ?? reject('no server'))
} catch (e) {
//nop
}
})

it('should query for mediation for Alice', async () => {
const msg = createV3RecipientQueryMessage(aliceDID, mediatorDID)
const packed = await aliceAgent.packDIDCommMessage({ packing: 'anoncrypt', message: msg })
const res = await aliceAgent.sendDIDCommMessage({
packedMessage: packed,
recipientDidUrl: mediatorDID,
messageId: msg.id,
})
expect(res.returnMessage?.type).toBe(CoordinateMediation.RECIPIENT_QUERY_RESPONSE)
expect((res.returnMessage?.data as any)?.dids).toEqual([])
})

it('should request for mediation and update the DID for Alice', async () => {
const request = createV3MediateRequestMessage(aliceDID, mediatorDID)
const packedRequest = await aliceAgent.packDIDCommMessage({ packing: 'anoncrypt', message: request })
const mediationResponse = await aliceAgent.sendDIDCommMessage({
packedMessage: packedRequest,
recipientDidUrl: mediatorDID,
messageId: request.id,
})
expect(mediationResponse.returnMessage?.type).toBe(CoordinateMediation.MEDIATE_GRANT)
expect((mediationResponse.returnMessage?.data as any)?.routing_did).toEqual([mediatorDID])

const update = createV3RecipientUpdateMessage(aliceDID, mediatorDID, [
{
recipient_did: aliceDID,
action: UpdateAction.ADD,
},
])
const packedUpdate = await aliceAgent.packDIDCommMessage({ packing: 'anoncrypt', message: update })
const updateResponse = await aliceAgent.sendDIDCommMessage({
packedMessage: packedUpdate,
recipientDidUrl: mediatorDID,
messageId: update.id,
})
expect(updateResponse.returnMessage?.type).toBe(CoordinateMediation.RECIPIENT_UPDATE_RESPONSE)
expect((updateResponse.returnMessage?.data as any)?.updates).toEqual([
{
recipient_did: aliceDID,
action: UpdateAction.ADD,
result: 'success',
},
])

const query = createV3RecipientQueryMessage(aliceDID, mediatorDID)
const packedQuery = await aliceAgent.packDIDCommMessage({ packing: 'anoncrypt', message: query })
const queryResponse = await aliceAgent.sendDIDCommMessage({
packedMessage: packedQuery,
recipientDidUrl: mediatorDID,
messageId: query.id,
})
expect(queryResponse.returnMessage?.type).toBe(CoordinateMediation.RECIPIENT_QUERY_RESPONSE)
// mediator is finally aware of the mediation. Alice can now tell everyone about her new inbox
expect((queryResponse.returnMessage?.data as any)?.dids).toEqual([{ recipient_did: aliceDID }])
})

async function ensureMediationGranted(aliceAgent: TAgent<UserAgentPlugins>, aliceDID: string) {
const request = createV3MediateRequestMessage(aliceDID, mediatorDID)
const packedRequest = await aliceAgent.packDIDCommMessage({ packing: 'anoncrypt', message: request })
const mediationResponse = await aliceAgent.sendDIDCommMessage({
packedMessage: packedRequest,
recipientDidUrl: mediatorDID,
messageId: request.id,
})
if (mediationResponse.returnMessage?.type !== CoordinateMediation.MEDIATE_GRANT) {
throw new Error('mediation not granted')
}
const update = createV3RecipientUpdateMessage(aliceDID, mediatorDID, [
{
recipient_did: aliceDID,
action: UpdateAction.ADD,
},
])
const packedUpdate = await aliceAgent.packDIDCommMessage({ packing: 'anoncrypt', message: update })
const updateResponse = await aliceAgent.sendDIDCommMessage({
packedMessage: packedUpdate,
recipientDidUrl: mediatorDID,
messageId: update.id,
})
if (
updateResponse.returnMessage?.type !== CoordinateMediation.RECIPIENT_UPDATE_RESPONSE ||
(updateResponse.returnMessage?.data as any)?.updates[0].result !== 'success'
) {
throw new Error('mediation update failed')
}
}

it('should send a message from Bob to Alice', async () => {
await ensureMediationGranted(aliceAgent, aliceDID)

// bob sends the message
const msg = {
type: 'https://didcomm.org/basicmessage/2.0/message',
from: bobDID,
to: [aliceDID], // Bob doesn't care that alice is using a mediator
id: 'test-message',
body: 'Hi Alice, this is Bob!',
}
const packed = await bobAgent.packDIDCommMessage({ packing: 'anoncrypt', message: msg })
const res = await bobAgent.sendDIDCommMessage({
packedMessage: packed,
recipientDidUrl: aliceDID,
messageId: msg.id,
})
expect(res.transportId).toBeDefined()

// Alice checks her messages
const deliveryRequest = createV3DeliveryRequestMessage(aliceDID, mediatorDID)
const packedRequest = await aliceAgent.packDIDCommMessage({
packing: 'anoncrypt',
message: deliveryRequest,
})
const deliveryResponse = await aliceAgent.sendDIDCommMessage({
packedMessage: packedRequest,
recipientDidUrl: mediatorDID,
messageId: deliveryRequest.id,
})

expect(deliveryResponse.returnMessage?.type).toBe(DELIVERY_MESSAGE_TYPE)
expect(deliveryResponse.returnMessage?.attachments?.length).toBeGreaterThan(0)

// Alice processes her messages.

// Technically the messages are already processed by the PickupRecipientMessageHandler,
// but that doesn't automatically save them.
// You'd need to listen for the DIDCommV2Message-received event and save the messages manually if you need them
// later. OR you can use this if you want to process them in-line:
for (const attachment of deliveryResponse?.returnMessage?.attachments ?? []) {
const msg = await aliceAgent.handleMessage({ raw: JSON.stringify(attachment.data.json) })
expect(msg.data).toEqual('Hi Alice, this is Bob!')
}
})
})
2 changes: 1 addition & 1 deletion packages/core/src/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ export class Agent implements IAgent {
if (this.schemaValidation && this.schema.components.methods[method]) {
validateReturnType(method, result, this.schema)
}
Debug('veramo:agent:' + method)('%s %o', 'res', result)
Debug('veramo:agent:' + method)('%s %o', 'res', JSON.stringify(result))
return result
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import {
migrations as dataStoreMigrations,
} from '../../../data-store/src'

// @ts-ignore
import express from 'express'
import { Server } from 'http'
import { DIDCommMessageHandler } from '../message-handler.js'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,7 @@ export const createV3DeliveryRequestMessage = (
const isMediateRequest = (message: Message): message is MediateRequestMessage => {
if (message.type !== CoordinateMediation.MEDIATE_REQUEST) return false
if (!message.from) throw new Error('invalid_argument: MediateRequest received without `from` set')
if (!message.from) throw new Error('invalid_argument: MediateRequest received without `to` set')
if (!message.to) throw new Error('invalid_argument: MediateRequest received without `to` set')
return true
}

Expand Down
2 changes: 1 addition & 1 deletion packages/did-comm/src/protocols/routing-message-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export class RoutingMessageHandler extends AbstractMessageHandler {
/**
* NOTE: the below check is used to determine if the agent is using the v3 mediation manager
**/
if ('isMediateDefaultGrantAll' in context.agent) {
if (typeof context.agent.mediationManagerIsMediationGranted === 'function') {
return await context.agent.mediationManagerIsMediationGranted({ recipientDid })
}
return false
Expand Down
Loading

0 comments on commit ab16cbd

Please sign in to comment.