Skip to content

Commit

Permalink
refactor: unify connection record state and role (#732)
Browse files Browse the repository at this point in the history
Signed-off-by: Timo Glastra <[email protected]>
  • Loading branch information
TimoGlastra authored May 2, 2022
1 parent 73d296f commit 3068f32
Show file tree
Hide file tree
Showing 15 changed files with 187 additions and 126 deletions.
4 changes: 2 additions & 2 deletions packages/core/src/agent/__tests__/TransportService.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { getMockConnection } from '../../../tests/helpers'
import { ConnectionRole } from '../../modules/connections'
import { DidExchangeRole } from '../../modules/connections'
import { TransportService } from '../TransportService'

import { DummyTransportSession } from './stubs'
Expand All @@ -13,7 +13,7 @@ describe('TransportService', () => {
})

test(`remove session saved for a given connection`, () => {
const connection = getMockConnection({ id: 'test-123', role: ConnectionRole.Inviter })
const connection = getMockConnection({ id: 'test-123', role: DidExchangeRole.Responder })
const session = new DummyTransportSession('dummy-session-123')
session.connection = connection

Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/modules/connections/ConnectionEvents.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { BaseEvent } from '../../agent/Events'
import type { ConnectionState, DidExchangeState } from './models'
import type { DidExchangeState } from './models'
import type { ConnectionRecord } from './repository/ConnectionRecord'

export enum ConnectionEventTypes {
Expand All @@ -10,6 +10,6 @@ export interface ConnectionStateChangedEvent extends BaseEvent {
type: typeof ConnectionEventTypes.ConnectionStateChanged
payload: {
connectionRecord: ConnectionRecord
previousState: ConnectionState | DidExchangeState | null
previousState: DidExchangeState | null
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,14 @@ import { DidRepository } from '../../dids/repository'
import { OutOfBandRole } from '../../oob/domain/OutOfBandRole'
import { OutOfBandState } from '../../oob/domain/OutOfBandState'
import { ConnectionRequestMessage, ConnectionResponseMessage, TrustPingMessage } from '../messages'
import { Connection, ConnectionState, ConnectionRole, DidDoc, EmbeddedAuthentication, Ed25119Sig2018 } from '../models'
import {
Connection,
DidDoc,
EmbeddedAuthentication,
Ed25119Sig2018,
DidExchangeRole,
DidExchangeState,
} from '../models'
import { ConnectionRepository } from '../repository/ConnectionRepository'
import { ConnectionService } from '../services/ConnectionService'
import { convertToNewDidDocument } from '../services/helpers'
Expand Down Expand Up @@ -76,7 +83,7 @@ describe('ConnectionService', () => {

const { connectionRecord, message } = await connectionService.createRequest(outOfBand, config)

expect(connectionRecord.state).toBe(ConnectionState.Requested)
expect(connectionRecord.state).toBe(DidExchangeState.RequestSent)
expect(message.label).toBe(agentConfig.label)
expect(message.connection.did).toBe('fakeDid')
expect(message.connection.didDoc).toEqual(
Expand Down Expand Up @@ -207,7 +214,7 @@ describe('ConnectionService', () => {
})
const processedConnection = await connectionService.processRequest(messageContext, outOfBand)

expect(processedConnection.state).toBe(ConnectionState.Requested)
expect(processedConnection.state).toBe(DidExchangeState.RequestReceived)
expect(processedConnection.theirDid).toBe('did:peer:1zQmbCWxDcdq3rxQ1LVTELSWeNMsmiPdwAJzwzHLVDFfVks5')
expect(processedConnection.theirLabel).toBe('test-label')
expect(processedConnection.threadId).toBe(connectionRequest.id)
Expand All @@ -219,8 +226,8 @@ describe('ConnectionService', () => {

const connectionRecord = getMockConnection({
id: 'test',
state: ConnectionState.Invited,
role: ConnectionRole.Inviter,
state: DidExchangeState.InvitationSent,
role: DidExchangeRole.Responder,
multiUseInvitation: true,
})

Expand Down Expand Up @@ -267,15 +274,15 @@ describe('ConnectionService', () => {
})
const processedConnection = await connectionService.processRequest(messageContext, outOfBand)

expect(processedConnection.state).toBe(ConnectionState.Requested)
expect(processedConnection.state).toBe(DidExchangeState.RequestReceived)
expect(processedConnection.theirDid).toBe('did:peer:1zQmbCWxDcdq3rxQ1LVTELSWeNMsmiPdwAJzwzHLVDFfVks5')
expect(processedConnection.theirLabel).toBe('test-label')
expect(processedConnection.threadId).toBe(connectionRequest.id)

expect(connectionRepository.save).toHaveBeenCalledTimes(1)
expect(processedConnection.id).not.toBe(connectionRecord.id)
expect(connectionRecord.id).toBe('test')
expect(connectionRecord.state).toBe(ConnectionState.Invited)
expect(connectionRecord.state).toBe(DidExchangeState.InvitationSent)
})

it('throws an error when the message does not contain a did doc', async () => {
Expand Down Expand Up @@ -336,8 +343,8 @@ describe('ConnectionService', () => {
// Needed for signing connection~sig
const { did, verkey } = await wallet.createDid()
const mockConnection = getMockConnection({
state: ConnectionState.Requested,
role: ConnectionRole.Inviter,
state: DidExchangeState.RequestReceived,
role: DidExchangeRole.Responder,
tags: {
threadId: 'test',
},
Expand Down Expand Up @@ -384,33 +391,42 @@ describe('ConnectionService', () => {
})
const plainConnection = JsonTransformer.toJSON(connection)

expect(connectionRecord.state).toBe(ConnectionState.Responded)
expect(connectionRecord.state).toBe(DidExchangeState.ResponseSent)
expect(await unpackAndVerifySignatureDecorator(message.connectionSig, wallet)).toEqual(plainConnection)
})

it(`throws an error when connection role is ${ConnectionRole.Invitee} and not ${ConnectionRole.Inviter}`, async () => {
it(`throws an error when connection role is ${DidExchangeRole.Requester} and not ${DidExchangeRole.Responder}`, async () => {
expect.assertions(1)

const connection = getMockConnection({
role: ConnectionRole.Invitee,
state: ConnectionState.Requested,
role: DidExchangeRole.Requester,
state: DidExchangeState.RequestReceived,
})
const outOfBand = getMockOutOfBand()
return expect(connectionService.createResponse(connection, outOfBand)).rejects.toThrowError(
`Connection record has invalid role ${ConnectionRole.Invitee}. Expected role ${ConnectionRole.Inviter}.`
`Connection record has invalid role ${DidExchangeRole.Requester}. Expected role ${DidExchangeRole.Responder}.`
)
})

const invalidOutOfBandStates = [ConnectionState.Invited, ConnectionState.Responded, ConnectionState.Complete]
const invalidOutOfBandStates = [
DidExchangeState.InvitationSent,
DidExchangeState.InvitationReceived,
DidExchangeState.RequestSent,
DidExchangeState.ResponseSent,
DidExchangeState.ResponseReceived,
DidExchangeState.Completed,
DidExchangeState.Abandoned,
DidExchangeState.Start,
]
test.each(invalidOutOfBandStates)(
`throws an error when connection state is %s and not ${ConnectionState.Requested}`,
`throws an error when connection state is %s and not ${DidExchangeState.RequestReceived}`,
async (state) => {
expect.assertions(1)

const connection = getMockConnection({ state })
const outOfBand = getMockOutOfBand()
return expect(connectionService.createResponse(connection, outOfBand)).rejects.toThrowError(
`Connection record is in invalid state ${state}. Valid states are: ${ConnectionState.Requested}.`
`Connection record is in invalid state ${state}. Valid states are: ${DidExchangeState.RequestReceived}.`
)
}
)
Expand All @@ -425,8 +441,8 @@ describe('ConnectionService', () => {

const connectionRecord = getMockConnection({
did,
state: ConnectionState.Requested,
role: ConnectionRole.Invitee,
state: DidExchangeState.RequestSent,
role: DidExchangeRole.Requester,
})

const theirKey = Key.fromPublicKeyBase58(theirVerkey, KeyType.Ed25519)
Expand Down Expand Up @@ -479,17 +495,17 @@ describe('ConnectionService', () => {
convertToNewDidDocument(otherPartyConnection.didDoc!),
PeerDidNumAlgo.GenesisDoc
)
expect(processedConnection.state).toBe(ConnectionState.Responded)
expect(processedConnection.state).toBe(DidExchangeState.ResponseReceived)
expect(processedConnection.theirDid).toBe(peerDid.did)
})

it(`throws an error when connection role is ${ConnectionRole.Inviter} and not ${ConnectionRole.Invitee}`, async () => {
it(`throws an error when connection role is ${DidExchangeRole.Responder} and not ${DidExchangeRole.Requester}`, async () => {
expect.assertions(1)

const outOfBandRecord = getMockOutOfBand()
const connectionRecord = getMockConnection({
role: ConnectionRole.Inviter,
state: ConnectionState.Requested,
role: DidExchangeRole.Responder,
state: DidExchangeState.RequestSent,
})
const messageContext = new InboundMessageContext(jest.fn()(), {
connection: connectionRecord,
Expand All @@ -498,7 +514,7 @@ describe('ConnectionService', () => {
})

return expect(connectionService.processResponse(messageContext, outOfBandRecord)).rejects.toThrowError(
`Connection record has invalid role ${ConnectionRole.Inviter}. Expected role ${ConnectionRole.Invitee}.`
`Connection record has invalid role ${DidExchangeRole.Responder}. Expected role ${DidExchangeRole.Requester}.`
)
})

Expand All @@ -509,8 +525,8 @@ describe('ConnectionService', () => {
const { did: theirDid, verkey: theirVerkey } = await wallet.createDid()
const connectionRecord = getMockConnection({
did,
role: ConnectionRole.Invitee,
state: ConnectionState.Requested,
role: DidExchangeRole.Requester,
state: DidExchangeState.RequestSent,
})

const theirKey = Key.fromPublicKeyBase58(theirVerkey, KeyType.Ed25519)
Expand Down Expand Up @@ -571,7 +587,7 @@ describe('ConnectionService', () => {
const { did: theirDid, verkey: theirVerkey } = await wallet.createDid()
const connectionRecord = getMockConnection({
did,
state: ConnectionState.Requested,
state: DidExchangeState.RequestSent,
theirDid: undefined,
})

Expand Down Expand Up @@ -600,23 +616,31 @@ describe('ConnectionService', () => {
it('returns a trust ping message', async () => {
expect.assertions(2)

const mockConnection = getMockConnection({ state: ConnectionState.Responded })
const mockConnection = getMockConnection({ state: DidExchangeState.ResponseReceived })

const { message, connectionRecord: connectionRecord } = await connectionService.createTrustPing(mockConnection)

expect(connectionRecord.state).toBe(ConnectionState.Complete)
expect(connectionRecord.state).toBe(DidExchangeState.Completed)
expect(message).toEqual(expect.any(TrustPingMessage))
})

const invalidConnectionStates = [ConnectionState.Invited, ConnectionState.Requested]
const invalidConnectionStates = [
DidExchangeState.InvitationSent,
DidExchangeState.InvitationReceived,
DidExchangeState.RequestSent,
DidExchangeState.RequestReceived,
DidExchangeState.ResponseSent,
DidExchangeState.Abandoned,
DidExchangeState.Start,
]
test.each(invalidConnectionStates)(
`throws an error when connection state is %s and not ${ConnectionState.Responded} or ${ConnectionState.Complete}`,
`throws an error when connection state is %s and not ${DidExchangeState.ResponseReceived} or ${DidExchangeState.Completed}`,
(state) => {
expect.assertions(1)
const connection = getMockConnection({ state })

return expect(connectionService.createTrustPing(connection)).rejects.toThrowError(
`Connection record is in invalid state ${state}. Valid states are: ${ConnectionState.Responded}, ${ConnectionState.Complete}.`
`Connection record is in invalid state ${state}. Valid states are: ${DidExchangeState.ResponseReceived}, ${DidExchangeState.Completed}.`
)
}
)
Expand All @@ -638,12 +662,12 @@ describe('ConnectionService', () => {
)
})

it('updates the state to Completed when the state is Responded and role is Inviter', async () => {
it('updates the state to Completed when the state is ResponseSent and role is Responder', async () => {
expect.assertions(1)

const connection = getMockConnection({
state: ConnectionState.Responded,
role: ConnectionRole.Inviter,
state: DidExchangeState.ResponseSent,
role: DidExchangeRole.Responder,
})

const ack = new AckMessage({
Expand All @@ -655,15 +679,15 @@ describe('ConnectionService', () => {

const updatedConnection = await connectionService.processAck(messageContext)

expect(updatedConnection.state).toBe(ConnectionState.Complete)
expect(updatedConnection.state).toBe(DidExchangeState.Completed)
})

it('does not update the state when the state is not Responded or the role is not Inviter', async () => {
it('does not update the state when the state is not ResponseSent or the role is not Responder', async () => {
expect.assertions(1)

const connection = getMockConnection({
state: ConnectionState.Responded,
role: ConnectionRole.Invitee,
state: DidExchangeState.ResponseReceived,
role: DidExchangeRole.Requester,
})

const ack = new AckMessage({
Expand All @@ -675,7 +699,7 @@ describe('ConnectionService', () => {

const updatedConnection = await connectionService.processAck(messageContext)

expect(updatedConnection.state).toBe(ConnectionState.Responded)
expect(updatedConnection.state).toBe(DidExchangeState.ResponseReceived)
})
})

Expand All @@ -684,7 +708,7 @@ describe('ConnectionService', () => {
expect.assertions(1)

const messageContext = new InboundMessageContext(new AgentMessage(), {
connection: getMockConnection({ state: ConnectionState.Complete }),
connection: getMockConnection({ state: DidExchangeState.Completed }),
})

expect(() => connectionService.assertConnectionOrServiceDecorator(messageContext)).not.toThrow()
Expand All @@ -694,7 +718,7 @@ describe('ConnectionService', () => {
expect.assertions(1)

const messageContext = new InboundMessageContext(new AgentMessage(), {
connection: getMockConnection({ state: ConnectionState.Invited }),
connection: getMockConnection({ state: DidExchangeState.InvitationReceived }),
})

expect(() => connectionService.assertConnectionOrServiceDecorator(messageContext)).toThrowError(
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type { TrustPingService } from '../services/TrustPingService'

import { AriesFrameworkError } from '../../../error'
import { TrustPingMessage } from '../messages'
import { ConnectionState } from '../models'
import { DidExchangeState } from '../models'

export class TrustPingMessageHandler implements Handler {
private trustPingService: TrustPingService
Expand All @@ -24,8 +24,8 @@ export class TrustPingMessageHandler implements Handler {

// TODO: This is better addressed in a middleware of some kind because
// any message can transition the state to complete, not just an ack or trust ping
if (connection.state === ConnectionState.Responded) {
await this.connectionService.updateState(connection, ConnectionState.Complete)
if (connection.state === DidExchangeState.ResponseSent) {
await this.connectionService.updateState(connection, DidExchangeState.Completed)
}

return this.trustPingService.processPing(messageContext, connection)
Expand Down
21 changes: 19 additions & 2 deletions packages/core/src/modules/connections/models/ConnectionState.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,30 @@
import { DidExchangeState } from './DidExchangeState'

/**
* Connection states as defined in RFC 0160.
*
* State 'null' from RFC is changed to 'init'
*
* @see https://github.com/hyperledger/aries-rfcs/blob/master/features/0160-connection-protocol/README.md#states
*/
export enum ConnectionState {
Null = 'null',
Invited = 'invited',
Requested = 'requested',
Responded = 'responded',
Complete = 'complete',
}

export function rfc0160StateFromDidExchangeState(didExchangeState: DidExchangeState) {
const stateMapping = {
[DidExchangeState.Start]: ConnectionState.Null,
[DidExchangeState.Abandoned]: ConnectionState.Null,
[DidExchangeState.InvitationReceived]: ConnectionState.Invited,
[DidExchangeState.InvitationSent]: ConnectionState.Invited,
[DidExchangeState.RequestReceived]: ConnectionState.Requested,
[DidExchangeState.RequestSent]: ConnectionState.Requested,
[DidExchangeState.ResponseReceived]: ConnectionState.Responded,
[DidExchangeState.ResponseSent]: ConnectionState.Responded,
[DidExchangeState.Completed]: DidExchangeState.Completed,
}

return stateMapping[didExchangeState]
}
Loading

0 comments on commit 3068f32

Please sign in to comment.