Skip to content

Commit

Permalink
add capabilities to implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
achou11 committed Sep 5, 2023
1 parent c74f3a2 commit e26ff99
Show file tree
Hide file tree
Showing 2 changed files with 114 additions and 6 deletions.
20 changes: 16 additions & 4 deletions src/member-api.js
Original file line number Diff line number Diff line change
@@ -1,36 +1,46 @@
import { TypedEmitter } from 'tiny-typed-emitter'
import { InviteResponse_Decision } from './generated/rpc.js'

export class MemberApi extends TypedEmitter {
#capabilities
#encryptionKeys
#getProjectInfo
#projectKey
#rpc

/**
* @param {Object} opts
* @param {import('./capabilities.js').Capabilities} opts.capabilities
* @param {import('./generated/keys.js').EncryptionKeys} opts.encryptionKeys
* @param {() => Promise<import('./generated/rpc.js').Invite_ProjectInfo>} opts.getProjectInfo
* @param {Buffer} opts.projectKey
* @param {import('./rpc/index.js').MapeoRPC} opts.rpc
*/
constructor({ encryptionKeys, getProjectInfo, projectKey, rpc }) {
constructor({
capabilities,
encryptionKeys,
getProjectInfo,
projectKey,
rpc,
}) {
super()
this.#encryptionKeys = encryptionKeys
this.#getProjectInfo = getProjectInfo
this.#projectKey = projectKey
this.#rpc = rpc
this.#capabilities = capabilities
}

/**
* @param {string} deviceId
*
* @param {Object} opts
* @param {string} opts.roleId
* @param {import('./capabilities.js').RoleId} opts.roleId
* @param {number} [opts.timeout]
*
* @returns {Promise<import('./generated/rpc.js').InviteResponse_Decision>}
*/
async invite(deviceId, { timeout }) {
async invite(deviceId, { roleId, timeout }) {
const projectInfo = await this.#getProjectInfo()

const response = await this.#rpc.invite(deviceId, {
Expand All @@ -40,7 +50,9 @@ export class MemberApi extends TypedEmitter {
timeout,
})

// TODO: If response is ACCEPT, write to capabilities
if (response === InviteResponse_Decision.ACCEPT) {
await this.#capabilities.assignRole(deviceId, roleId)
}

return response
}
Expand Down
100 changes: 98 additions & 2 deletions tests/member-api.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { MemberApi } from '../src/member-api.js'
import { InviteResponse_Decision } from '../src/generated/rpc.js'
import { replicate } from './helpers/rpc.js'

test('Invite sends expected project-related details', async (t) => {
test('invite() sends expected project-related details', async (t) => {
t.plan(4)

const projectKey = KeyManager.generateProjectKeypair().publicKey
Expand All @@ -18,6 +18,7 @@ test('Invite sends expected project-related details', async (t) => {
const r2 = new MapeoRPC()

const memberApi = new MemberApi({
capabilities: { async assignRole() {} },
encryptionKeys,
getProjectInfo: async () => projectInfo,
projectKey,
Expand All @@ -32,7 +33,7 @@ test('Invite sends expected project-related details', async (t) => {
t.is(response, InviteResponse_Decision.ACCEPT)
})

r2.on('invite', async (peerId, invite) => {
r2.on('invite', (peerId, invite) => {
t.alike(invite.projectKey, projectKey)
t.alike(invite.encryptionKeys, encryptionKeys)
t.alike(invite.projectInfo, projectInfo)
Expand All @@ -45,3 +46,98 @@ test('Invite sends expected project-related details', async (t) => {

replicate(r1, r2)
})

test('invite() assigns role to invited device after invite accepted', async (t) => {
t.plan(4)

const r1 = new MapeoRPC()
const r2 = new MapeoRPC()

const expectedRoleId = randomBytes(8).toString('hex')
let expectedDeviceId = null

// We're only testing that this gets called with the expected arguments
const capabilities = {
async assignRole(deviceId, roleId) {
t.ok(expectedDeviceId)
t.is(deviceId, expectedDeviceId)
t.is(roleId, expectedRoleId)
},
}

const memberApi = new MemberApi({
capabilities,
encryptionKeys: { auth: randomBytes(32) },
getProjectInfo: async () => {},
projectKey: KeyManager.generateProjectKeypair().publicKey,
rpc: r1,
})

r1.on('peers', async (peers) => {
expectedDeviceId = peers[0].id

const response = await memberApi.invite(expectedDeviceId, {
roleId: expectedRoleId,
})

t.is(response, InviteResponse_Decision.ACCEPT)
})

r2.on('invite', (peerId, invite) => {
r2.inviteResponse(peerId, {
projectKey: invite.projectKey,
decision: InviteResponse_Decision.ACCEPT,
})
})

replicate(r1, r2)
})

test('invite() does not assign role to invited device if invite is not accepted', async (t) => {
const nonAcceptInviteDecisions = Object.values(
InviteResponse_Decision
).filter((d) => d !== InviteResponse_Decision.ACCEPT)

for (const decision of nonAcceptInviteDecisions) {
t.test(`${decision}`, (t) => {
t.plan(1)

const r1 = new MapeoRPC()
const r2 = new MapeoRPC()

const capabilities = {
// This should not be called at any point in this test
async assignRole() {
t.fail(
'Attempted to assign role despite decision being non-acceptance'
)
},
}

const memberApi = new MemberApi({
capabilities,
encryptionKeys: { auth: randomBytes(32) },
getProjectInfo: async () => {},
projectKey: KeyManager.generateProjectKeypair().publicKey,
rpc: r1,
})

r1.on('peers', async (peers) => {
const response = await memberApi.invite(peers[0].id, {
roleId: randomBytes(8).toString('hex'),
})

t.is(response, decision)
})

r2.on('invite', (peerId, invite) => {
r2.inviteResponse(peerId, {
projectKey: invite.projectKey,
decision,
})
})

replicate(r1, r2)
})
}
})

0 comments on commit e26ff99

Please sign in to comment.