Skip to content
This repository has been archived by the owner on Nov 7, 2024. It is now read-only.

Commit

Permalink
Eventual consistency fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
guillemcordoba committed Jul 4, 2024
1 parent c9877e6 commit 7321123
Show file tree
Hide file tree
Showing 17 changed files with 858 additions and 595 deletions.
30 changes: 15 additions & 15 deletions flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

551 changes: 257 additions & 294 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion tests/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "tests",
"private": true,
"scripts": {
"test": "vitest run"
"test": "RUST_LOG=error vitest run"
},
"dependencies": {
"@holochain-open-dev/signals": "^0.300.4",
Expand Down
127 changes: 87 additions & 40 deletions tests/src/assign-role-lifecycle.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { toPromise } from '@holochain-open-dev/signals';
import { HashType, retype } from '@holochain-open-dev/utils';
import { dhtSync, runScenario } from '@holochain/tryorama';
import { dhtSync, pause, runScenario } from '@holochain/tryorama';
import { assert, expect, test } from 'vitest';

import { RolesStore } from '../../ui/src/roles-store.js';
Expand All @@ -19,18 +19,23 @@ test('Assign role lifecycle', async () => {
await runScenario(async scenario => {
const { alice, bob } = await setup(scenario);

let roles = alice.store.allRoles;
assert.equal(roles.length, 2);
assert.equal(roles[0], 'admin');
assert.equal(roles[1], 'editor');

// Wait for the created entry to be propagated to the other node.
await dhtSync([alice.player, bob.player], alice.player.cells[0].cell_id[0]);
let roles = await toPromise(alice.store.allRolesWithAssignees);
assert.equal(roles.length, 1);
assert.equal(roles[0], 'admin');

await expect(() =>
createExampleEntryThatOnlyEditorsCanCreate(alice.store),
).rejects.toThrowError();

// Wait for the created entry to be propagated to the other node.
await dhtSync([alice.player, bob.player], alice.player.cells[0].cell_id[0]);
await waitUntil(
async () =>
(await toPromise(bob.store.assignees.get('admin'))).length === 1,
10000,
);

let admins = await toPromise(bob.store.assignees.get('admin'));
assert.equal(admins.length, 1);
Expand All @@ -51,10 +56,11 @@ test('Assign role lifecycle', async () => {
// Wait for the created entry to be propagated to the other node.
await dhtSync([alice.player, bob.player], alice.player.cells[0].cell_id[0]);

roles = await toPromise(bob.store.allRolesWithAssignees);
assert.equal(roles.length, 2);
assert.ok(roles.includes('admin'));
assert.ok(roles.includes('editor'));
await waitUntil(
async () =>
(await toPromise(bob.store.assignees.get('editor'))).length === 1,
10_000,
);

let editors = await toPromise(bob.store.assignees.get('editor'));
assert.equal(editors.length, 1);
Expand All @@ -63,6 +69,13 @@ test('Assign role lifecycle', async () => {
new Uint8Array(bob.player.agentPubKey).toString(),
);

await waitUntil(
async () =>
(await bob.store.client.queryUndeletedRoleClaimsForRole('editor'))
.length === 1,
40_000,
);

let roleClaims =
await bob.store.client.queryUndeletedRoleClaimsForRole('editor');
assert.equal(roleClaims.length, 1);
Expand All @@ -74,36 +87,39 @@ test('Assign role lifecycle', async () => {
bob.store.client.requestUnassignRole('editor', bob.player.agentPubKey),
).rejects.toThrowError();

let pendingUnassigments = await toPromise(bob.store.pendingUnassigments);
let pendingUnassigments = await toPromise(bob.store.pendingUnassignments);
assert.equal(pendingUnassigments.length, 0);

await alice.store.client.requestUnassignRole(
'editor',
bob.player.agentPubKey,
);

// Wait for the created entry to be propagated to the other node.
await dhtSync([alice.player, bob.player], alice.player.cells[0].cell_id[0]);

pendingUnassigments = await toPromise(bob.store.pendingUnassigments);
await pause(100);
pendingUnassigments = await toPromise(alice.store.pendingUnassignments);
assert.equal(pendingUnassigments.length, 1);
assert.equal(
retype(pendingUnassigments[0].target, HashType.AGENT).toString(),
new Uint8Array(bob.player.agentPubKey).toString(),
);

// Alice can't unassign bob's role
// Alice can't unassign Bob's role
await expect(() =>
alice.store.client.unassignMyRole(
pendingUnassigments[0].create_link_hash,
),
).rejects.toThrowError();
await dhtSync([alice.player, bob.player], alice.player.cells[0].cell_id[0]);

// Bob will delete their role claim automatically when getting assignees
await waitUntil(
async () =>
(await toPromise(bob.store.assignees.get('editor'))).length === 0,
10_000,
);

editors = await toPromise(bob.store.assignees.get('editor'));
assert.equal(editors.length, 0);

await waitUntil(async () => {
const roleClaims =
await bob.store.client.queryUndeletedRoleClaimsForRole('editor');
return roleClaims.length === 0;
}, 20_000);

roleClaims =
await bob.store.client.queryUndeletedRoleClaimsForRole('editor');
assert.equal(roleClaims.length, 0);
Expand All @@ -114,20 +130,36 @@ test('Assign role lifecycle', async () => {
});
});

async function waitUntil(condition: () => Promise<boolean>, timeout: number) {
const start = Date.now();
const isDone = await condition();
if (isDone) return;
if (timeout <= 0) throw new Error('timeout');
await pause(1000);
return waitUntil(condition, timeout - (Date.now() - start));
}

test('Admin can assign admin that assigns a role', async () => {
await runScenario(async scenario => {
const { alice, bob, carol } = await setup(scenario);

let roles = await toPromise(alice.store.allRolesWithAssignees);
assert.equal(roles.length, 1);
let roles = alice.store.allRoles;
assert.equal(roles.length, 2);
assert.equal(roles[0], 'admin');
assert.equal(roles[1], 'editor');

// Wait for the created entry to be propagated to the other node.
await dhtSync(
[alice.player, bob.player, carol.player],
alice.player.cells[0].cell_id[0],
);

await waitUntil(
async () =>
(await toPromise(bob.store.assignees.get('admin'))).length === 1,
30_000,
);

let admins = await toPromise(bob.store.assignees.get('admin'));
assert.equal(admins.length, 1);
assert.equal(
Expand All @@ -147,10 +179,16 @@ test('Admin can assign admin that assigns a role', async () => {
alice.player.cells[0].cell_id[0],
);

await waitUntil(
async () =>
(await toPromise(bob.store.assignees.get('admin'))).length === 2,
30_000,
);

admins = await toPromise(bob.store.assignees.get('admin'));
assert.equal(admins.length, 2);

let pendingUnassigments = await toPromise(bob.store.pendingUnassigments);
let pendingUnassigments = await toPromise(bob.store.pendingUnassignments);
assert.equal(pendingUnassigments.length, 0);

await expect(() =>
Expand All @@ -165,9 +203,16 @@ test('Admin can assign admin that assigns a role', async () => {
alice.player.cells[0].cell_id[0],
);

pendingUnassigments = await toPromise(carol.store.pendingUnassigments);
pendingUnassigments = await toPromise(carol.store.pendingUnassignments);
assert.equal(pendingUnassigments.length, 0);

await waitUntil(
async () =>
(await carol.store.client.queryUndeletedRoleClaimsForRole('editor'))
.length === 1,
20_000,
);

let editors = await toPromise(carol.store.assignees.get('editor'));
assert.equal(editors.length, 1);
assert.equal(
Expand All @@ -183,29 +228,31 @@ test('Admin can assign admin that assigns a role', async () => {
'editor',
carol.player.agentPubKey,
);

// Wait for the created entry to be propagated to the other node.
await dhtSync(
[alice.player, bob.player, carol.player],
alice.player.cells[0].cell_id[0],
);

pendingUnassigments = await toPromise(carol.store.pendingUnassigments);
await pause(100);
pendingUnassigments = await toPromise(bob.store.pendingUnassignments);
assert.equal(pendingUnassigments.length, 1);
assert.equal(
retype(pendingUnassigments[0].target, HashType.AGENT).toString(),
new Uint8Array(carol.player.agentPubKey).toString(),
);

// Bob can't unassign Carol's role
await expect(() =>
bob.store.client.unassignMyRole(pendingUnassigments[0].create_link_hash),
).rejects.toThrowError();

// Carol will delete their role claim automatically when getting assignees
await waitUntil(
async () =>
(await toPromise(carol.store.assignees.get('editor'))).length === 0,
10_000,
);

// Carol will filter out their role claim automatically when getting assignees
editors = await toPromise(carol.store.assignees.get('editor'));
assert.equal(editors.length, 0);

await waitUntil(async () => {
const roleClaims =
await carol.store.client.queryUndeletedRoleClaimsForRole('editor');
return roleClaims.length === 0;
}, 30_000);

roleClaims =
await bob.store.client.queryUndeletedRoleClaimsForRole('editor');
assert.equal(roleClaims.length, 0);
Expand Down
41 changes: 37 additions & 4 deletions tests/src/setup.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
import { AppBundle, encodeHashToBase64 } from '@holochain/client';
import { AgentApp, Scenario, enableAndGetAgentApp } from '@holochain/tryorama';
import { decode, encode } from '@msgpack/msgpack';
import { decompressSync, unzipSync } from 'fflate';
import {
AgentApp,
Scenario,
enableAndGetAgentApp,
pause,
} from '@holochain/tryorama';
import { decode } from '@msgpack/msgpack';
import { decompressSync } from 'fflate';
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';

import { RolesClient } from '../../ui/src/roles-client.js';
import { RolesStore } from '../../ui/src/roles-store.js';
import { RolesStore, RolesStoreConfig } from '../../ui/src/roles-store.js';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
Expand Down Expand Up @@ -59,16 +64,44 @@ export async function setup(scenario: Scenario) {
{ appBundleSource },
]);

await aliceConductor
.adminWs()
.authorizeSigningCredentials(
(Object.values(appInfo.cell_info)[0][0] as any).provisioned.cell_id,
);

await bob.conductor
.adminWs()
.authorizeSigningCredentials(bob.cells[0].cell_id);

await carol.conductor
.adminWs()
.authorizeSigningCredentials(carol.cells[0].cell_id);

const config: RolesStoreConfig = {
roles_config: [
{
role: 'editor',
description: 'An editor role can create special entries',
plural_name: 'editors',
singular_name: 'editor',
},
],
};

const aliceStore = new RolesStore(
new RolesClient(appWs as any, 'roles_test', 'roles'),
config,
);

const bobStore = new RolesStore(
new RolesClient(bob.appWs as any, 'roles_test', 'roles'),
config,
);

const carolStore = new RolesStore(
new RolesClient(carol.appWs as any, 'roles_test', 'roles'),
config,
);

// Shortcut peer discovery through gossip and register all agents in every
Expand Down
Loading

0 comments on commit 7321123

Please sign in to comment.