Skip to content

Commit bb196b3

Browse files
authored
ENG-1723 backend function: List group members
1 parent 586955a commit bb196b3

4 files changed

Lines changed: 443 additions & 1 deletion

File tree

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
import assert from "assert";
2+
import { describe, it, beforeAll, afterAll } from "vitest";
3+
import { createClient } from "@supabase/supabase-js";
4+
import type { Database } from "@repo/database/dbTypes";
5+
import type { DGSupabaseClient } from "@repo/database/lib/client";
6+
import {
7+
fetchOrCreateSpaceDirect,
8+
spaceAnonUserEmail,
9+
} from "@repo/database/lib/contextFunctions";
10+
import { createGroup } from "../../app/utils/supabase/account";
11+
12+
const SUPABASE_URL = process.env.SUPABASE_URL!;
13+
const ANON_KEY = process.env.SUPABASE_PUBLISHABLE_KEY!;
14+
const SERVICE_KEY = process.env.SUPABASE_SECRET_KEY!;
15+
const PASSWORD = "abcdefgh";
16+
17+
type GroupSpaceInfo = Database["public"]["CompositeTypes"]["group_space_info"];
18+
19+
const freshClient = (): DGSupabaseClient =>
20+
createClient<Database, "public">(SUPABASE_URL, ANON_KEY);
21+
22+
const serviceClient = () =>
23+
createClient<Database, "public">(SUPABASE_URL, SERVICE_KEY);
24+
25+
const signedInClient = async (spaceId: number): Promise<DGSupabaseClient> => {
26+
const client = freshClient();
27+
const { error } = await client.auth.signInWithPassword({
28+
email: spaceAnonUserEmail("Roam", spaceId),
29+
password: PASSWORD,
30+
});
31+
if (error) throw new Error(`Sign-in failed: ${error.message}`);
32+
return client;
33+
};
34+
35+
describe("list group members flow", { tags: ["database"] }, () => {
36+
let spaceId1: number;
37+
let spaceId2: number;
38+
let spaceAccountUuid1: string;
39+
let spaceAccountUuid2: string;
40+
let client1: DGSupabaseClient;
41+
let client2: DGSupabaseClient;
42+
let createdGroupId: string | null = null;
43+
44+
beforeAll(async () => {
45+
const s1 = await fetchOrCreateSpaceDirect({
46+
name: "vitest-s1",
47+
url: "https://roamresearch.com/#/app/vitest-s1",
48+
platform: "Roam",
49+
password: PASSWORD,
50+
});
51+
if (!s1.data)
52+
throw new Error(`Failed to create space 1: ${s1.error?.message}`);
53+
spaceId1 = s1.data.id;
54+
client1 = await signedInClient(spaceId1);
55+
assert(client1);
56+
const accountReq1 = await client1
57+
.from("PlatformAccount")
58+
.select("id,dg_account")
59+
.eq(
60+
"account_local_id",
61+
`roam-${spaceId1}-anon@database.discoursegraphs.com`,
62+
)
63+
.maybeSingle();
64+
assert(!accountReq1.error);
65+
assert(accountReq1.data);
66+
assert(accountReq1.data.dg_account);
67+
spaceAccountUuid1 = accountReq1.data.dg_account;
68+
const s2 = await fetchOrCreateSpaceDirect({
69+
name: "vitest-s2",
70+
url: "https://roamresearch.com/#/app/vitest-s2",
71+
platform: "Roam",
72+
password: PASSWORD,
73+
});
74+
if (!s2.data)
75+
throw new Error(`Failed to create space 2: ${s2.error?.message}`);
76+
spaceId2 = s2.data.id;
77+
client2 = await signedInClient(spaceId2);
78+
assert(client2);
79+
const accountReq2 = await client2
80+
.from("PlatformAccount")
81+
.select("id,dg_account")
82+
.eq(
83+
"account_local_id",
84+
`roam-${spaceId2}-anon@database.discoursegraphs.com`,
85+
)
86+
.maybeSingle();
87+
assert(!accountReq2.error);
88+
assert(accountReq2.data);
89+
assert(accountReq2.data.dg_account);
90+
spaceAccountUuid2 = accountReq2.data.dg_account;
91+
});
92+
93+
afterAll(async () => {
94+
if (createdGroupId)
95+
await serviceClient().auth.admin.deleteUser(createdGroupId);
96+
if (spaceAccountUuid1)
97+
await serviceClient().auth.admin.deleteUser(spaceAccountUuid1);
98+
if (spaceAccountUuid2)
99+
await serviceClient().auth.admin.deleteUser(spaceAccountUuid2);
100+
if (spaceId1)
101+
await serviceClient().from("Space").delete().eq("id", spaceId1);
102+
if (spaceId2)
103+
await serviceClient().from("Space").delete().eq("id", spaceId2);
104+
});
105+
106+
it("lists group members", async () => {
107+
// Step 1: user1 creates a group
108+
const groupId = await createGroup(client1, "vitest-invite-group");
109+
assert(groupId !== null, "createGroup should return a group ID");
110+
createdGroupId = groupId;
111+
112+
// Step 2: Add another member
113+
const { error: errorAddMember } = await client1
114+
.from("group_membership")
115+
.insert({
116+
member_id: spaceAccountUuid2, // eslint-disable-line @typescript-eslint/naming-convention
117+
group_id: groupId, // eslint-disable-line @typescript-eslint/naming-convention
118+
admin: false,
119+
});
120+
assert(!errorAddMember);
121+
122+
const expectedSpaceIds = [spaceId1, spaceId2];
123+
// Step 3: user1 lists group members
124+
const { data: data1, error: error1 } = await client1.rpc(
125+
"spaces_in_group",
126+
{
127+
p_group_id: createdGroupId, // eslint-disable-line @typescript-eslint/naming-convention
128+
},
129+
);
130+
131+
assert(error1 === null, error1 ? error1.message : "");
132+
assert(data1 !== null, "group spaces should not be empty");
133+
assert(data1.length === 2, "There should be two spaces");
134+
const spacesSeenBy1 = Object.fromEntries(
135+
data1.filter((gm) => gm.id !== null).map((gm) => [gm.id, gm]),
136+
) as Record<number, GroupSpaceInfo>;
137+
assert(
138+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
139+
expectedSpaceIds.every((id) => spacesSeenBy1[id] !== undefined),
140+
"Wrong membership information",
141+
);
142+
assert(
143+
Object.values(spacesSeenBy1).every(
144+
(gm) => gm.sharing_permissions === null,
145+
),
146+
);
147+
// Step 4: user2 lists group members
148+
const { data: data2, error: error2 } = await client2.rpc(
149+
"spaces_in_group",
150+
{
151+
p_group_id: createdGroupId, // eslint-disable-line @typescript-eslint/naming-convention
152+
},
153+
);
154+
assert(error2 === null, error2 ? error2.message : "");
155+
assert(data2 !== null, "group spaces should not be empty");
156+
assert(data2.length === 2, "There should be two spaces");
157+
const spacesSeenBy2 = new Set(
158+
data2.map((gm) => gm.id).filter((id) => id !== null),
159+
);
160+
assert(
161+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
162+
expectedSpaceIds.every((id) => spacesSeenBy2.has(id)),
163+
"Wrong membership information",
164+
);
165+
166+
// Step 5: User 2 publishes a space
167+
const { error: errorPublishSpace2 } = await client2
168+
.from("SpaceAccess")
169+
.insert({
170+
space_id: spaceId2, // eslint-disable-line @typescript-eslint/naming-convention
171+
account_uid: groupId, // eslint-disable-line @typescript-eslint/naming-convention
172+
permissions: "partial",
173+
});
174+
assert(!errorPublishSpace2);
175+
// Step 6: that space is now seen as published by 1.
176+
const { data: data1b, error: error1b } = await client1.rpc(
177+
"spaces_in_group",
178+
{
179+
p_group_id: createdGroupId, // eslint-disable-line @typescript-eslint/naming-convention
180+
},
181+
);
182+
183+
assert(error1b === null, error1b ? error1b.message : "");
184+
assert(data1b !== null, "group spaces should not be empty");
185+
assert(data1b.length === 2, "There should be two spaces");
186+
const spacesSeenBy1b = Object.fromEntries(
187+
data1b.filter((gm) => gm.id !== null).map((gm) => [gm.id, gm]),
188+
) as Record<number, GroupSpaceInfo>;
189+
assert(
190+
spacesSeenBy1b[spaceId2]?.sharing_permissions,
191+
"Second space should now be seen as shared",
192+
);
193+
});
194+
});

0 commit comments

Comments
 (0)