@edifice.io/communities-tests 1.0.0-develop-pedago.20250725171105

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,582 @@
1
+ // @ts-ignore
2
+ import { chai, describe } from "https://jslib.k6.io/k6chaijs/4.3.4.0/index.js";
3
+ import { fail, check } from "k6";
4
+
5
+ import {
6
+ authenticateWeb,
7
+ getUsersOfSchool,
8
+ createAndSetRole,
9
+ linkRoleToUsers,
10
+ initStructure,
11
+ getRandomUserWithProfile,
12
+ getRolesOfStructure,
13
+ Structure,
14
+ logout,
15
+ UserInfo,
16
+ } from "../../node_modules/edifice-k6-commons/dist/index.js";
17
+
18
+ // Import community API utils
19
+ import {
20
+ createCommunityOrFail,
21
+ deleteCommunity,
22
+ getSecretCode,
23
+ CreateCommunityParams,
24
+ } from "./utils/_community-api.utils.ts";
25
+
26
+ // Import invitation API utils
27
+ import {
28
+ createInvitations,
29
+ listCommunityInvitations,
30
+ listMyInvitations,
31
+ updateInvitationStatus,
32
+ deleteInvitation,
33
+ findInvitationById,
34
+ CreateInvitationParams,
35
+ Invitation,
36
+ InvitationStatus,
37
+ MembershipRole,
38
+ deleteInvitationsBatch,
39
+ joinCommunity,
40
+ updateRequestStatus,
41
+ updateInvitationSeeLater,
42
+ } from "./utils/_invitation-api.utils.ts";
43
+
44
+ // Import membership API utils for verification
45
+ import {
46
+ listCommunityMembers,
47
+ findMembershipByUserId,
48
+ } from "./utils/_membership-api.utils.ts";
49
+
50
+ chai.config.logFailures = true;
51
+
52
+ export const options = {
53
+ setupTimeout: "1h",
54
+ maxRedirects: 0,
55
+ thresholds: {
56
+ checks: ["rate == 1.00"],
57
+ },
58
+ scenarios: {
59
+ invitationsTest: {
60
+ exec: "testInvitationsApi",
61
+ executor: "per-vu-iterations",
62
+ vus: 1,
63
+ maxDuration: "1m",
64
+ gracefulStop: "5s",
65
+ },
66
+ },
67
+ };
68
+
69
+ const timestamp = new Date().toISOString();
70
+ const schoolName = `IT Community Invitations`;
71
+
72
+ export function setup(): Structure {
73
+ let structure: Structure = {} as Structure;
74
+ describe("[CommunityInvitations] Initialize data", () => {
75
+ authenticateWeb(__ENV.ADMC_LOGIN, __ENV.ADMC_PASSWORD);
76
+ structure = initStructure(schoolName, "tiny");
77
+ const role = createAndSetRole("Communities");
78
+ const groups = getRolesOfStructure(structure.id);
79
+ linkRoleToUsers(
80
+ structure,
81
+ role,
82
+ groups.map((g: any) => g.name),
83
+ );
84
+ });
85
+ return structure;
86
+ }
87
+
88
+ export function testInvitationsApi(structure: Structure) {
89
+ // Community data
90
+ const communityData: CreateCommunityParams = {
91
+ title: `InvitationTest - Community ${timestamp}`,
92
+ type: "FREE",
93
+ schoolYearStart: 2025,
94
+ schoolYearEnd: 2026,
95
+ discussionEnabled: true,
96
+ welcomeNote: "Test community for invitation API tests",
97
+ invitations: {
98
+ users: [],
99
+ },
100
+ };
101
+
102
+ let communityId: number;
103
+ let invitationId: number;
104
+ let adminUser: UserInfo;
105
+ let memberUser: UserInfo;
106
+ let member2User: UserInfo;
107
+ let member3User: UserInfo;
108
+
109
+ describe("[Community Invitations API Test with Authentication]", () => {
110
+ // Step 1: Admin logs in via Web
111
+ authenticateWeb(__ENV.ADMC_LOGIN, __ENV.ADMC_PASSWORD);
112
+
113
+ // Get users from school
114
+ const users = getUsersOfSchool(structure);
115
+ adminUser = getRandomUserWithProfile(users, "Teacher");
116
+ memberUser = getRandomUserWithProfile(users, "Student", [adminUser]);
117
+ member2User = getRandomUserWithProfile(users, "Student", [
118
+ adminUser,
119
+ memberUser,
120
+ ]);
121
+ member3User = getRandomUserWithProfile(users, "Student", [
122
+ adminUser,
123
+ memberUser,
124
+ member2User,
125
+ ]);
126
+
127
+ logout();
128
+
129
+ // Step 2: Admin authenticates
130
+ const adminAuthenticated = authenticateWeb(adminUser.login, "password");
131
+ if (!adminAuthenticated) {
132
+ fail("Admin authentication failed");
133
+ }
134
+
135
+ // Step 3: Admin creates a community
136
+ describe("Create community for invitation tests", () => {
137
+ // Use the utility function to create a community
138
+ communityId = Number(createCommunityOrFail(communityData));
139
+ console.log(`Created community with ID: ${communityId}`);
140
+ });
141
+
142
+ // Step 4: Admin invites member to community
143
+ describe("Create invitation for member", () => {
144
+ // Use the invitation API utility to create an invitation
145
+ const invitationData: CreateInvitationParams = {
146
+ users: [{ userId: memberUser.id, role: MembershipRole.MEMBER }],
147
+ message: "You are invited to join our test community",
148
+ };
149
+
150
+ const invitations = createInvitations(communityId, invitationData);
151
+
152
+ check(invitations, {
153
+ "invitation created successfully": (r) => r !== null && r.length > 0,
154
+ });
155
+
156
+ invitationId = invitations![0].id;
157
+ console.log(`Created invitation with ID: ${invitationId}`);
158
+ });
159
+
160
+ // 5.1: Get specific invitation from community invitations list
161
+ describe("Find invitation in community invitations list", () => {
162
+ const invitations = listCommunityInvitations(communityId);
163
+ const invitation = findInvitationById(invitationId, invitations);
164
+
165
+ check(invitation, {
166
+ "invitation found in community list": (r) => r !== null,
167
+ "invitation has correct ID": (r) => r !== null && r.id === invitationId,
168
+ "invitation has correct status": (r) =>
169
+ r !== null && r.status === InvitationStatus.PENDING,
170
+ });
171
+ });
172
+
173
+ // 5.2: List invitations for a community
174
+ describe("List invitations for community", () => {
175
+ const invitations = listCommunityInvitations(communityId);
176
+
177
+ check(invitations, {
178
+ "list invitations successful": (r) => r !== null,
179
+ "invitations list contains data": (r) => r !== null && r.length > 0,
180
+ "created invitation is in the list": (r) => {
181
+ return (
182
+ r !== null && r.some((inv: Invitation) => inv.id === invitationId)
183
+ );
184
+ },
185
+ });
186
+ });
187
+
188
+ // Step 6: Member authenticates
189
+ logout();
190
+ const memberAuthenticated = authenticateWeb(memberUser.login, "password");
191
+ if (!memberAuthenticated) {
192
+ fail("Member authentication failed");
193
+ }
194
+
195
+ // Step 7: Member lists their invitations
196
+ describe("Member lists received invitations", () => {
197
+ const myInvitations = listMyInvitations();
198
+ const invitation = findInvitationById(invitationId, myInvitations);
199
+
200
+ check(myInvitations, {
201
+ "member can see invitations": (r) => r !== null,
202
+ });
203
+
204
+ check(invitation, {
205
+ "invitation is in the member's list": (r) => r !== null,
206
+ "invitation has correct status": (r) =>
207
+ r !== null && r.status === InvitationStatus.PENDING,
208
+ });
209
+ });
210
+
211
+ // Step 8: Member accepts invitation
212
+ describe("Member accepts invitation", () => {
213
+ const updatedInvitation = updateInvitationStatus(
214
+ invitationId,
215
+ InvitationStatus.ACCEPTED,
216
+ );
217
+
218
+ check(updatedInvitation, {
219
+ "invitation accepted successfully": (r) => r !== null,
220
+ "status changed to ACCEPTED": (r) =>
221
+ r !== null && r.status === InvitationStatus.ACCEPTED,
222
+ });
223
+ });
224
+
225
+ // Step 9: Admin logs back in to check invitation status
226
+ logout();
227
+ authenticateWeb(adminUser.login, "password");
228
+
229
+ describe("Admin verifies invitation was accepted", () => {
230
+ const invitations = listCommunityInvitations(communityId);
231
+ const invitation = findInvitationById(invitationId, invitations);
232
+
233
+ check(invitation, {
234
+ "invitation found in list": (r) => r !== null,
235
+ "invitation status is ACCEPTED": (r) =>
236
+ r !== null && r.status === InvitationStatus.ACCEPTED,
237
+ });
238
+ });
239
+
240
+ // Test deletion of an invitation
241
+ describe("Admin tries to delete an invitation", () => {
242
+ // First create a new invitation for deletion test
243
+ const newInvitationData: CreateInvitationParams = {
244
+ users: [{ userId: member2User.id, role: MembershipRole.MEMBER }],
245
+ message: "This invitation will be deleted",
246
+ };
247
+
248
+ const newInvitations = createInvitations(communityId, newInvitationData);
249
+ const newInvitationId = newInvitations![0].id;
250
+
251
+ // Now delete the invitation
252
+ const deleted = deleteInvitation(communityId, newInvitationId);
253
+
254
+ check(deleted, {
255
+ "invitation deleted successfully": (r) => r === true,
256
+ });
257
+
258
+ // Verify the invitation is actually deleted by looking for it in the list
259
+ const updatedInvitations = listCommunityInvitations(communityId);
260
+ const deletedInvitation = findInvitationById(
261
+ newInvitationId,
262
+ updatedInvitations,
263
+ );
264
+
265
+ check(deletedInvitation, {
266
+ "invitation no longer exists in list": (r) => r === null,
267
+ });
268
+ });
269
+
270
+ // Test deletion of a PENDING invitation
271
+ describe("Admin deletes a PENDING invitation", () => {
272
+ // First create a new invitation for deletion test using a different user
273
+ const newInvitationData: CreateInvitationParams = {
274
+ users: [{ userId: member3User.id, role: MembershipRole.MEMBER }],
275
+ message: "This invitation will be deleted",
276
+ };
277
+
278
+ const newInvitations = createInvitations(communityId, newInvitationData);
279
+ const newInvitationId = newInvitations![0].id;
280
+
281
+ // Verify the new invitation is indeed in PENDING status before attempting deletion
282
+ const pendingInvitation = findInvitationById(
283
+ newInvitationId,
284
+ listCommunityInvitations(communityId),
285
+ );
286
+
287
+ check(pendingInvitation, {
288
+ "invitation is in PENDING status before deletion": (r) =>
289
+ r !== null && r.status === InvitationStatus.PENDING,
290
+ });
291
+
292
+ // Skip deletion if invitation is not in PENDING status
293
+ if (pendingInvitation?.status !== InvitationStatus.PENDING) {
294
+ console.error(
295
+ `Cannot proceed with deletion: invitation ${newInvitationId} status is ${pendingInvitation?.status}`,
296
+ );
297
+ return;
298
+ }
299
+
300
+ // Now delete the invitation
301
+ const deleted = deleteInvitation(communityId, newInvitationId);
302
+
303
+ check(deleted, {
304
+ "invitation deleted successfully": (r) => r === true,
305
+ });
306
+
307
+ // Verify the invitation is actually deleted
308
+ const updatedInvitations = listCommunityInvitations(communityId);
309
+ const deletedInvitation = findInvitationById(
310
+ newInvitationId,
311
+ updatedInvitations,
312
+ );
313
+
314
+ check(deletedInvitation, {
315
+ "invitation no longer exists in list": (r) => r === null,
316
+ });
317
+ });
318
+
319
+ // Test batch deletion of invitations
320
+ describe("Admin deletes multiple invitations in batch", () => {
321
+ // Create multiple invitations for batch deletion test
322
+ const batchInvitationData: CreateInvitationParams = {
323
+ users: [
324
+ { userId: member2User.id, role: MembershipRole.MEMBER },
325
+ { userId: member3User.id, role: MembershipRole.MEMBER },
326
+ ],
327
+ message: "These invitations will be deleted in batch",
328
+ };
329
+
330
+ const batchInvitations = createInvitations(
331
+ communityId,
332
+ batchInvitationData,
333
+ );
334
+ const batchInvitationIds = batchInvitations!.map((inv) => inv.id);
335
+
336
+ // Verify all invitations are in PENDING status
337
+ const pendingInvitations = listCommunityInvitations(communityId);
338
+ check(pendingInvitations, {
339
+ "all new invitations are in PENDING status": (r) => {
340
+ if (!r) return false;
341
+ const foundInvitations = batchInvitationIds.map((id) =>
342
+ findInvitationById(id, r),
343
+ );
344
+ return foundInvitations.every(
345
+ (inv) => inv !== null && inv.status === InvitationStatus.PENDING,
346
+ );
347
+ },
348
+ });
349
+
350
+ // Now delete the invitations in batch
351
+ const batchDeleted = deleteInvitationsBatch(
352
+ communityId,
353
+ batchInvitationIds,
354
+ );
355
+
356
+ check(batchDeleted, {
357
+ "batch deletion successful": (r) => r === true,
358
+ });
359
+
360
+ // Verify all invitations are deleted
361
+ const remainingInvitations = listCommunityInvitations(communityId);
362
+ check(remainingInvitations, {
363
+ "all batch-deleted invitations are gone": (r) => {
364
+ if (!r) return false;
365
+ return batchInvitationIds.every(
366
+ (id) => findInvitationById(id, r) === null,
367
+ );
368
+ },
369
+ });
370
+ });
371
+
372
+ // NEW SECTION: Testing the join request workflow with admin approval/rejection
373
+ describe("Secret code join requests and admin approval/rejection", () => {
374
+ // Get the secret code for the community
375
+ const secretCode = getSecretCode(communityId) || "";
376
+ check(secretCode, {
377
+ "Secret code retrieved successfully": (code) =>
378
+ code !== null && code !== "",
379
+ });
380
+
381
+ let acceptedUserId: number | null = null;
382
+ let rejectedUserId: number | null = null;
383
+ let acceptRequestId: number;
384
+ let rejectRequestId: number;
385
+
386
+ // Member 2 creates a join request (to be approved)
387
+ logout();
388
+ authenticateWeb(member2User.login, "password");
389
+
390
+ const joinRequest1 = joinCommunity(secretCode);
391
+ check(joinRequest1, {
392
+ "Member2 join request created successfully": (r) => r !== null,
393
+ "Member2 request has REQUEST status": (r) =>
394
+ r !== null && r.status === InvitationStatus.REQUEST,
395
+ });
396
+
397
+ if (joinRequest1 && joinRequest1.receiver) {
398
+ acceptedUserId = Number(joinRequest1.receiver.id);
399
+ acceptRequestId = joinRequest1.id;
400
+ console.log(`Member2 created join request ${acceptRequestId}`);
401
+ }
402
+
403
+ // Member 3 creates a join request (to be rejected)
404
+ logout();
405
+ authenticateWeb(member3User.login, "password");
406
+
407
+ const joinRequest2 = joinCommunity(secretCode);
408
+ check(joinRequest2, {
409
+ "Member3 join request created successfully": (r) => r !== null,
410
+ "Member3 request has REQUEST status": (r) =>
411
+ r !== null && r.status === InvitationStatus.REQUEST,
412
+ });
413
+
414
+ if (joinRequest2 && joinRequest2.receiver) {
415
+ rejectedUserId = Number(joinRequest2.receiver.id!);
416
+ rejectRequestId = joinRequest2.id!;
417
+ console.log(`Member3 created join request ${rejectRequestId}`);
418
+ }
419
+
420
+ // Admin approves first request and rejects second request
421
+ logout();
422
+ authenticateWeb(adminUser.login, "password");
423
+
424
+ // Admin verifies join requests appear in the community's invitations list
425
+ const pendingRequests = listCommunityInvitations(communityId);
426
+ const foundRequest1 = findInvitationById(
427
+ acceptRequestId!,
428
+ pendingRequests,
429
+ );
430
+ const foundRequest2 = findInvitationById(
431
+ rejectRequestId!,
432
+ pendingRequests,
433
+ );
434
+
435
+ check(foundRequest1, {
436
+ "Member2 join request found in invitations list": (r) => r !== null,
437
+ "Member2 join request has REQUEST status": (r) =>
438
+ r !== null && r.status === InvitationStatus.REQUEST,
439
+ });
440
+
441
+ check(foundRequest2, {
442
+ "Member3 join request found in invitations list": (r) => r !== null,
443
+ "Member3 join request has REQUEST status": (r) =>
444
+ r !== null && r.status === InvitationStatus.REQUEST,
445
+ });
446
+
447
+ // Admin approves the first request
448
+ const approvedRequest = updateRequestStatus(
449
+ communityId,
450
+ acceptRequestId!,
451
+ InvitationStatus.REQUEST_ACCEPTED,
452
+ );
453
+
454
+ check(approvedRequest, {
455
+ "Join request approved successfully": (r) => r !== null,
456
+ "Status changed to REQUEST_ACCEPTED": (r) =>
457
+ r !== null && r.status === InvitationStatus.REQUEST_ACCEPTED,
458
+ });
459
+
460
+ // Admin rejects the second request
461
+ const rejectedRequest = updateRequestStatus(
462
+ communityId,
463
+ rejectRequestId!,
464
+ InvitationStatus.REQUEST_REJECTED,
465
+ );
466
+
467
+ check(rejectedRequest, {
468
+ "Join request rejected successfully": (r) => r !== null,
469
+ "Status changed to REQUEST_REJECTED": (r) =>
470
+ r !== null && r.status === InvitationStatus.REQUEST_REJECTED,
471
+ });
472
+
473
+ // Verify that Member2 is now a member
474
+ const memberships = listCommunityMembers(communityId);
475
+ const member2Membership = acceptedUserId
476
+ ? findMembershipByUserId(memberships, acceptedUserId)
477
+ : null;
478
+
479
+ check(member2Membership, {
480
+ "Approved user is now a member": (m) => m !== null,
481
+ "Member has correct role": (m) =>
482
+ m !== null && m.role === MembershipRole.MEMBER,
483
+ });
484
+
485
+ // Verify that Member3 is NOT a member
486
+ const member3Membership = rejectedUserId
487
+ ? findMembershipByUserId(memberships, rejectedUserId)
488
+ : null;
489
+
490
+ check(member3Membership, {
491
+ "Rejected user is not a member": (m) => m === null,
492
+ });
493
+ });
494
+
495
+ // Test see-later flag functionality
496
+ describe("Member tests see-later flag functionality", () => {
497
+ // First, login as admin
498
+ logout();
499
+ authenticateWeb(adminUser.login, "password");
500
+
501
+ // Create a new invitation to test see-later flag
502
+ const seeLaterInvitationData: CreateInvitationParams = {
503
+ users: [{ userId: member3User.id, role: MembershipRole.MEMBER }],
504
+ message: "This invitation will be marked as see later",
505
+ };
506
+
507
+ const seeLaterInvitations = createInvitations(communityId, seeLaterInvitationData);
508
+
509
+ check(seeLaterInvitations, {
510
+ "See-later test invitation created successfully": (r) => r !== null && r.length > 0,
511
+ });
512
+
513
+ if (!seeLaterInvitations || seeLaterInvitations.length === 0) {
514
+ console.error("Failed to create see-later test invitation");
515
+ return;
516
+ }
517
+
518
+ const seeLaterInvitationId = seeLaterInvitations[0].id;
519
+ console.log(`Created invitation with ID: ${seeLaterInvitationId} for see-later test`);
520
+
521
+ // Member logs in
522
+ logout();
523
+ authenticateWeb(member3User.login, "password");
524
+
525
+ // Check that the invitation exists in the member's list
526
+ const initialInvitations = listMyInvitations();
527
+ const initialInvitation = findInvitationById(seeLaterInvitationId, initialInvitations);
528
+
529
+ check(initialInvitation, {
530
+ "See-later invitation found in member's list": (r) => r !== null,
531
+ "See-later flag is initially false": (r) => r !== null && r.seeLater === false,
532
+ });
533
+
534
+ // Update the see-later flag to true
535
+ const updatedInvitation = updateInvitationSeeLater(seeLaterInvitationId, true);
536
+
537
+ check(updatedInvitation, {
538
+ "See-later flag updated successfully": (r) => r !== null,
539
+ "See-later flag set to true": (r) => r !== null && r.seeLater === true,
540
+ });
541
+
542
+ // Verify the see-later flag is persisted when listing invitations again
543
+ const filteredInvitations = listMyInvitations({ seeLater: true });
544
+
545
+ check(filteredInvitations, {
546
+ "Can filter invitations by see-later flag": (r) => r !== null && r.items.length > 0,
547
+ "Filtered list contains our marked invitation": (r) =>
548
+ r !== null && r.items.some((inv: Invitation) => inv.id === seeLaterInvitationId),
549
+ });
550
+
551
+ // Change see-later flag back to false
552
+ const updatedAgainInvitation = updateInvitationSeeLater(seeLaterInvitationId, false);
553
+
554
+ check(updatedAgainInvitation, {
555
+ "See-later flag turned off successfully": (r) => r !== null,
556
+ "See-later flag set back to false": (r) => r !== null && r.seeLater === false,
557
+ });
558
+
559
+ // Verify filtering with seeLater=false
560
+ const nonSeeLaterInvitations = listMyInvitations({ seeLater: false });
561
+
562
+ check(nonSeeLaterInvitations, {
563
+ "Can filter invitations by see-later=false": (r) => r !== null,
564
+ "Filtered list contains our unmarked invitation": (r) =>
565
+ r !== null && r.items.some((inv: Invitation) => inv.id === seeLaterInvitationId),
566
+ });
567
+ });
568
+
569
+ // Final Step: Cleanup - Delete the community
570
+ describe("Cleanup - Delete test community", () => {
571
+ // Assurez-vous que l'admin est connecté pour supprimer la communauté
572
+ logout();
573
+ authenticateWeb(adminUser.login, "password");
574
+
575
+ const deleted = deleteCommunity(communityId);
576
+
577
+ check(deleted, {
578
+ "community deleted successfully": (r) => r === true,
579
+ });
580
+ });
581
+ });
582
+ }