From 5ce148f485a7b78c93359bcbde8ab8ae4d059987 Mon Sep 17 00:00:00 2001 From: Marc-Antoine Parent Date: Thu, 8 Jan 2026 12:33:52 -0500 Subject: [PATCH 01/11] group function tests --- .../database/features/groupAccess.feature | 36 +++++++++++++ .../features/step-definitions/stepdefs.ts | 50 +++++++++++++++++++ 2 files changed, 86 insertions(+) create mode 100644 packages/database/features/groupAccess.feature diff --git a/packages/database/features/groupAccess.feature b/packages/database/features/groupAccess.feature new file mode 100644 index 000000000..075c7ce42 --- /dev/null +++ b/packages/database/features/groupAccess.feature @@ -0,0 +1,36 @@ +Feature: Group content access + User story: + * As a user of the Obsidian plugin + * Logged in through a given space's anonymous account + * I want to be able to create a group including another user outside my space + * giving that user access to my private content + + Acceptance criteria: + * The second user should not have access to the content before I publish my content to the group + * The second user should have access after I publish my content to the group + + Background: + Given the database is blank + And the user user1 opens the Roam plugin in space s1 + And the user user2 opens the Roam plugin in space s2 + + Scenario Outline: Creating content + When Document are added to the database: + | $id | source_local_id | created | last_modified | _author_id | _space_id | + | d1 | ld1 | 2025/01/01 | 2025/01/01 | user1 | s1 | + And Content are added to the database: + | $id | source_local_id | _document_id | text | created | last_modified | scale | _author_id | _space_id | + | ct1 | lct1 | d1 | Claim | 2025/01/01 | 2025/01/01 | document | user1 | s1 | + Then a user logged in space s1 should see 2 PlatformAccount in the database + And a user logged in space s1 should see 1 Content in the database + And a user logged in space s2 should see 2 PlatformAccount in the database + But a user logged in space s2 should see 0 Content in the database + When user of space s1 creates group my_group + And user of space s1 adds space s2 to group my_group + Then a user logged in space s1 should see 1 Content in the database + But a user logged in space s2 should see 0 Content in the database + And ContentAccess are added to the database: + | _account_uid | _content_id | + | my_group | ct1 | + Then a user logged in space s1 should see 1 Content in the database + Then a user logged in space s2 should see 1 Content in the database diff --git a/packages/database/features/step-definitions/stepdefs.ts b/packages/database/features/step-definitions/stepdefs.ts index c7c8bb903..699171475 100644 --- a/packages/database/features/step-definitions/stepdefs.ts +++ b/packages/database/features/step-definitions/stepdefs.ts @@ -64,6 +64,19 @@ Given("the database is blank", async () => { assert.equal(r.error, null); r = await client.from("AgentIdentifier").delete().neq("account_id", -1); assert.equal(r.error, null); + const r3 = await client.from("group_membership").select("group_id"); + assert.equal(r3.error, null); + const groupIds = new Set((r3.data || []).map(({group_id})=>group_id)); + for (const id of groupIds) { + const ur = await client.auth.admin.deleteUser(id); + assert.equal(ur.error, null); + } + const r2 = await client.from("PlatformAccount").select("dg_account").not('dg_account', 'is', 'null'); + assert.equal(r2.error, null); + for (const {dg_account} of r2.data || []) { + const r = await client.auth.admin.deleteUser(dg_account!); + assert.equal(r.error, null); + } r = await client.from("PlatformAccount").delete().neq("id", -1); assert.equal(r.error, null); r = await client.from("Space").delete().neq("id", -1); @@ -389,3 +402,40 @@ Then("query results should look like this", (table: DataTable) => { assert.deepEqual(truncatedResults, values); } }); + +When("user of space {word} creates group {word}", async (spaceName: string, name: string)=>{ + const localRefs = (world.localRefs || {}) as Record; + const spaceId = localRefs[spaceName]; + if (spaceId === undefined) assert.fail("spaceId"); + const client = await getLoggedinDatabase(spaceId as number); + try{ + const response = await client.functions.invoke<{group_id: string}>("create-group", {body:{name}}); + assert.equal(response.error, null); + localRefs[name] = response.data!.group_id; + } catch (error) { + console.error((error as any).actual); + throw error; + } +}) + +When("user of space {word} adds space {word} to group {word}", + async (space1Name: string, space2Name:string, groupName: string)=>{ + const localRefs = (world.localRefs || {}) as Record; + const space1Id = localRefs[space1Name] as number; + const space2Id = localRefs[space2Name] as number; + const groupId = localRefs[groupName] as string; + if (space1Id === undefined) assert.fail("space1Id"); + if (space2Id === undefined) assert.fail("space2Id"); + if (groupId === undefined) assert.fail("groupId"); + const client1 = await getLoggedinDatabase(space1Id as number); + const client2 = await getLoggedinDatabase(space2Id as number); + const r1 = await client2.from("PlatformAccount").select("dg_account").eq("account_local_id", spaceAnonUserEmail("Roam", space2Id)).maybeSingle(); + assert.equal(r1.error, null); + const memberId = r1.data?.dg_account; + assert(!!memberId); + const r2 = await client1.from("group_membership").insert({ + group_id: groupId, + member_id: memberId! + }); + assert.equal(r2.error, null); +}) From 313c174c735b8a2e4421e6af446a30af7743113b Mon Sep 17 00:00:00 2001 From: Marc-Antoine Parent Date: Sat, 17 Jan 2026 16:53:51 -0500 Subject: [PATCH 02/11] adjust to new ContentAccess --- packages/database/features/groupAccess.feature | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/database/features/groupAccess.feature b/packages/database/features/groupAccess.feature index 075c7ce42..d5c71a67d 100644 --- a/packages/database/features/groupAccess.feature +++ b/packages/database/features/groupAccess.feature @@ -30,7 +30,7 @@ Feature: Group content access Then a user logged in space s1 should see 1 Content in the database But a user logged in space s2 should see 0 Content in the database And ContentAccess are added to the database: - | _account_uid | _content_id | - | my_group | ct1 | + | _account_uid | _space_id | source_local_id | + | my_group | s1 | lct1 | Then a user logged in space s1 should see 1 Content in the database Then a user logged in space s2 should see 1 Content in the database From 19ab5cc9440a5fa38e0d811bfa231d751bd44ae9 Mon Sep 17 00:00:00 2001 From: Marc-Antoine Parent Date: Mon, 19 Jan 2026 09:32:00 -0500 Subject: [PATCH 03/11] coderabbit correction, linting --- .../database/features/step-definitions/stepdefs.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/packages/database/features/step-definitions/stepdefs.ts b/packages/database/features/step-definitions/stepdefs.ts index 699171475..105cd01a4 100644 --- a/packages/database/features/step-definitions/stepdefs.ts +++ b/packages/database/features/step-definitions/stepdefs.ts @@ -71,7 +71,7 @@ Given("the database is blank", async () => { const ur = await client.auth.admin.deleteUser(id); assert.equal(ur.error, null); } - const r2 = await client.from("PlatformAccount").select("dg_account").not('dg_account', 'is', 'null'); + const r2 = await client.from("PlatformAccount").select("dg_account").not('dg_account', 'is', null); assert.equal(r2.error, null); for (const {dg_account} of r2.data || []) { const r = await client.auth.admin.deleteUser(dg_account!); @@ -409,11 +409,13 @@ When("user of space {word} creates group {word}", async (spaceName: string, name if (spaceId === undefined) assert.fail("spaceId"); const client = await getLoggedinDatabase(spaceId as number); try{ + // eslint-disable-next-line @typescript-eslint/naming-convention const response = await client.functions.invoke<{group_id: string}>("create-group", {body:{name}}); assert.equal(response.error, null); + assert.notStrictEqual(response.data, null); localRefs[name] = response.data!.group_id; } catch (error) { - console.error((error as any).actual); + console.error((error as Record).actual); throw error; } }) @@ -427,15 +429,17 @@ When("user of space {word} adds space {word} to group {word}", if (space1Id === undefined) assert.fail("space1Id"); if (space2Id === undefined) assert.fail("space2Id"); if (groupId === undefined) assert.fail("groupId"); - const client1 = await getLoggedinDatabase(space1Id as number); - const client2 = await getLoggedinDatabase(space2Id as number); + const client2 = await getLoggedinDatabase(space2Id); const r1 = await client2.from("PlatformAccount").select("dg_account").eq("account_local_id", spaceAnonUserEmail("Roam", space2Id)).maybeSingle(); assert.equal(r1.error, null); const memberId = r1.data?.dg_account; assert(!!memberId); + const client1 = await getLoggedinDatabase(space1Id); const r2 = await client1.from("group_membership").insert({ + /* eslint-disable @typescript-eslint/naming-convention */ group_id: groupId, - member_id: memberId! + member_id: memberId + /* eslint-enable @typescript-eslint/naming-convention */ }); assert.equal(r2.error, null); }) From b47811eb34183faf868607c7c30572ebed3f4361 Mon Sep 17 00:00:00 2001 From: Marc-Antoine Parent Date: Mon, 19 Jan 2026 09:45:00 -0500 Subject: [PATCH 04/11] coderabbit correction --- packages/database/features/groupAccess.feature | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/database/features/groupAccess.feature b/packages/database/features/groupAccess.feature index d5c71a67d..8f38a98c1 100644 --- a/packages/database/features/groupAccess.feature +++ b/packages/database/features/groupAccess.feature @@ -14,7 +14,7 @@ Feature: Group content access And the user user1 opens the Roam plugin in space s1 And the user user2 opens the Roam plugin in space s2 - Scenario Outline: Creating content + Scenario: Creating content When Document are added to the database: | $id | source_local_id | created | last_modified | _author_id | _space_id | | d1 | ld1 | 2025/01/01 | 2025/01/01 | user1 | s1 | From 51530db94cf43e7da7acb3f44eff9a4dfae809b3 Mon Sep 17 00:00:00 2001 From: Marc-Antoine Parent Date: Mon, 19 Jan 2026 21:47:24 -0500 Subject: [PATCH 05/11] Account for rename --- packages/database/features/groupAccess.feature | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/database/features/groupAccess.feature b/packages/database/features/groupAccess.feature index 8f38a98c1..1e70b7de5 100644 --- a/packages/database/features/groupAccess.feature +++ b/packages/database/features/groupAccess.feature @@ -29,7 +29,7 @@ Feature: Group content access And user of space s1 adds space s2 to group my_group Then a user logged in space s1 should see 1 Content in the database But a user logged in space s2 should see 0 Content in the database - And ContentAccess are added to the database: + And ResourceAccess are added to the database: | _account_uid | _space_id | source_local_id | | my_group | s1 | lct1 | Then a user logged in space s1 should see 1 Content in the database From 28bb8b2224101e8515d66960d5105b769dea2084 Mon Sep 17 00:00:00 2001 From: Marc-Antoine Parent Date: Tue, 20 Jan 2026 08:49:19 -0500 Subject: [PATCH 06/11] coderabbit nits --- .../features/step-definitions/stepdefs.ts | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/database/features/step-definitions/stepdefs.ts b/packages/database/features/step-definitions/stepdefs.ts index 105cd01a4..9a7df52d0 100644 --- a/packages/database/features/step-definitions/stepdefs.ts +++ b/packages/database/features/step-definitions/stepdefs.ts @@ -412,8 +412,8 @@ When("user of space {word} creates group {word}", async (spaceName: string, name // eslint-disable-next-line @typescript-eslint/naming-convention const response = await client.functions.invoke<{group_id: string}>("create-group", {body:{name}}); assert.equal(response.error, null); - assert.notStrictEqual(response.data, null); - localRefs[name] = response.data!.group_id; + assert.ok(response.data?.group_id, "create-group response missing group_id"); + localRefs[name] = response.data.group_id; } catch (error) { console.error((error as Record).actual); throw error; @@ -423,21 +423,21 @@ When("user of space {word} creates group {word}", async (spaceName: string, name When("user of space {word} adds space {word} to group {word}", async (space1Name: string, space2Name:string, groupName: string)=>{ const localRefs = (world.localRefs || {}) as Record; - const space1Id = localRefs[space1Name] as number; - const space2Id = localRefs[space2Name] as number; - const groupId = localRefs[groupName] as string; - if (space1Id === undefined) assert.fail("space1Id"); - if (space2Id === undefined) assert.fail("space2Id"); - if (groupId === undefined) assert.fail("groupId"); - const client2 = await getLoggedinDatabase(space2Id); + const space1Id = localRefs[space1Name]; + const space2Id = localRefs[space2Name]; + const groupId = localRefs[groupName]; + if (space1Id === undefined) assert.fail("space1Id not found"); + if (space2Id === undefined) assert.fail("space2Id not found"); + if (groupId === undefined) assert.fail("groupId not found"); + const client2 = await getLoggedinDatabase(space2Id as number); const r1 = await client2.from("PlatformAccount").select("dg_account").eq("account_local_id", spaceAnonUserEmail("Roam", space2Id)).maybeSingle(); assert.equal(r1.error, null); const memberId = r1.data?.dg_account; - assert(!!memberId); - const client1 = await getLoggedinDatabase(space1Id); + assert.ok(memberId, "memberId not found for space2"); + const client1 = await getLoggedinDatabase(space1Id as number); const r2 = await client1.from("group_membership").insert({ /* eslint-disable @typescript-eslint/naming-convention */ - group_id: groupId, + group_id: groupId as string, member_id: memberId /* eslint-enable @typescript-eslint/naming-convention */ }); From c2a1855100cce8a827388ff3489374f421ef8468 Mon Sep 17 00:00:00 2001 From: Marc-Antoine Parent Date: Tue, 20 Jan 2026 08:54:25 -0500 Subject: [PATCH 07/11] forgot a cast --- packages/database/features/step-definitions/stepdefs.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/database/features/step-definitions/stepdefs.ts b/packages/database/features/step-definitions/stepdefs.ts index 9a7df52d0..086dc657f 100644 --- a/packages/database/features/step-definitions/stepdefs.ts +++ b/packages/database/features/step-definitions/stepdefs.ts @@ -430,7 +430,10 @@ When("user of space {word} adds space {word} to group {word}", if (space2Id === undefined) assert.fail("space2Id not found"); if (groupId === undefined) assert.fail("groupId not found"); const client2 = await getLoggedinDatabase(space2Id as number); - const r1 = await client2.from("PlatformAccount").select("dg_account").eq("account_local_id", spaceAnonUserEmail("Roam", space2Id)).maybeSingle(); + const r1 = await client2.from("PlatformAccount") + .select("dg_account") + .eq("account_local_id", spaceAnonUserEmail("Roam", space2Id as number)) + .maybeSingle(); assert.equal(r1.error, null); const memberId = r1.data?.dg_account; assert.ok(memberId, "memberId not found for space2"); From 3022fcc4e882511075a4cd9324ff52d5ece42662 Mon Sep 17 00:00:00 2001 From: Marc-Antoine Parent Date: Tue, 20 Jan 2026 10:03:13 -0500 Subject: [PATCH 08/11] better type checking --- .../features/step-definitions/stepdefs.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/database/features/step-definitions/stepdefs.ts b/packages/database/features/step-definitions/stepdefs.ts index 086dc657f..eaacd78b2 100644 --- a/packages/database/features/step-definitions/stepdefs.ts +++ b/packages/database/features/step-definitions/stepdefs.ts @@ -403,7 +403,7 @@ Then("query results should look like this", (table: DataTable) => { } }); -When("user of space {word} creates group {word}", async (spaceName: string, name: string)=>{ +When("user of space {word} creates group {word}", async (spaceName: string, name: string) => { const localRefs = (world.localRefs || {}) as Record; const spaceId = localRefs[spaceName]; if (spaceId === undefined) assert.fail("spaceId"); @@ -421,26 +421,26 @@ When("user of space {word} creates group {word}", async (spaceName: string, name }) When("user of space {word} adds space {word} to group {word}", - async (space1Name: string, space2Name:string, groupName: string)=>{ + async (space1Name: string, space2Name:string, groupName: string): Promise =>{ const localRefs = (world.localRefs || {}) as Record; const space1Id = localRefs[space1Name]; const space2Id = localRefs[space2Name]; const groupId = localRefs[groupName]; - if (space1Id === undefined) assert.fail("space1Id not found"); - if (space2Id === undefined) assert.fail("space2Id not found"); - if (groupId === undefined) assert.fail("groupId not found"); - const client2 = await getLoggedinDatabase(space2Id as number); + if (typeof space1Id !== 'number') assert.fail("space1Id not a number"); + if (typeof space2Id !== 'number') assert.fail("space2Id not a number"); + if (typeof groupId !== 'string') assert.fail("groupId not a string"); + const client2 = await getLoggedinDatabase(space2Id); const r1 = await client2.from("PlatformAccount") .select("dg_account") - .eq("account_local_id", spaceAnonUserEmail("Roam", space2Id as number)) + .eq("account_local_id", spaceAnonUserEmail("Roam", space2Id)) .maybeSingle(); assert.equal(r1.error, null); const memberId = r1.data?.dg_account; assert.ok(memberId, "memberId not found for space2"); - const client1 = await getLoggedinDatabase(space1Id as number); + const client1 = await getLoggedinDatabase(space1Id); const r2 = await client1.from("group_membership").insert({ /* eslint-disable @typescript-eslint/naming-convention */ - group_id: groupId as string, + group_id: groupId, member_id: memberId /* eslint-enable @typescript-eslint/naming-convention */ }); From 9f264bb5f73462344a29223d06e0d882b5900c17 Mon Sep 17 00:00:00 2001 From: Marc-Antoine Parent Date: Tue, 20 Jan 2026 11:51:10 -0500 Subject: [PATCH 09/11] Uniform handling of localRefs type --- .../features/step-definitions/stepdefs.ts | 49 ++++++++++--------- 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/packages/database/features/step-definitions/stepdefs.ts b/packages/database/features/step-definitions/stepdefs.ts index eaacd78b2..4917d56e8 100644 --- a/packages/database/features/step-definitions/stepdefs.ts +++ b/packages/database/features/step-definitions/stepdefs.ts @@ -19,6 +19,7 @@ import { type Platform = Enums<"Platform">; type TableName = keyof Database["public"]["Tables"]; +type LocalRefsType = Record; const PLATFORMS: readonly Platform[] = Constants.public.Enums.Platform; if (getVariant() === "production") { @@ -88,7 +89,7 @@ Given("the database is blank", async () => { const substituteLocalReferences = ( obj: any, - localRefs: Record, + localRefs: LocalRefsType, prefixValue: boolean = false, ): any => { const substituteLocalReferencesRec = (v: any): any => { @@ -115,7 +116,7 @@ const substituteLocalReferences = ( const substituteLocalReferencesRow = ( row: Record, - localRefs: Record, + localRefs: LocalRefsType, ): Record => { const processKV = ([k, v]: [string, any]): [string, any] => { const isJson = k.charAt(0) === "@"; @@ -147,7 +148,7 @@ Given( // Columns prefixed with _ are translated back from aliases to db ids. // Columns prefixed with @ are parsed as json values. (Use @ before _) const client = getServiceClient(); - const localRefs = (world.localRefs || {}) as Record; + const localRefs = (world.localRefs || {}) as LocalRefsType; const rows = table.hashes(); const values: Record[] = rows.map((r) => substituteLocalReferencesRow(r, localRefs), @@ -199,7 +200,7 @@ When( // assumption: turbo dev is running. TODO: Make into hooks if (PLATFORMS.indexOf(platform) < 0) throw new Error(`Platform must be one of ${PLATFORMS.join(", ")}`); - const localRefs = (world.localRefs || {}) as Record; + const localRefs = (world.localRefs || {}) as LocalRefsType; const spaceResponse = await fetchOrCreateSpaceDirect({ password: SPACE_ANONYMOUS_PASSWORD, url: `https://roamresearch.com/#/app/${spaceName}`, @@ -259,9 +260,9 @@ const getLoggedinDatabase = async (spaceId: number) => { Then( "a user logged in space {word} should see a {word} in the database", async (spaceName: string, tableName: TableName) => { - const localRefs = (world.localRefs || {}) as Record; + const localRefs = (world.localRefs || {}) as LocalRefsType; const spaceId = localRefs[spaceName]; - if (spaceId === undefined) assert.fail("spaceId"); + if (typeof spaceId !== "number") assert.fail("spaceId not a number"); const client = await getLoggedinDatabase(spaceId); const response = await client .from(tableName) @@ -274,9 +275,9 @@ Then( Then( "a user logged in space {word} should see {int} {word} in the database", async (spaceName: string, expectedCount: number, tableName: TableName) => { - const localRefs = (world.localRefs || {}) as Record; + const localRefs = (world.localRefs || {}) as LocalRefsType; const spaceId = localRefs[spaceName]; - if (spaceId === undefined) assert.fail("spaceId"); + if (typeof spaceId !== "number") assert.fail("spaceId not a number"); const client = await getLoggedinDatabase(spaceId); const response = await client .from(tableName) @@ -290,9 +291,9 @@ Given( "user {word} upserts these accounts to space {word}:", async (userName: string, spaceName: string, accountsString: string) => { const accounts = JSON.parse(accountsString) as Json; - const localRefs = (world.localRefs || {}) as Record; + const localRefs = (world.localRefs || {}) as LocalRefsType; const spaceId = localRefs[spaceName]; - if (spaceId === undefined) assert.fail("spaceId"); + if (typeof spaceId !== "number") assert.fail("spaceId not a number"); const client = await getLoggedinDatabase(spaceId); const response = await client.rpc("upsert_accounts_in_space", { space_id_: spaceId, // eslint-disable-line @typescript-eslint/naming-convention @@ -307,9 +308,9 @@ Given( "user {word} upserts these documents to space {word}:", async (userName: string, spaceName: string, docString: string) => { const data = JSON.parse(docString) as Json; - const localRefs = (world.localRefs || {}) as Record; + const localRefs = (world.localRefs || {}) as LocalRefsType; const spaceId = localRefs[spaceName]; - if (spaceId === undefined) assert.fail("spaceId"); + if (typeof spaceId !== "number") assert.fail("spaceId not a number"); const client = await getLoggedinDatabase(spaceId); const response = await client.rpc("upsert_documents", { v_space_id: spaceId, // eslint-disable-line @typescript-eslint/naming-convention @@ -324,11 +325,11 @@ Given( "user {word} upserts this content to space {word}:", async (userName: string, spaceName: string, docString: string) => { const data = JSON.parse(docString) as Json; - const localRefs = (world.localRefs || {}) as Record; + const localRefs = (world.localRefs || {}) as LocalRefsType; const spaceId = localRefs[spaceName]; - if (spaceId === undefined) assert.fail("spaceId"); + if (typeof spaceId !== "number") assert.fail("spaceId not a number"); const userId = localRefs[userName]; - if (userId === undefined) assert.fail("userId"); + if (typeof userId !== "number") assert.fail("userId not a number"); const client = await getLoggedinDatabase(spaceId); const response = await client.rpc("upsert_content", { v_space_id: spaceId, // eslint-disable-line @typescript-eslint/naming-convention @@ -345,9 +346,9 @@ Given( "user {word} upserts these concepts to space {word}:", async (userName: string, spaceName: string, docString: string) => { const data = JSON.parse(docString) as Json; - const localRefs = (world.localRefs || {}) as Record; + const localRefs = (world.localRefs || {}) as LocalRefsType; const spaceId = localRefs[spaceName]; - if (spaceId === undefined) assert.fail("spaceId"); + if (typeof spaceId !== "number") assert.fail("spaceId not a number"); const client = await getLoggedinDatabase(spaceId); const response = await client.rpc("upsert_concepts", { v_space_id: spaceId, // eslint-disable-line @typescript-eslint/naming-convention @@ -361,14 +362,14 @@ Given( "a user logged in space {word} and calling getConcepts with these parameters: {string}", async (spaceName: string, paramsJ: string) => { // params are assumed to be Json. Values prefixed with '@' are interpreted as aliases. - const localRefs = (world.localRefs || {}) as Record; + const localRefs = (world.localRefs || {}) as LocalRefsType; const params = substituteLocalReferences( JSON.parse(paramsJ), localRefs, true, ) as object; const spaceId = localRefs[spaceName]; - if (spaceId === undefined) assert.fail("spaceId"); + if (typeof spaceId !== "number") assert.fail("spaceId not a number"); const supabase = await getLoggedinDatabase(spaceId); // note that we supply spaceId and supabase, they do not need to be part of the incoming Json const nodes = await getConcepts({ ...params, supabase, spaceId }); @@ -380,7 +381,7 @@ Given( type ObjectWithId = object & { id: number }; Then("query results should look like this", (table: DataTable) => { - const localRefs = (world.localRefs || {}) as Record; + const localRefs = (world.localRefs || {}) as LocalRefsType; const rows = table.hashes(); const values = rows.map((r) => substituteLocalReferencesRow(r, localRefs), @@ -404,10 +405,10 @@ Then("query results should look like this", (table: DataTable) => { }); When("user of space {word} creates group {word}", async (spaceName: string, name: string) => { - const localRefs = (world.localRefs || {}) as Record; + const localRefs = (world.localRefs || {}) as LocalRefsType; const spaceId = localRefs[spaceName]; - if (spaceId === undefined) assert.fail("spaceId"); - const client = await getLoggedinDatabase(spaceId as number); + if (typeof spaceId !== "number") assert.fail("spaceId not a number"); + const client = await getLoggedinDatabase(spaceId); try{ // eslint-disable-next-line @typescript-eslint/naming-convention const response = await client.functions.invoke<{group_id: string}>("create-group", {body:{name}}); @@ -422,7 +423,7 @@ When("user of space {word} creates group {word}", async (spaceName: string, name When("user of space {word} adds space {word} to group {word}", async (space1Name: string, space2Name:string, groupName: string): Promise =>{ - const localRefs = (world.localRefs || {}) as Record; + const localRefs = (world.localRefs || {}) as LocalRefsType; const space1Id = localRefs[space1Name]; const space2Id = localRefs[space2Name]; const groupId = localRefs[groupName]; From 7a9c73b8032eae00b601cef398963b777e9c21e9 Mon Sep 17 00:00:00 2001 From: Marc-Antoine Parent Date: Tue, 20 Jan 2026 11:52:19 -0500 Subject: [PATCH 10/11] persist localRefs --- packages/database/features/step-definitions/stepdefs.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/database/features/step-definitions/stepdefs.ts b/packages/database/features/step-definitions/stepdefs.ts index 4917d56e8..e0636c4c0 100644 --- a/packages/database/features/step-definitions/stepdefs.ts +++ b/packages/database/features/step-definitions/stepdefs.ts @@ -415,6 +415,7 @@ When("user of space {word} creates group {word}", async (spaceName: string, name assert.equal(response.error, null); assert.ok(response.data?.group_id, "create-group response missing group_id"); localRefs[name] = response.data.group_id; + world.localRefs = localRefs; } catch (error) { console.error((error as Record).actual); throw error; From 524bb13e94fe31a71f29f879ce99f093c012aa0d Mon Sep 17 00:00:00 2001 From: Marc-Antoine Parent Date: Sun, 25 Jan 2026 09:59:11 -0500 Subject: [PATCH 11/11] Add an unshared content, give partal access to Space --- packages/database/features/groupAccess.feature | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/packages/database/features/groupAccess.feature b/packages/database/features/groupAccess.feature index 1e70b7de5..2819cb0c9 100644 --- a/packages/database/features/groupAccess.feature +++ b/packages/database/features/groupAccess.feature @@ -18,19 +18,26 @@ Feature: Group content access When Document are added to the database: | $id | source_local_id | created | last_modified | _author_id | _space_id | | d1 | ld1 | 2025/01/01 | 2025/01/01 | user1 | s1 | + | d2 | ld2 | 2025/01/01 | 2025/01/01 | user1 | s1 | And Content are added to the database: - | $id | source_local_id | _document_id | text | created | last_modified | scale | _author_id | _space_id | - | ct1 | lct1 | d1 | Claim | 2025/01/01 | 2025/01/01 | document | user1 | s1 | + | $id | source_local_id | _document_id | text | created | last_modified | scale | _author_id | _space_id | + | ct1 | lct1 | d1 | Claim 1 | 2025/01/01 | 2025/01/01 | document | user1 | s1 | + | ct2 | lct2 | d2 | Claim 2 | 2025/01/01 | 2025/01/01 | document | user1 | s1 | Then a user logged in space s1 should see 2 PlatformAccount in the database - And a user logged in space s1 should see 1 Content in the database + And a user logged in space s1 should see 2 Content in the database And a user logged in space s2 should see 2 PlatformAccount in the database But a user logged in space s2 should see 0 Content in the database When user of space s1 creates group my_group And user of space s1 adds space s2 to group my_group - Then a user logged in space s1 should see 1 Content in the database + Then a user logged in space s1 should see 2 Content in the database But a user logged in space s2 should see 0 Content in the database + And a user logged in space s2 should see 1 Space in the database And ResourceAccess are added to the database: | _account_uid | _space_id | source_local_id | | my_group | s1 | lct1 | - Then a user logged in space s1 should see 1 Content in the database + And SpaceAccess are added to the database: + | _account_uid | _space_id | permissions | + | my_group | s1 | partial | + Then a user logged in space s1 should see 2 Content in the database Then a user logged in space s2 should see 1 Content in the database + And a user logged in space s2 should see 2 Space in the database