@stamhoofd/backend 2.102.0 → 2.103.0

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stamhoofd/backend",
3
- "version": "2.102.0",
3
+ "version": "2.103.0",
4
4
  "main": "./dist/index.js",
5
5
  "exports": {
6
6
  ".": {
@@ -45,14 +45,14 @@
45
45
  "@simonbackx/simple-encoding": "2.22.0",
46
46
  "@simonbackx/simple-endpoints": "1.20.1",
47
47
  "@simonbackx/simple-logging": "^1.0.1",
48
- "@stamhoofd/backend-i18n": "2.102.0",
49
- "@stamhoofd/backend-middleware": "2.102.0",
50
- "@stamhoofd/email": "2.102.0",
51
- "@stamhoofd/models": "2.102.0",
52
- "@stamhoofd/queues": "2.102.0",
53
- "@stamhoofd/sql": "2.102.0",
54
- "@stamhoofd/structures": "2.102.0",
55
- "@stamhoofd/utility": "2.102.0",
48
+ "@stamhoofd/backend-i18n": "2.103.0",
49
+ "@stamhoofd/backend-middleware": "2.103.0",
50
+ "@stamhoofd/email": "2.103.0",
51
+ "@stamhoofd/models": "2.103.0",
52
+ "@stamhoofd/queues": "2.103.0",
53
+ "@stamhoofd/sql": "2.103.0",
54
+ "@stamhoofd/structures": "2.103.0",
55
+ "@stamhoofd/utility": "2.103.0",
56
56
  "archiver": "^7.0.1",
57
57
  "axios": "^1.8.2",
58
58
  "cookie": "^0.7.0",
@@ -70,5 +70,5 @@
70
70
  "publishConfig": {
71
71
  "access": "public"
72
72
  },
73
- "gitHead": "0dcf9d704578c9f1972f7068b8727a5572c723c7"
73
+ "gitHead": "81c0e08f64f5c9a32a513cf6cfd2d48e3601f1a8"
74
74
  }
package/src/crons.ts CHANGED
@@ -2,7 +2,7 @@ import { Database } from '@simonbackx/simple-database';
2
2
  import { Group, Organization, Payment, Registration, STPackage, Webshop } from '@stamhoofd/models';
3
3
  import { PaymentMethod, PaymentProvider, PaymentStatus } from '@stamhoofd/structures';
4
4
  import { Formatter } from '@stamhoofd/utility';
5
-
5
+ import { SQL } from '@stamhoofd/sql';
6
6
  import { registerCron } from '@stamhoofd/crons';
7
7
  import { checkSettlements } from './helpers/CheckSettlements';
8
8
  import { PaymentService } from './services/PaymentService';
@@ -130,30 +130,25 @@ async function checkPayments() {
130
130
  const timeout = 60 * 1000 * 31;
131
131
 
132
132
  // TODO: only select the ID + organizationId
133
- const payments = await Payment.where({
134
- status: {
135
- sign: 'IN',
136
- value: [PaymentStatus.Created, PaymentStatus.Pending],
137
- },
138
- method: {
139
- sign: 'IN',
140
- value: [PaymentMethod.Bancontact, PaymentMethod.iDEAL, PaymentMethod.Payconiq, PaymentMethod.CreditCard],
141
- },
142
- // Check all payments that are 11 minutes old and are still pending
143
- createdAt: {
144
- sign: '<',
145
- value: new Date(new Date().getTime() - timeout),
146
- },
147
- }, {
148
- limit: 100,
149
-
150
- // Return oldest payments first
151
- // If at some point, they are still pending after 1 day, their status should change to failed
152
- sort: [{
153
- column: 'createdAt',
154
- direction: 'ASC',
155
- }],
156
- });
133
+ const payments = await Payment.select()
134
+ .where(
135
+ SQL.where('method', [
136
+ PaymentMethod.Bancontact, PaymentMethod.iDEAL, PaymentMethod.Payconiq, PaymentMethod.CreditCard,
137
+ ])
138
+ .and('status', [PaymentStatus.Created, PaymentStatus.Pending])
139
+ .and('createdAt', '<', new Date(new Date().getTime() - timeout)),
140
+ )
141
+ // For payconiq payments, we have a shorter timeout of 1 minute if they are still in the 'created' state (not scanned)
142
+ .orWhere(
143
+ SQL.where('method', [
144
+ PaymentMethod.Payconiq,
145
+ ])
146
+ .and('status', [PaymentStatus.Created])
147
+ .and('createdAt', '<', new Date(new Date().getTime() - 60 * 1000)),
148
+ )
149
+ .orderBy('createdAt', 'ASC')
150
+ .limit(200)
151
+ .fetch();
157
152
 
158
153
  console.log('[DELAYED PAYMENTS] Checking pending payments: ' + payments.length);
159
154
 
@@ -1,12 +1,15 @@
1
1
  import { Request } from '@simonbackx/simple-endpoints';
2
2
  import { EmailMocker } from '@stamhoofd/email';
3
3
  import { BalanceItemFactory, Group, GroupFactory, MemberFactory, MemberWithRegistrations, Organization, OrganizationFactory, OrganizationRegistrationPeriodFactory, Registration, RegistrationFactory, RegistrationPeriod, RegistrationPeriodFactory, Token, UserFactory } from '@stamhoofd/models';
4
- import { BalanceItemCartItem, BalanceItemType, Company, GroupOption, GroupOptionMenu, IDRegisterCart, IDRegisterCheckout, IDRegisterItem, OrganizationPackages, PaymentCustomer, PaymentMethod, PermissionLevel, Permissions, ReduceablePrice, RegisterItemOption, STPackageStatus, STPackageType, UserPermissions, Version } from '@stamhoofd/structures';
4
+ import { BalanceItemCartItem, BalanceItemStatus, BalanceItemType, BooleanStatus, Company, GroupOption, GroupOptionMenu, IDRegisterCart, IDRegisterCheckout, IDRegisterItem, OrganizationPackages, PaymentCustomer, PaymentMethod, PermissionLevel, Permissions, PermissionsResourceType, ReduceablePrice, RegisterItemOption, ResourcePermissions, STPackageStatus, STPackageType, UserPermissions, Version } from '@stamhoofd/structures';
5
5
  import { STExpect, TestUtils } from '@stamhoofd/test-utils';
6
6
  import { v4 as uuidv4 } from 'uuid';
7
7
  import { testServer } from '../../../../tests/helpers/TestServer';
8
8
  import { initPayconiq } from '../../../../tests/init/initPayconiq';
9
9
  import { RegisterMembersEndpoint } from './RegisterMembersEndpoint';
10
+ import { assertBalances } from '../../../../tests/assertions/assertBalances';
11
+ import PersistentFile from 'formidable/PersistentFile';
12
+ import { PatchMap } from '@simonbackx/simple-encoding';
10
13
 
11
14
  const baseUrl = `/v${Version}/members/register`;
12
15
 
@@ -15,6 +18,7 @@ describe('Endpoint.RegisterMembers', () => {
15
18
  const endpoint = new RegisterMembersEndpoint();
16
19
  let period: RegistrationPeriod;
17
20
  let defaultPermissionLevel = PermissionLevel.None;
21
+ let defaultLinkMembersToUser = true;
18
22
  const post = async (body: IDRegisterCheckout, organization: Organization, token: Token) => {
19
23
  const request = Request.buildJson('POST', baseUrl, organization.getApiHost(), body);
20
24
  request.headers.authorization = 'Bearer ' + token.accessToken;
@@ -52,7 +56,7 @@ describe('Endpoint.RegisterMembers', () => {
52
56
  return { organization, organizationRegistrationPeriod };
53
57
  };
54
58
 
55
- async function initData({ otherMemberAmount = 0, permissionLevel = defaultPermissionLevel }: { otherMemberAmount?: number; permissionLevel?: PermissionLevel } = {}) {
59
+ async function initData({ otherMemberAmount = 0, groupPermissionLevel = PermissionLevel.None, memberPermissionLevel = PermissionLevel.None, permissionLevel = defaultPermissionLevel, linkMembersToUser = defaultLinkMembersToUser }: { otherMemberAmount?: number; memberPermissionLevel?: PermissionLevel; groupPermissionLevel?: PermissionLevel; permissionLevel?: PermissionLevel; linkMembersToUser?: boolean } = {}) {
56
60
  const { organization, organizationRegistrationPeriod } = await initOrganization(period);
57
61
 
58
62
  const user = await new UserFactory({
@@ -67,23 +71,73 @@ describe('Endpoint.RegisterMembers', () => {
67
71
 
68
72
  const token = await Token.createToken(user);
69
73
 
70
- const member = await new MemberFactory({ organization, user })
74
+ const member = await new MemberFactory({ organization, user: linkMembersToUser ? user : undefined })
71
75
  .create();
72
76
 
73
77
  const otherMembers: MemberWithRegistrations[] = [];
74
78
 
75
79
  for (let i = 0; i < otherMemberAmount; i++) {
76
- otherMembers.push(await new MemberFactory({ organization, user })
80
+ otherMembers.push(await new MemberFactory({ organization, user: linkMembersToUser ? user : undefined })
77
81
  .create());
78
82
  }
79
83
 
84
+ if (!linkMembersToUser && (permissionLevel !== PermissionLevel.None || memberPermissionLevel !== null)) {
85
+ // Give write permission to the member by registering them for another group
86
+ const genericGroup = await new GroupFactory({
87
+ organization,
88
+ price: 0,
89
+ })
90
+ .create();
91
+
92
+ await new RegistrationFactory({
93
+ member,
94
+ group: genericGroup,
95
+ groupPrice: genericGroup.settings.prices[0],
96
+ }).create();
97
+
98
+ if (memberPermissionLevel !== PermissionLevel.None) {
99
+ // Grant permissions to this genericGroup
100
+ user.permissions = user.permissions ?? UserPermissions.create({});
101
+ let org = user.permissions.organizationPermissions.get(organization.id) ?? Permissions.create({});
102
+ const p = Permissions.patch({});
103
+ p.resources.set(PermissionsResourceType.Groups, new PatchMap([[
104
+ genericGroup.id, ResourcePermissions.patch({
105
+ level: memberPermissionLevel,
106
+ }),
107
+ ]]));
108
+ org = org.patch(p);
109
+ user.permissions.organizationPermissions.set(organization.id, org);
110
+
111
+ // Save
112
+ await user.save();
113
+ }
114
+ }
115
+
80
116
  const group = await new GroupFactory({
81
117
  organization,
82
118
  price: 25_00,
119
+ reducedPrice: 12_50,
83
120
  stock: 500,
84
121
  })
85
122
  .create();
86
123
 
124
+ if (groupPermissionLevel !== PermissionLevel.None) {
125
+ // Grant permissions to this genericGroup
126
+ user.permissions = user.permissions ?? UserPermissions.create({});
127
+ let org = user.permissions.organizationPermissions.get(organization.id) ?? Permissions.create({});
128
+ const p = Permissions.patch({});
129
+ p.resources.set(PermissionsResourceType.Groups, new PatchMap([[
130
+ group.id, ResourcePermissions.patch({
131
+ level: groupPermissionLevel,
132
+ }),
133
+ ]]));
134
+ org = org.patch(p);
135
+ user.permissions.organizationPermissions.set(organization.id, org);
136
+
137
+ // Save
138
+ await user.save();
139
+ }
140
+
87
141
  const groupPrice = group.settings.prices[0];
88
142
 
89
143
  return {
@@ -101,6 +155,7 @@ describe('Endpoint.RegisterMembers', () => {
101
155
  describe('Register as member', () => {
102
156
  beforeEach(() => {
103
157
  defaultPermissionLevel = PermissionLevel.None;
158
+ defaultLinkMembersToUser = true;
104
159
  });
105
160
 
106
161
  test('Should fail if demo limit reached', async () => {
@@ -1161,6 +1216,7 @@ describe('Endpoint.RegisterMembers', () => {
1161
1216
  describe('Register as organization', () => {
1162
1217
  beforeEach(() => {
1163
1218
  defaultPermissionLevel = PermissionLevel.Full;
1219
+ defaultLinkMembersToUser = false;
1164
1220
  });
1165
1221
 
1166
1222
  test('Should reuse recently deactivated registration', async () => {
@@ -1246,15 +1302,272 @@ describe('Endpoint.RegisterMembers', () => {
1246
1302
  code: 'cannot_pay_balance_items',
1247
1303
  }));
1248
1304
  });
1305
+
1306
+ /**
1307
+ * Case: you have write permission - but no access to financial data.
1308
+ */
1309
+ test('Registering members with financial support', async () => {
1310
+ const { member, user, groupPrice, group, organization, token } = await initData({
1311
+ permissionLevel: PermissionLevel.Write,
1312
+ });
1313
+
1314
+ // Set member financial data status
1315
+ member.details.requiresFinancialSupport = BooleanStatus.create({ value: true });
1316
+ await member.save();
1317
+
1318
+ const body = IDRegisterCheckout.create({
1319
+ cart: IDRegisterCart.create({
1320
+ items: [
1321
+ IDRegisterItem.create({
1322
+ id: uuidv4(),
1323
+ replaceRegistrationIds: [],
1324
+ options: [],
1325
+ groupPrice,
1326
+ organizationId: organization.id,
1327
+ groupId: group.id,
1328
+ memberId: member.id,
1329
+ }),
1330
+ ],
1331
+ balanceItems: [
1332
+ ],
1333
+ deleteRegistrationIds: [],
1334
+ }),
1335
+ administrationFee: 0,
1336
+ freeContribution: 0,
1337
+ paymentMethod: PaymentMethod.PointOfSale,
1338
+ totalPrice: 25_00, // This is wrong, but the admin should not know that since he/she does not know the actual financial status of the member
1339
+ asOrganizationId: organization.id,
1340
+ });
1341
+
1342
+ const response = await post(body, organization, token);
1343
+ expect(response.body.registrations.length).toBe(1);
1344
+
1345
+ // No balance information leaks
1346
+ expect(response.body.registrations[0].balances.length).toEqual(0);
1347
+
1348
+ // Check acual charged amount
1349
+ const registration = (await Registration.getByID(response.body.registrations[0].id))!;
1350
+ expect(registration).toBeDefined();
1351
+ expect(registration.discounts).toMatchMap(new Map([]));
1352
+
1353
+ // Check balance has been added
1354
+ await assertBalances({ member }, [
1355
+ {
1356
+ type: BalanceItemType.Registration,
1357
+ registrationId: registration.id,
1358
+ amount: 1,
1359
+ price: 12_50,
1360
+ status: BalanceItemStatus.Due,
1361
+ priceOpen: 12_50,
1362
+ pricePending: 0,
1363
+ },
1364
+ ]);
1365
+
1366
+ // Check member in response doesn't include sensitive data
1367
+ const memberInResponse = response.body.members.members.find(m => m.id === member.id)!;
1368
+ expect(memberInResponse).toBeDefined();
1369
+ expect(memberInResponse.details.requiresFinancialSupport).toBe(null);
1370
+ const returnedRegistration = memberInResponse.registrations.find(r => r.id === registration.id)!;
1371
+ expect(returnedRegistration).toBeDefined();
1372
+ expect(returnedRegistration.balances.length).toBe(0);
1373
+ });
1374
+
1375
+ /**
1376
+ * Negative test for previous test
1377
+ */
1378
+ test('Can register a member as full admin', async () => {
1379
+ const { member, groupPrice, group, organization, token } = await initData({});
1380
+
1381
+ const body = IDRegisterCheckout.create({
1382
+ cart: IDRegisterCart.create({
1383
+ items: [
1384
+ IDRegisterItem.create({
1385
+ id: uuidv4(),
1386
+ replaceRegistrationIds: [],
1387
+ options: [],
1388
+ groupPrice,
1389
+ organizationId: organization.id,
1390
+ groupId: group.id,
1391
+ memberId: member.id,
1392
+ }),
1393
+ ],
1394
+ balanceItems: [
1395
+ ],
1396
+ deleteRegistrationIds: [],
1397
+ }),
1398
+ administrationFee: 0,
1399
+ freeContribution: 0,
1400
+ paymentMethod: PaymentMethod.PointOfSale,
1401
+ totalPrice: 25_00,
1402
+ asOrganizationId: organization.id,
1403
+ });
1404
+
1405
+ const response = await post(body, organization, token);
1406
+ expect(response.body.registrations.length).toBe(1);
1407
+
1408
+ // Check acual charged amount
1409
+ const registration = (await Registration.getByID(response.body.registrations[0].id))!;
1410
+ expect(registration).toBeDefined();
1411
+ expect(registration.discounts).toMatchMap(new Map([]));
1412
+
1413
+ // Check balance has been added
1414
+ await assertBalances({ member }, [
1415
+ {
1416
+ type: BalanceItemType.Registration,
1417
+ registrationId: registration.id,
1418
+ amount: 1,
1419
+ price: 25_00,
1420
+ status: BalanceItemStatus.Due,
1421
+ priceOpen: 25_00,
1422
+ pricePending: 0,
1423
+ },
1424
+ ]);
1425
+
1426
+ // Check member in response does include financial data
1427
+ const memberInResponse = response.body.members.members.find(m => m.id === member.id)!;
1428
+ expect(memberInResponse).toBeDefined();
1429
+ const returnedRegistration = memberInResponse.registrations.find(r => r.id === registration.id)!;
1430
+ expect(returnedRegistration).toBeDefined();
1431
+ expect(returnedRegistration.balances.length).not.toBe(0);
1432
+ });
1433
+
1434
+ test('Can register a member as admin with write permission to new group only', async () => {
1435
+ // read permission to existing member, write permission to new group you want to register the member in
1436
+ const { member, groupPrice, group, organization, token } = await initData({
1437
+ permissionLevel: PermissionLevel.None,
1438
+ groupPermissionLevel: PermissionLevel.Write,
1439
+ memberPermissionLevel: PermissionLevel.Read,
1440
+ });
1441
+
1442
+ const body = IDRegisterCheckout.create({
1443
+ cart: IDRegisterCart.create({
1444
+ items: [
1445
+ IDRegisterItem.create({
1446
+ id: uuidv4(),
1447
+ replaceRegistrationIds: [],
1448
+ options: [],
1449
+ groupPrice,
1450
+ organizationId: organization.id,
1451
+ groupId: group.id,
1452
+ memberId: member.id,
1453
+ }),
1454
+ ],
1455
+ balanceItems: [
1456
+ ],
1457
+ deleteRegistrationIds: [],
1458
+ }),
1459
+ administrationFee: 0,
1460
+ freeContribution: 0,
1461
+ paymentMethod: PaymentMethod.PointOfSale,
1462
+ totalPrice: 25_00,
1463
+ asOrganizationId: organization.id,
1464
+ });
1465
+
1466
+ const response = await post(body, organization, token);
1467
+ expect(response.body.registrations.length).toBe(1);
1468
+
1469
+ // No balance information leaks
1470
+ expect(response.body.registrations[0].balances.length).toEqual(0);
1471
+
1472
+ // Check acual charged amount
1473
+ const registration = (await Registration.getByID(response.body.registrations[0].id))!;
1474
+ expect(registration).toBeDefined();
1475
+ expect(registration.discounts).toMatchMap(new Map([]));
1476
+
1477
+ // Check balance has been added
1478
+ await assertBalances({ member }, [
1479
+ {
1480
+ type: BalanceItemType.Registration,
1481
+ registrationId: registration.id,
1482
+ amount: 1,
1483
+ price: 25_00,
1484
+ status: BalanceItemStatus.Due,
1485
+ priceOpen: 25_00,
1486
+ pricePending: 0,
1487
+ },
1488
+ ]);
1489
+ });
1490
+
1491
+ test('Cannot register a member with only read permissions', async () => {
1492
+ const { member, groupPrice, group, organization, token } = await initData({
1493
+ permissionLevel: PermissionLevel.None,
1494
+ groupPermissionLevel: PermissionLevel.Read,
1495
+ memberPermissionLevel: PermissionLevel.Read,
1496
+ });
1497
+
1498
+ const body = IDRegisterCheckout.create({
1499
+ cart: IDRegisterCart.create({
1500
+ items: [
1501
+ IDRegisterItem.create({
1502
+ id: uuidv4(),
1503
+ replaceRegistrationIds: [],
1504
+ options: [],
1505
+ groupPrice,
1506
+ organizationId: organization.id,
1507
+ groupId: group.id,
1508
+ memberId: member.id,
1509
+ }),
1510
+ ],
1511
+ balanceItems: [
1512
+ ],
1513
+ deleteRegistrationIds: [],
1514
+ }),
1515
+ administrationFee: 0,
1516
+ freeContribution: 0,
1517
+ paymentMethod: PaymentMethod.PointOfSale,
1518
+ totalPrice: 25_00,
1519
+ asOrganizationId: organization.id,
1520
+ });
1521
+
1522
+ // send request and check occupancy
1523
+ await expect(async () => await post(body, organization, token)).rejects.toThrow('No permission to register in this group');
1524
+ });
1525
+
1526
+ test('Cannot register a member with only read permissions even when having full permissions to the specific member', async () => {
1527
+ const { member, groupPrice, group, organization, token } = await initData({
1528
+ // No global organization permissions
1529
+ permissionLevel: PermissionLevel.None,
1530
+ groupPermissionLevel: PermissionLevel.Read, // Only read access to the new group
1531
+ memberPermissionLevel: PermissionLevel.Full, // Full access to the member
1532
+ });
1533
+
1534
+ const body = IDRegisterCheckout.create({
1535
+ cart: IDRegisterCart.create({
1536
+ items: [
1537
+ IDRegisterItem.create({
1538
+ id: uuidv4(),
1539
+ replaceRegistrationIds: [],
1540
+ options: [],
1541
+ groupPrice,
1542
+ organizationId: organization.id,
1543
+ groupId: group.id,
1544
+ memberId: member.id,
1545
+ }),
1546
+ ],
1547
+ balanceItems: [
1548
+ ],
1549
+ deleteRegistrationIds: [],
1550
+ }),
1551
+ administrationFee: 0,
1552
+ freeContribution: 0,
1553
+ paymentMethod: PaymentMethod.PointOfSale,
1554
+ totalPrice: 25_00,
1555
+ asOrganizationId: organization.id,
1556
+ });
1557
+
1558
+ // send request and check occupancy
1559
+ await expect(async () => await post(body, organization, token)).rejects.toThrow('No permission to register in this group');
1560
+ });
1249
1561
  });
1250
1562
 
1251
1563
  describe('Register by other organization', () => {
1252
1564
  beforeEach(() => {
1253
1565
  defaultPermissionLevel = PermissionLevel.Full;
1566
+ defaultLinkMembersToUser = false;
1254
1567
  });
1255
1568
 
1256
1569
  async function initDualData(options?: Parameters<typeof initData>[0]) {
1257
- const base = await initData(options);
1570
+ const base = await initData({ ...options, permissionLevel: PermissionLevel.None });
1258
1571
 
1259
1572
  base.group.settings.allowRegistrationsByOrganization = true;
1260
1573
  await base.group.save();
@@ -1270,6 +1583,18 @@ describe('Endpoint.RegisterMembers', () => {
1270
1583
  });
1271
1584
  await base.user.save();
1272
1585
 
1586
+ // Give the user permission to the original member
1587
+ const genericGroup = await new GroupFactory({
1588
+ organization: organization2,
1589
+ price: 0,
1590
+ }).create();
1591
+
1592
+ await new RegistrationFactory({
1593
+ member: base.member,
1594
+ group: genericGroup,
1595
+ groupPrice: genericGroup.settings.prices[0],
1596
+ }).create();
1597
+
1273
1598
  return {
1274
1599
  ...base,
1275
1600
  organization2,
@@ -1760,9 +2085,11 @@ describe('Endpoint.RegisterMembers', () => {
1760
2085
  // #endregion
1761
2086
  });
1762
2087
 
1763
- test('Move registration should fail if admin of same organization', async () => {
1764
- // #region arrange
1765
- const { organization, group: group1, groupPrice: groupPrice1, token, member } = await initData();
2088
+ test('Move registration should fail via member portal (no asOrganizationId set)', async () => {
2089
+ const { organization, group: group1, groupPrice: groupPrice1, token, member } = await initData({
2090
+ permissionLevel: defaultPermissionLevel,
2091
+ linkMembersToUser: true,
2092
+ });
1766
2093
 
1767
2094
  const registration = await new RegistrationFactory({
1768
2095
  member,
@@ -1801,14 +2128,9 @@ describe('Endpoint.RegisterMembers', () => {
1801
2128
  totalPrice: 5,
1802
2129
  customer: null,
1803
2130
  });
1804
- // #endregion
1805
-
1806
- // #region act and assert
1807
2131
 
1808
2132
  // send request and check occupancy
1809
2133
  await expect(async () => await post(body, organization, token)).rejects.toThrow('Not allowed to move registrations');
1810
-
1811
- // #endregion
1812
2134
  });
1813
2135
  });
1814
2136
 
@@ -1885,8 +2207,10 @@ describe('Endpoint.RegisterMembers', () => {
1885
2207
  });
1886
2208
 
1887
2209
  test('Should throw error if deleting registrations as normal member', async () => {
1888
- // #region arrange
1889
- const { member, group: group1, groupPrice: groupPrice1, organization: organization1, token } = await initData();
2210
+ const { member, group: group1, groupPrice: groupPrice1, organization: organization1, token } = await initData({
2211
+ permissionLevel: PermissionLevel.Full,
2212
+ linkMembersToUser: true,
2213
+ });
1890
2214
  await initPayconiq({ organization: organization1 });
1891
2215
 
1892
2216
  const registration = await new RegistrationFactory({
@@ -1933,7 +2257,6 @@ describe('Endpoint.RegisterMembers', () => {
1933
2257
  });
1934
2258
 
1935
2259
  test('Should deactivate registration', async () => {
1936
- // #region arrange
1937
2260
  const { member, group: group1, groupPrice: groupPrice1, organization, token } = await initData();
1938
2261
 
1939
2262
  const registration = await new RegistrationFactory({
@@ -1973,15 +2296,12 @@ describe('Endpoint.RegisterMembers', () => {
1973
2296
  asOrganizationId: organization.id,
1974
2297
  customer: null,
1975
2298
  });
1976
- // #endregion
1977
2299
 
1978
- // #region act and assert
1979
2300
  await post(body, organization, token);
1980
2301
 
1981
2302
  const updatedRegistration = await Registration.getByID(registration.id);
1982
2303
  expect(updatedRegistration).toBeDefined();
1983
2304
  expect(updatedRegistration!.deactivatedAt).not.toBe(null);
1984
- // #endregion
1985
2305
  });
1986
2306
 
1987
2307
  test('Should fail if invalid cancelation fee', async () => {
@@ -2059,9 +2379,10 @@ describe('Endpoint.RegisterMembers', () => {
2059
2379
  }
2060
2380
  });
2061
2381
 
2062
- test('Delete by member should fail if no permission to delete registration', async () => {
2063
- // #region arrange
2064
- const { member, group: group1, groupPrice: groupPrice1, organization, token } = await initData();
2382
+ test('Cannot delete registrations via the member portal', async () => {
2383
+ const { member, group: group1, groupPrice: groupPrice1, organization, token } = await initData({
2384
+ linkMembersToUser: true,
2385
+ });
2065
2386
 
2066
2387
  const registration = await new RegistrationFactory({
2067
2388
  member,
@@ -2099,18 +2420,51 @@ describe('Endpoint.RegisterMembers', () => {
2099
2420
  totalPrice: 5,
2100
2421
  customer: null,
2101
2422
  });
2102
- // #endregion
2103
2423
 
2104
- // #region act and assert
2105
2424
  await expect(async () => await post(body, organization, token))
2106
2425
  .rejects
2107
2426
  .toThrow(new RegExp('Permission denied: you are not allowed to delete registrations'));
2108
- // #endregion
2109
2427
  });
2110
2428
 
2111
- test('Delete by organization should fail if no permission to delete registration', async () => {
2112
- // #region arrange
2113
- const { member, group: group1, groupPrice: groupPrice1, organization, token } = await initData({ permissionLevel: PermissionLevel.Read });
2429
+ test('Cannot delete registrations as admin if no write permission to group', async () => {
2430
+ const { member, group: group1, groupPrice: groupPrice1, organization, token } = await initData({
2431
+ permissionLevel: PermissionLevel.None,
2432
+ groupPermissionLevel: PermissionLevel.Read,
2433
+ memberPermissionLevel: PermissionLevel.Full,
2434
+ linkMembersToUser: false,
2435
+ });
2436
+
2437
+ const registration = await new RegistrationFactory({
2438
+ member,
2439
+ group: group1,
2440
+ groupPrice: groupPrice1,
2441
+ }).create();
2442
+
2443
+ const body = IDRegisterCheckout.create({
2444
+ cart: IDRegisterCart.create({
2445
+ items: [],
2446
+ balanceItems: [],
2447
+ deleteRegistrationIds: [registration.id],
2448
+ }),
2449
+ administrationFee: 0,
2450
+ freeContribution: 0,
2451
+ paymentMethod: PaymentMethod.PointOfSale,
2452
+ totalPrice: 0,
2453
+ customer: null,
2454
+ asOrganizationId: organization.id,
2455
+ });
2456
+
2457
+ await expect(async () => await post(body, organization, token)).rejects.toThrow(/No permission to delete this registration/);
2458
+ });
2459
+
2460
+ /**
2461
+ * userManager does not allow unregistering your own members, even when you have some admin permissions and set asOrganizationId
2462
+ */
2463
+ test('Cannot delete registrations as admin if no write permission to group but does have userManager permissions', async () => {
2464
+ const { member, group: group1, groupPrice: groupPrice1, organization, token } = await initData({
2465
+ permissionLevel: PermissionLevel.Read,
2466
+ linkMembersToUser: true,
2467
+ });
2114
2468
 
2115
2469
  const registration = await new RegistrationFactory({
2116
2470
  member,
@@ -2131,11 +2485,8 @@ describe('Endpoint.RegisterMembers', () => {
2131
2485
  customer: null,
2132
2486
  asOrganizationId: organization.id,
2133
2487
  });
2134
- // #endregion
2135
2488
 
2136
- // #region act and assert
2137
2489
  await expect(async () => await post(body, organization, token)).rejects.toThrow(/No permission to delete this registration/);
2138
- // #endregion
2139
2490
  });
2140
2491
 
2141
2492
  test('Should fail if registration does not exist anymore', async () => {