Skip to content

Commit

Permalink
Merge branch 'main' into getByDocId-allow-missing
Browse files Browse the repository at this point in the history
  • Loading branch information
EvanHahn authored Nov 18, 2024
2 parents 56f7e18 + 6d6e7d7 commit b498e54
Show file tree
Hide file tree
Showing 4 changed files with 168 additions and 38 deletions.
55 changes: 30 additions & 25 deletions src/roles.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,33 @@ export const CREATOR_ROLE = {
},
}

/**
* @type {Role<typeof BLOCKED_ROLE_ID>}
*/
const BLOCKED_ROLE = {
roleId: BLOCKED_ROLE_ID,
name: 'Blocked',
docs: mapObject(currentSchemaVersions, (key) => {
return [
key,
{
readOwn: false,
writeOwn: false,
readOthers: false,
writeOthers: false,
},
]
}),
roleAssignment: [],
sync: {
auth: 'blocked',
config: 'blocked',
data: 'blocked',
blobIndex: 'blocked',
blob: 'blocked',
},
}

/**
* This is the role assumed for a device when no role record can be found. This
* can happen when an invited device did not manage to sync with the device that
Expand Down Expand Up @@ -166,29 +193,7 @@ export const ROLES = {
blob: 'allowed',
},
},
[BLOCKED_ROLE_ID]: {
roleId: BLOCKED_ROLE_ID,
name: 'Blocked',
docs: mapObject(currentSchemaVersions, (key) => {
return [
key,
{
readOwn: false,
writeOwn: false,
readOthers: false,
writeOthers: false,
},
]
}),
roleAssignment: [],
sync: {
auth: 'blocked',
config: 'blocked',
data: 'blocked',
blobIndex: 'blocked',
blob: 'blocked',
},
},
[BLOCKED_ROLE_ID]: BLOCKED_ROLE,
[LEFT_ROLE_ID]: {
roleId: LEFT_ROLE_ID,
name: 'Left',
Expand Down Expand Up @@ -281,7 +286,7 @@ export class Roles extends TypedEmitter {

const { roleId } = roleAssignment
if (!isRoleId(roleId)) {
return ROLES[BLOCKED_ROLE_ID]
return BLOCKED_ROLE
}
return ROLES[roleId]
}
Expand Down Expand Up @@ -403,7 +408,7 @@ export class Roles extends TypedEmitter {
}
}

async #isProjectCreator() {
#isProjectCreator() {
const ownAuthCoreId = this.#coreManager
.getWriterCore('auth')
.key.toString('hex')
Expand Down
136 changes: 123 additions & 13 deletions test-e2e/members.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { once } from 'node:events'
import {
COORDINATOR_ROLE_ID,
CREATOR_ROLE,
CREATOR_ROLE_ID,
ROLES,
MEMBER_ROLE_ID,
NO_ROLE,
Expand All @@ -18,6 +19,8 @@ import {
waitForSync,
} from './utils.js'
import { kDataTypes } from '../src/mapeo-project.js'
/** @import { MapeoProject } from '../src/mapeo-project.js' */
/** @import { RoleId } from '../src/roles.js' */

test('getting yourself after creating project', async (t) => {
const [manager] = await createManagers(1, t, 'tablet')
Expand Down Expand Up @@ -355,8 +358,8 @@ test('roles - getMany() on newly invited device before sync', async (t) => {
})

test('roles - assignRole()', async (t) => {
const managers = await createManagers(2, t)
const [invitor, invitee] = managers
const managers = await createManagers(3, t)
const [invitor, invitee, invitee2] = managers
const disconnectPeers = connectPeers(managers)
t.after(disconnectPeers)

Expand All @@ -365,21 +368,48 @@ test('roles - assignRole()', async (t) => {
await invite({
invitor,
projectId,
invitees: [invitee],
invitees: [invitee, invitee2],
roleId: MEMBER_ROLE_ID,
})

const projects = await Promise.all(
managers.map((m) => m.getProject(projectId))
)

const [invitorProject, inviteeProject] = projects
const [invitorProject, inviteeProject, invitee2Project] = projects

/**
* @param {MapeoProject} project
* @param {string} otherDeviceId
* @param {RoleId} expectedRoleId
* @param {string} message
* @returns {Promise<void>}
*/
const assertRole = async (
project,
otherDeviceId,
expectedRoleId,
message
) => {
assert.equal(
(await project.$member.getById(otherDeviceId)).role.roleId,
expectedRoleId,
message
)
}

assert.deepEqual(
(await invitorProject.$member.getById(invitee.deviceId)).role,
ROLES[MEMBER_ROLE_ID],
await assertRole(
invitorProject,
invitee.deviceId,
MEMBER_ROLE_ID,
'invitee has member role from invitor perspective'
)
await assertRole(
invitorProject,
invitee2.deviceId,
MEMBER_ROLE_ID,
'invitee 2 has member role from invitor perspective'
)

assert.deepEqual(
await inviteeProject.$getOwnRole(),
Expand Down Expand Up @@ -410,9 +440,10 @@ test('roles - assignRole()', async (t) => {

await waitForSync(projects, 'initial')

assert.deepEqual(
(await invitorProject.$member.getById(invitee.deviceId)).role,
ROLES[COORDINATOR_ROLE_ID],
await assertRole(
invitorProject,
invitee.deviceId,
COORDINATOR_ROLE_ID,
'invitee now has coordinator role from invitor perspective'
)

Expand Down Expand Up @@ -447,9 +478,10 @@ test('roles - assignRole()', async (t) => {

await waitForSync(projects, 'initial')

assert.deepEqual(
(await invitorProject.$member.getById(invitee.deviceId)).role,
ROLES[MEMBER_ROLE_ID],
await assertRole(
invitorProject,
invitee.deviceId,
MEMBER_ROLE_ID,
'invitee now has member role from invitor perspective'
)

Expand All @@ -459,6 +491,84 @@ test('roles - assignRole()', async (t) => {
'invitee now has member role from invitee perspective'
)
})

await t.test(
'regular members cannot assign roles to coordinator',
async () => {
await Promise.all(
[invitorProject, inviteeProject, invitee2Project].flatMap((project) => [
assertRole(
project,
invitee.deviceId,
MEMBER_ROLE_ID,
'test setup: everyone believes invitee 1 is a regular member'
),
assertRole(
project,
invitee2.deviceId,
MEMBER_ROLE_ID,
'test setup: everyone believes invitee 2 is a regular member'
),
])
)

await assert.rejects(() =>
inviteeProject.$member.assignRole(invitee.deviceId, COORDINATOR_ROLE_ID)
)
await assert.rejects(() =>
inviteeProject.$member.assignRole(
invitee2.deviceId,
COORDINATOR_ROLE_ID
)
)

await waitForSync(projects, 'initial')

await Promise.all(
[invitorProject, inviteeProject, invitee2Project].flatMap((project) => [
assertRole(
project,
invitee.deviceId,
MEMBER_ROLE_ID,
'everyone believes invitee 1 is a regular member, even after attempting to assign higher role'
),
assertRole(
project,
invitee2.deviceId,
MEMBER_ROLE_ID,
'everyone believes invitee 2 is a regular member, even after attempting to assign higher role'
),
])
)
}
)

await t.test(
'non-creator members cannot change roles of creator',
async () => {
await invitorProject.$member.assignRole(
invitee.deviceId,
COORDINATOR_ROLE_ID
)
await waitForSync(projects, 'initial')

await assert.rejects(() =>
inviteeProject.$member.assignRole(invitor.deviceId, COORDINATOR_ROLE_ID)
)

await waitForSync(projects, 'initial')
await Promise.all(
[invitorProject, inviteeProject, invitee2Project].map((project) =>
assertRole(
project,
invitor.deviceId,
CREATOR_ROLE_ID,
'everyone still believes creator to be a creator'
)
)
)
}
)
})

test('roles - assignRole() with forked role', async (t) => {
Expand Down
6 changes: 6 additions & 0 deletions test-e2e/sync.js
Original file line number Diff line number Diff line change
Expand Up @@ -855,6 +855,12 @@ test('Correct sync state prior to data sync', async function (t) {
managers.map((m) => m.getProject(projectId))
)

for (const project of projects) {
const { remoteDeviceSyncState } = project.$sync.getState()
const otherDeviceCount = Object.keys(remoteDeviceSyncState).length
assert.equal(otherDeviceCount, COUNT - 1)
}

const generated = await seedDatabases(projects, { schemas: ['observation'] })
await waitForSync(projects, 'initial')

Expand Down
9 changes: 9 additions & 0 deletions test/data-type.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,15 @@ test('private createWithDocId() method throws when doc exists', async () => {
)
})

test('getByVersionId fetches docs by their version ID', async () => {
const { dataType } = await testenv()

const created = await dataType.create(obsFixture)
const fetched = await dataType.getByVersionId(created.versionId)

assert.equal(created.docId, fetched.docId)
})

test('`originalVersionId` field', async () => {
const { dataType, dataStore } = await testenv()

Expand Down

0 comments on commit b498e54

Please sign in to comment.