@appsemble/cli 0.27.12 → 0.28.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.
@@ -0,0 +1,606 @@
1
+ import { createServer, createTestUser, models, setArgv, useTestDatabase } from '@appsemble/server';
2
+ import { TeamRole } from '@appsemble/utils';
3
+ import { setTestApp } from 'axios-test-instance';
4
+ import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';
5
+ import { initAxios } from './initAxios.js';
6
+ import { createTeam, deleteMember, deleteTeam, inviteMember, updateMember, updateTeam, } from './team.js';
7
+ import { authorizeCLI } from './testUtils.js';
8
+ const { App, AppMember, BlockVersion, Organization, OrganizationMember, Team, TeamMember } = models;
9
+ const argv = { host: 'http://localhost', secret: 'test', aesSecret: 'testSecret' };
10
+ let user;
11
+ let organization;
12
+ let testApp;
13
+ useTestDatabase(import.meta);
14
+ beforeAll(() => {
15
+ vi.useFakeTimers();
16
+ setArgv(argv);
17
+ });
18
+ beforeEach(async () => {
19
+ vi.clearAllTimers();
20
+ vi.setSystemTime(0);
21
+ const server = await createServer();
22
+ testApp = await setTestApp(server);
23
+ initAxios({ remote: testApp.defaults.baseURL });
24
+ user = await createTestUser();
25
+ organization = await Organization.create({
26
+ id: 'testorganization',
27
+ name: 'Test Organization',
28
+ });
29
+ await OrganizationMember.create({
30
+ OrganizationId: organization.id,
31
+ UserId: user.id,
32
+ role: 'Owner',
33
+ });
34
+ await Organization.create({ id: 'appsemble', name: 'Appsemble' });
35
+ await BlockVersion.create({
36
+ name: 'test',
37
+ OrganizationId: 'appsemble',
38
+ version: '0.0.0',
39
+ parameters: {
40
+ type: 'object',
41
+ properties: {
42
+ foo: {
43
+ type: 'number',
44
+ },
45
+ },
46
+ },
47
+ });
48
+ });
49
+ afterAll(() => {
50
+ vi.useRealTimers();
51
+ });
52
+ describe('createTeam', () => {
53
+ it('should create a new team', async () => {
54
+ const app = await App.create({
55
+ definition: {
56
+ name: 'Test App',
57
+ defaultPage: 'Test Page',
58
+ security: {
59
+ teams: {
60
+ join: 'anyone',
61
+ invite: [],
62
+ },
63
+ default: {
64
+ role: 'Reader',
65
+ policy: 'everyone',
66
+ },
67
+ roles: {
68
+ Reader: {},
69
+ },
70
+ },
71
+ },
72
+ path: 'test-app',
73
+ vapidPublicKey: 'a',
74
+ vapidPrivateKey: 'b',
75
+ OrganizationId: organization.id,
76
+ });
77
+ const clientCredentials = await authorizeCLI('teams:write', testApp);
78
+ await createTeam({
79
+ appId: app.id,
80
+ name: 'test',
81
+ remote: testApp.defaults.baseURL,
82
+ clientCredentials,
83
+ });
84
+ const team = await Team.findOne();
85
+ expect(team).toMatchInlineSnapshot(`
86
+ {
87
+ "AppId": 1,
88
+ "annotations": null,
89
+ "created": 1970-01-01T00:00:00.000Z,
90
+ "id": 1,
91
+ "name": "test",
92
+ "updated": 1970-01-01T00:00:00.000Z,
93
+ }
94
+ `);
95
+ });
96
+ it('should throw if app does not exist', async () => {
97
+ const clientCredentials = await authorizeCLI('teams:write', testApp);
98
+ await expect(() => createTeam({
99
+ appId: 1,
100
+ name: 'test',
101
+ remote: testApp.defaults.baseURL,
102
+ clientCredentials,
103
+ })).rejects.toThrow('Request failed with status code 404');
104
+ });
105
+ });
106
+ describe('deleteTeam', () => {
107
+ it('should delete a team', async () => {
108
+ const app = await App.create({
109
+ definition: {
110
+ name: 'Test App',
111
+ defaultPage: 'Test Page',
112
+ security: {
113
+ teams: {
114
+ join: 'anyone',
115
+ invite: [],
116
+ },
117
+ default: {
118
+ role: 'Reader',
119
+ policy: 'everyone',
120
+ },
121
+ roles: {
122
+ Reader: {},
123
+ },
124
+ },
125
+ },
126
+ path: 'test-app',
127
+ vapidPublicKey: 'a',
128
+ vapidPrivateKey: 'b',
129
+ OrganizationId: organization.id,
130
+ });
131
+ const team = await Team.create({
132
+ AppId: app.id,
133
+ name: 'test',
134
+ });
135
+ const clientCredentials = await authorizeCLI('teams:write', testApp);
136
+ await deleteTeam({
137
+ appId: app.id,
138
+ id: team.id,
139
+ remote: testApp.defaults.baseURL,
140
+ clientCredentials,
141
+ });
142
+ expect(team).toMatchInlineSnapshot(`
143
+ {
144
+ "AppId": 1,
145
+ "annotations": null,
146
+ "created": 1970-01-01T00:00:00.000Z,
147
+ "id": 1,
148
+ "name": "test",
149
+ "updated": 1970-01-01T00:00:00.000Z,
150
+ }
151
+ `);
152
+ const foundTeam = await Team.findOne();
153
+ expect(foundTeam).toBeNull();
154
+ });
155
+ it('should throw an error if the team does not exist', async () => {
156
+ const app = await App.create({
157
+ definition: {
158
+ name: 'Test App',
159
+ defaultPage: 'Test Page',
160
+ security: {
161
+ teams: {
162
+ join: 'anyone',
163
+ invite: [],
164
+ },
165
+ default: {
166
+ role: 'Reader',
167
+ policy: 'everyone',
168
+ },
169
+ roles: {
170
+ Reader: {},
171
+ },
172
+ },
173
+ },
174
+ path: 'test-app',
175
+ vapidPublicKey: 'a',
176
+ vapidPrivateKey: 'b',
177
+ OrganizationId: organization.id,
178
+ });
179
+ const clientCredentials = await authorizeCLI('teams:write', testApp);
180
+ await expect(() => deleteTeam({ appId: app.id, id: 1, remote: testApp.defaults.baseURL, clientCredentials })).rejects.toThrow('Request failed with status code 404');
181
+ });
182
+ });
183
+ describe('updateTeam', () => {
184
+ it('should update a team', async () => {
185
+ const app = await App.create({
186
+ definition: {
187
+ name: 'Test App',
188
+ defaultPage: 'Test Page',
189
+ security: {
190
+ teams: {
191
+ join: 'anyone',
192
+ invite: [],
193
+ },
194
+ default: {
195
+ role: 'Reader',
196
+ policy: 'everyone',
197
+ },
198
+ roles: {
199
+ Reader: {},
200
+ },
201
+ },
202
+ },
203
+ path: 'test-app',
204
+ vapidPublicKey: 'a',
205
+ vapidPrivateKey: 'b',
206
+ OrganizationId: organization.id,
207
+ });
208
+ const team = await Team.create({
209
+ AppId: app.id,
210
+ name: 'test',
211
+ });
212
+ await authorizeCLI('teams:write', testApp);
213
+ await updateTeam({
214
+ appId: app.id,
215
+ id: team.id,
216
+ remote: testApp.defaults.baseURL,
217
+ name: 'test2',
218
+ });
219
+ await team.reload();
220
+ expect(team).toMatchInlineSnapshot(`
221
+ {
222
+ "AppId": 1,
223
+ "annotations": null,
224
+ "created": 1970-01-01T00:00:00.000Z,
225
+ "id": 1,
226
+ "name": "test2",
227
+ "updated": 1970-01-01T00:00:00.000Z,
228
+ }
229
+ `);
230
+ });
231
+ it('should throw an error if the team does not exist', async () => {
232
+ const app = await App.create({
233
+ definition: {
234
+ name: 'Test App',
235
+ defaultPage: 'Test Page',
236
+ security: {
237
+ teams: {
238
+ join: 'anyone',
239
+ invite: [],
240
+ },
241
+ default: {
242
+ role: 'Reader',
243
+ policy: 'everyone',
244
+ },
245
+ roles: {
246
+ Reader: {},
247
+ },
248
+ },
249
+ },
250
+ path: 'test-app',
251
+ vapidPublicKey: 'a',
252
+ vapidPrivateKey: 'b',
253
+ OrganizationId: organization.id,
254
+ });
255
+ const team = await Team.create({
256
+ AppId: app.id,
257
+ name: 'test',
258
+ });
259
+ await team.destroy();
260
+ const clientCredentials = await authorizeCLI('teams:write', testApp);
261
+ await expect(() => updateTeam({
262
+ appId: app.id,
263
+ id: team.id,
264
+ remote: testApp.defaults.baseURL,
265
+ name: 'test2',
266
+ clientCredentials,
267
+ })).rejects.toThrow('Request failed with status code 404');
268
+ });
269
+ it('should throw an error if the user is not authenticated', async () => {
270
+ const app = await App.create({
271
+ definition: {
272
+ name: 'Test App',
273
+ defaultPage: 'Test Page',
274
+ security: {
275
+ teams: {
276
+ join: 'anyone',
277
+ invite: [],
278
+ },
279
+ default: {
280
+ role: 'Reader',
281
+ policy: 'everyone',
282
+ },
283
+ roles: {
284
+ Reader: {},
285
+ },
286
+ },
287
+ },
288
+ path: 'test-app',
289
+ vapidPublicKey: 'a',
290
+ vapidPrivateKey: 'b',
291
+ OrganizationId: organization.id,
292
+ });
293
+ const team = await Team.create({
294
+ AppId: app.id,
295
+ name: 'test',
296
+ });
297
+ await expect(() => updateTeam({
298
+ appId: app.id,
299
+ id: team.id,
300
+ remote: testApp.defaults.baseURL,
301
+ name: 'test2',
302
+ })).rejects.toThrow('Request failed with status code 401');
303
+ });
304
+ });
305
+ describe('inviteMember', () => {
306
+ it('should invite a member to a team', async () => {
307
+ const app = await App.create({
308
+ definition: {
309
+ name: 'Test App',
310
+ defaultPage: 'Test Page',
311
+ security: {
312
+ teams: {
313
+ join: 'anyone',
314
+ invite: [],
315
+ },
316
+ default: {
317
+ role: 'Reader',
318
+ policy: 'everyone',
319
+ },
320
+ roles: {
321
+ Reader: {},
322
+ },
323
+ },
324
+ },
325
+ path: 'test-app',
326
+ vapidPublicKey: 'a',
327
+ vapidPrivateKey: 'b',
328
+ OrganizationId: organization.id,
329
+ });
330
+ const team = await Team.create({
331
+ AppId: app.id,
332
+ name: 'test',
333
+ });
334
+ await AppMember.create({
335
+ AppId: app.id,
336
+ UserId: user.id,
337
+ role: 'Manager',
338
+ name: user.name,
339
+ });
340
+ await authorizeCLI('teams:write', testApp);
341
+ await inviteMember({
342
+ appId: app.id,
343
+ id: team.id,
344
+ remote: testApp.defaults.baseURL,
345
+ user: user.id,
346
+ });
347
+ const member = await TeamMember.findOne();
348
+ expect(member.dataValues).toMatchInlineSnapshot({
349
+ AppMemberId: expect.any(String),
350
+ }, `
351
+ {
352
+ "AppMemberId": Any<String>,
353
+ "TeamId": 1,
354
+ "created": 1970-01-01T00:00:00.000Z,
355
+ "role": "member",
356
+ "updated": 1970-01-01T00:00:00.000Z,
357
+ }
358
+ `);
359
+ });
360
+ it('should throw an error if the user is not AppMember', async () => {
361
+ const app = await App.create({
362
+ definition: {
363
+ name: 'Test App',
364
+ defaultPage: 'Test Page',
365
+ security: {
366
+ teams: {
367
+ join: 'anyone',
368
+ invite: [],
369
+ },
370
+ default: {
371
+ role: 'Reader',
372
+ policy: 'everyone',
373
+ },
374
+ roles: {
375
+ Reader: {},
376
+ },
377
+ },
378
+ },
379
+ path: 'test-app',
380
+ vapidPublicKey: 'a',
381
+ vapidPrivateKey: 'b',
382
+ OrganizationId: organization.id,
383
+ });
384
+ const team = await Team.create({
385
+ AppId: app.id,
386
+ name: 'test',
387
+ });
388
+ await authorizeCLI('teams:write', testApp);
389
+ await expect(() => inviteMember({ appId: app.id, id: team.id, remote: testApp.defaults.baseURL, user: user.id })).rejects.toThrow('Request failed with status code 404');
390
+ });
391
+ it('should throw an error if the team does not exist', async () => {
392
+ const app = await App.create({
393
+ definition: {
394
+ name: 'Test App',
395
+ defaultPage: 'Test Page',
396
+ security: {
397
+ teams: {
398
+ join: 'anyone',
399
+ invite: [],
400
+ },
401
+ default: {
402
+ role: 'Reader',
403
+ policy: 'everyone',
404
+ },
405
+ roles: {
406
+ Reader: {},
407
+ },
408
+ },
409
+ },
410
+ path: 'test-app',
411
+ vapidPublicKey: 'a',
412
+ vapidPrivateKey: 'b',
413
+ OrganizationId: organization.id,
414
+ });
415
+ const team = await Team.create({
416
+ AppId: app.id,
417
+ name: 'test',
418
+ });
419
+ await team.destroy();
420
+ await authorizeCLI('teams:write', testApp);
421
+ await expect(() => inviteMember({ appId: app.id, id: team.id, remote: testApp.defaults.baseURL, user: user.id })).rejects.toThrow('Request failed with status code 404');
422
+ });
423
+ });
424
+ describe('updateMember', () => {
425
+ it('should update role of a team member', async () => {
426
+ const app = await App.create({
427
+ definition: {
428
+ name: 'Test App',
429
+ defaultPage: 'Test Page',
430
+ security: {
431
+ teams: {
432
+ join: 'anyone',
433
+ invite: [],
434
+ },
435
+ default: {
436
+ role: 'Reader',
437
+ policy: 'everyone',
438
+ },
439
+ roles: {
440
+ Reader: {},
441
+ },
442
+ },
443
+ },
444
+ path: 'test-app',
445
+ vapidPublicKey: 'a',
446
+ vapidPrivateKey: 'b',
447
+ OrganizationId: organization.id,
448
+ });
449
+ const team = await Team.create({
450
+ AppId: app.id,
451
+ name: 'test',
452
+ });
453
+ const member = await AppMember.create({
454
+ AppId: app.id,
455
+ UserId: user.id,
456
+ role: 'Manager',
457
+ name: user.name,
458
+ });
459
+ const teamMember = await TeamMember.create({
460
+ TeamId: team.id,
461
+ AppMemberId: member.id,
462
+ });
463
+ await authorizeCLI('teams:write', testApp);
464
+ await updateMember({
465
+ appId: app.id,
466
+ remote: testApp.defaults.baseURL,
467
+ user: user.id,
468
+ role: TeamRole.Manager,
469
+ id: team.id,
470
+ });
471
+ expect(teamMember.role).toBe('member');
472
+ await teamMember.reload();
473
+ expect(teamMember.role).toBe('manager');
474
+ });
475
+ it('should throw an error if the user is not a TeamMember', async () => {
476
+ const app = await App.create({
477
+ definition: {
478
+ name: 'Test App',
479
+ defaultPage: 'Test Page',
480
+ security: {
481
+ teams: {
482
+ join: 'anyone',
483
+ invite: [],
484
+ },
485
+ default: {
486
+ role: 'Reader',
487
+ policy: 'everyone',
488
+ },
489
+ roles: {
490
+ Reader: {},
491
+ },
492
+ },
493
+ },
494
+ path: 'test-app',
495
+ vapidPublicKey: 'a',
496
+ vapidPrivateKey: 'b',
497
+ OrganizationId: organization.id,
498
+ });
499
+ const team = await Team.create({
500
+ AppId: app.id,
501
+ name: 'test',
502
+ });
503
+ await AppMember.create({
504
+ AppId: app.id,
505
+ UserId: user.id,
506
+ role: 'Manager',
507
+ name: user.name,
508
+ });
509
+ await authorizeCLI('teams:write', testApp);
510
+ await expect(() => updateMember({
511
+ appId: app.id,
512
+ remote: testApp.defaults.baseURL,
513
+ user: user.id,
514
+ role: TeamRole.Manager,
515
+ id: team.id,
516
+ })).rejects.toThrow('Request failed with status code 400');
517
+ });
518
+ });
519
+ describe('deleteMember', () => {
520
+ it('should delete a team member', async () => {
521
+ const app = await App.create({
522
+ definition: {
523
+ name: 'Test App',
524
+ defaultPage: 'Test Page',
525
+ security: {
526
+ teams: {
527
+ join: 'anyone',
528
+ invite: [],
529
+ },
530
+ default: {
531
+ role: 'Reader',
532
+ policy: 'everyone',
533
+ },
534
+ roles: {
535
+ Reader: {},
536
+ },
537
+ },
538
+ },
539
+ path: 'test-app',
540
+ vapidPublicKey: 'a',
541
+ vapidPrivateKey: 'b',
542
+ OrganizationId: organization.id,
543
+ });
544
+ const team = await Team.create({
545
+ AppId: app.id,
546
+ name: 'test',
547
+ });
548
+ const member = await AppMember.create({
549
+ AppId: app.id,
550
+ UserId: user.id,
551
+ role: 'Manager',
552
+ name: user.name,
553
+ });
554
+ await TeamMember.create({
555
+ TeamId: team.id,
556
+ AppMemberId: member.id,
557
+ });
558
+ await authorizeCLI('teams:write', testApp);
559
+ await deleteMember({
560
+ id: team.id,
561
+ appId: app.id,
562
+ user: user.id,
563
+ remote: testApp.defaults.baseURL,
564
+ });
565
+ const foundTeamMember = await TeamMember.findOne();
566
+ expect(foundTeamMember).toBeNull();
567
+ });
568
+ it('should throw an error if the TeamMember does not exist', async () => {
569
+ const app = await App.create({
570
+ definition: {
571
+ name: 'Test App',
572
+ defaultPage: 'Test Page',
573
+ security: {
574
+ teams: {
575
+ join: 'anyone',
576
+ invite: [],
577
+ },
578
+ default: {
579
+ role: 'Reader',
580
+ policy: 'everyone',
581
+ },
582
+ roles: {
583
+ Reader: {},
584
+ },
585
+ },
586
+ },
587
+ path: 'test-app',
588
+ vapidPublicKey: 'a',
589
+ vapidPrivateKey: 'b',
590
+ OrganizationId: organization.id,
591
+ });
592
+ const team = await Team.create({
593
+ AppId: app.id,
594
+ name: 'test',
595
+ });
596
+ await AppMember.create({
597
+ AppId: app.id,
598
+ UserId: user.id,
599
+ role: 'Manager',
600
+ name: user.name,
601
+ });
602
+ await authorizeCLI('teams:write', testApp);
603
+ await expect(() => deleteMember({ appId: app.id, remote: testApp.defaults.baseURL, user: user.id, id: team.id })).rejects.toThrow('Request failed with status code 400');
604
+ });
605
+ });
606
+ //# sourceMappingURL=team.test.js.map
@@ -0,0 +1,2 @@
1
+ import { type AxiosTestInstance } from 'axios-test-instance';
2
+ export declare function authorizeCLI(scopes: string, testingApp: AxiosTestInstance): Promise<string>;
@@ -0,0 +1,11 @@
1
+ import { authenticate } from '@appsemble/node-utils';
2
+ import { authorizeClientCredentials } from '@appsemble/server';
3
+ import { hash } from 'bcrypt';
4
+ export async function authorizeCLI(scopes, testingApp) {
5
+ const OAuth2AuthorizationCode = await authorizeClientCredentials(scopes);
6
+ const { id, secret } = OAuth2AuthorizationCode;
7
+ await OAuth2AuthorizationCode.update({ secret: await hash(secret, 10) });
8
+ await authenticate(testingApp.defaults.baseURL, scopes, `${id}:${secret}`);
9
+ return `${id}:${secret}`;
10
+ }
11
+ //# sourceMappingURL=testUtils.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@appsemble/cli",
3
- "version": "0.27.12",
3
+ "version": "0.28.0",
4
4
  "description": "The CLI for developing with Appsemble apps and blocks",
5
5
  "keywords": [
6
6
  "app",
@@ -42,9 +42,9 @@
42
42
  "test": "vitest"
43
43
  },
44
44
  "dependencies": {
45
- "@appsemble/node-utils": "0.27.12",
46
- "@appsemble/types": "0.27.12",
47
- "@appsemble/utils": "0.27.12",
45
+ "@appsemble/node-utils": "0.28.0",
46
+ "@appsemble/types": "0.28.0",
47
+ "@appsemble/utils": "0.28.0",
48
48
  "@fortawesome/fontawesome-common-types": "^6.0.0",
49
49
  "@koa/cors": "^4.0.0",
50
50
  "@odata/parser": "^0.2.0",
@@ -87,8 +87,8 @@
87
87
  "yargs": "^17.0.0"
88
88
  },
89
89
  "devDependencies": {
90
- "@appsemble/types": "0.27.12",
91
- "@appsemble/server": "0.27.12",
90
+ "@appsemble/types": "0.28.0",
91
+ "@appsemble/server": "0.28.0",
92
92
  "bcrypt": "^5.0.0",
93
93
  "axios-test-instance": "^7.0.0",
94
94
  "@types/concat-stream": "^2.0.0",
@@ -5,6 +5,7 @@ describe('parseQuery', () => {
5
5
  const queryParams = {
6
6
  $filter: "AuthorId eq 1 and (Title ne 'example' or Year gt 2020)",
7
7
  $orderby: 'Title asc,Year desc',
8
+ resourceDefinition: {},
8
9
  };
9
10
  const expectedParsedQuery = {
10
11
  where: {
@@ -27,6 +28,7 @@ describe('parseQuery', () => {
27
28
  const queryParams = {
28
29
  $filter: "Title eq 'example'",
29
30
  $orderby: 'Title asc,$author/id desc',
31
+ resourceDefinition: {},
30
32
  };
31
33
  const expectedParsedQuery = {
32
34
  where: {
@@ -6,7 +6,7 @@ pages:
6
6
  - name: Example Page A
7
7
  blocks:
8
8
  - type: action-button
9
- version: 0.27.12
9
+ version: 0.28.0
10
10
  parameters:
11
11
  icon: arrow-right
12
12
  actions:
@@ -17,7 +17,7 @@ pages:
17
17
  - name: Example Page B
18
18
  blocks:
19
19
  - type: action-button
20
- version: 0.27.12
20
+ version: 0.28.0
21
21
  parameters:
22
22
  icon: arrow-left
23
23
  actions:
@@ -2,7 +2,7 @@
2
2
  "private": true,
3
3
  "type": "module",
4
4
  "dependencies": {
5
- "@appsemble/sdk": "0.27.12",
5
+ "@appsemble/sdk": "0.28.0",
6
6
  "mini-jsx": "^4.0.0"
7
7
  }
8
8
  }