@appsemble/cli 0.30.14-test.6 → 0.32.1-test.15

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.
Files changed (72) hide show
  1. package/README.md +3 -3
  2. package/commands/app/create.js +9 -4
  3. package/commands/app/delete.js +1 -1
  4. package/commands/app/export.js +1 -1
  5. package/commands/app/patch.d.ts +2 -0
  6. package/commands/app/patch.js +8 -0
  7. package/commands/app/update.js +1 -1
  8. package/commands/asset/publish.js +1 -1
  9. package/commands/cleanupSoftDeletedRecords.d.ts +6 -0
  10. package/commands/cleanupSoftDeletedRecords.js +40 -0
  11. package/commands/config/get.js +6 -2
  12. package/commands/config/set.js +6 -1
  13. package/commands/migrate.js +19 -0
  14. package/commands/serve.js +5 -1
  15. package/commands/start.js +19 -0
  16. package/commands/synchronizeTrainings.d.ts +6 -0
  17. package/commands/synchronizeTrainings.js +40 -0
  18. package/index.js +5 -0
  19. package/lib/app.d.ts +8 -0
  20. package/lib/app.js +59 -17
  21. package/lib/asset.d.ts +1 -1
  22. package/lib/authentication.js +4 -4
  23. package/lib/block.js +15 -6
  24. package/lib/coercers.js +1 -0
  25. package/lib/config.js +26 -18
  26. package/lib/createServers.js +3 -1
  27. package/lib/group.d.ts +1 -1
  28. package/lib/group.js +7 -6
  29. package/lib/output.js +3 -1
  30. package/lib/processCss.js +1 -1
  31. package/lib/project.js +3 -2
  32. package/lib/serverImport.d.ts +1 -1
  33. package/lib/serverImport.js +1 -1
  34. package/package.json +19 -16
  35. package/server/db/methods.js +3 -0
  36. package/server/options/createAppAsset.d.ts +1 -1
  37. package/server/options/createAppAsset.js +5 -6
  38. package/server/options/createAppResourcesWithAssets.js +4 -6
  39. package/server/options/createSettings.js +1 -1
  40. package/server/options/createTheme.js +2 -0
  41. package/server/options/getAppAsset.js +5 -10
  42. package/server/options/getAppAssets.js +5 -9
  43. package/server/options/getAppScreenshots.js +3 -3
  44. package/server/options/getBlockAsset.js +3 -0
  45. package/server/options/getTheme.js +1 -0
  46. package/server/options/options.js +2 -0
  47. package/templates/apps/empty/app-definition.yaml +2 -2
  48. package/templates/blocks/mini-jsx/package.json +4 -1
  49. package/templates/blocks/mini-jsx/src/index.tsx +1 -1
  50. package/templates/blocks/preact/package.json +5 -2
  51. package/templates/blocks/vanilla/package.json +4 -1
  52. package/vitest.setup.js +11 -2
  53. package/lib/app.test.d.ts +0 -1
  54. package/lib/app.test.js +0 -2212
  55. package/lib/asset.test.d.ts +0 -1
  56. package/lib/asset.test.js +0 -105
  57. package/lib/block.test.d.ts +0 -1
  58. package/lib/block.test.js +0 -235
  59. package/lib/coercers.test.d.ts +0 -1
  60. package/lib/coercers.test.js +0 -27
  61. package/lib/config.test.d.ts +0 -1
  62. package/lib/config.test.js +0 -239
  63. package/lib/group.test.d.ts +0 -1
  64. package/lib/group.test.js +0 -616
  65. package/lib/organization.test.d.ts +0 -1
  66. package/lib/organization.test.js +0 -222
  67. package/lib/resource.test.d.ts +0 -1
  68. package/lib/resource.test.js +0 -393
  69. package/server/db/methods.test.d.ts +0 -1
  70. package/server/db/methods.test.js +0 -366
  71. package/server/options/parseQuery.test.d.ts +0 -1
  72. package/server/options/parseQuery.test.js +0 -46
package/lib/app.test.js DELETED
@@ -1,2212 +0,0 @@
1
- import { writeFile } from 'node:fs/promises';
2
- import { readFixture, resolveFixture } from '@appsemble/node-utils';
3
- import { createServer, createTestUser, models, setArgv } from '@appsemble/server';
4
- import { PredefinedOrganizationRole } from '@appsemble/types';
5
- import { ISODateTimePattern } from '@appsemble/utils';
6
- import { setTestApp } from 'axios-test-instance';
7
- import FormData from 'form-data';
8
- import sharp from 'sharp';
9
- import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';
10
- import { deleteApp, patchApp, publishApp, resolveAppIdAndRemote, traverseAppDirectory, updateApp, writeAppMessages, } from './app.js';
11
- import { initAxios } from './initAxios.js';
12
- import { authorizeCLI } from './testUtils.js';
13
- const { App, AppBlockStyle, AppCollection, AppCollectionApp, AppMessages, AppOAuth2Secret, AppSamlSecret, AppScreenshot, AppServiceSecret, AppVariable, Asset, BlockVersion, Organization, OrganizationMember, Resource, } = models;
14
- const argv = { host: 'http://localhost', secret: 'test', aesSecret: 'testSecret' };
15
- let user;
16
- let organization;
17
- let testApp;
18
- describe('app', () => {
19
- beforeAll(() => {
20
- vi.useFakeTimers();
21
- setArgv(argv);
22
- });
23
- beforeEach(async () => {
24
- vi.clearAllTimers();
25
- vi.setSystemTime(0);
26
- const server = await createServer();
27
- testApp = await setTestApp(server);
28
- initAxios({ remote: testApp.defaults.baseURL });
29
- user = await createTestUser();
30
- organization = await Organization.create({
31
- id: 'testorganization',
32
- name: 'Test Organization',
33
- });
34
- await OrganizationMember.create({
35
- OrganizationId: organization.id,
36
- UserId: user.id,
37
- role: PredefinedOrganizationRole.Owner,
38
- });
39
- await Organization.create({ id: 'appsemble', name: 'Appsemble' });
40
- await BlockVersion.create({
41
- name: 'test',
42
- OrganizationId: 'appsemble',
43
- version: '0.0.0',
44
- parameters: {
45
- type: 'object',
46
- properties: {
47
- foo: {
48
- type: 'number',
49
- },
50
- },
51
- },
52
- });
53
- });
54
- afterAll(() => {
55
- vi.useRealTimers();
56
- });
57
- describe('publishApp', () => {
58
- it('should publish app', async () => {
59
- vi.useRealTimers();
60
- const clientCredentials = await authorizeCLI('apps:write resources:write', testApp);
61
- await publishApp({
62
- path: resolveFixture('apps/test'),
63
- organization: organization.id,
64
- remote: testApp.defaults.baseURL,
65
- clientCredentials,
66
- // Required defaults
67
- visibility: 'unlisted',
68
- iconBackground: '#ffffff',
69
- });
70
- vi.useFakeTimers();
71
- const app = await App.findOne();
72
- expect(app.toJSON()).toMatchInlineSnapshot({
73
- $created: expect.stringMatching(ISODateTimePattern),
74
- $updated: expect.stringMatching(ISODateTimePattern),
75
- iconUrl: expect.any(String),
76
- }, `
77
- {
78
- "$created": StringMatching /\\\\d\\{4\\}\\(\\.\\\\d\\{2\\}\\)\\{2\\}\\(\\\\s\\|T\\)\\(\\\\d\\{2\\}\\.\\)\\{2\\}\\\\d\\{2\\}/,
79
- "$updated": StringMatching /\\\\d\\{4\\}\\(\\.\\\\d\\{2\\}\\)\\{2\\}\\(\\\\s\\|T\\)\\(\\\\d\\{2\\}\\.\\)\\{2\\}\\\\d\\{2\\}/,
80
- "OrganizationId": "testorganization",
81
- "OrganizationName": undefined,
82
- "controllerCode": null,
83
- "controllerImplementations": null,
84
- "coreStyle": ".tux {
85
- color: rgb(0 0 0);
86
- }
87
- ",
88
- "definition": {
89
- "defaultPage": "Test Page",
90
- "name": "Test App",
91
- "pages": [
92
- {
93
- "blocks": [
94
- {
95
- "type": "test",
96
- "version": "0.0.0",
97
- },
98
- ],
99
- "name": "Test Page",
100
- },
101
- ],
102
- "resources": {
103
- "test": {
104
- "schema": {
105
- "additionalProperties": false,
106
- "properties": {
107
- "test": {
108
- "format": "binary",
109
- "type": "string",
110
- },
111
- },
112
- "type": "object",
113
- },
114
- },
115
- },
116
- },
117
- "demoMode": false,
118
- "domain": null,
119
- "emailName": null,
120
- "enableSelfRegistration": true,
121
- "enableUnsecuredServiceSecrets": false,
122
- "googleAnalyticsID": null,
123
- "hasClonableAssets": undefined,
124
- "hasClonableResources": undefined,
125
- "hasIcon": true,
126
- "hasMaskableIcon": true,
127
- "iconBackground": "#ffffff",
128
- "iconUrl": Any<String>,
129
- "id": 1,
130
- "locked": "unlocked",
131
- "messages": undefined,
132
- "path": "test-app",
133
- "rating": undefined,
134
- "readmeUrl": undefined,
135
- "screenshotUrls": undefined,
136
- "sentryDsn": null,
137
- "sentryEnvironment": null,
138
- "sharedStyle": ".tux {
139
- color: rgb(0 0 0);
140
- }
141
- ",
142
- "showAppDefinition": true,
143
- "showAppsembleLogin": false,
144
- "showAppsembleOAuth2Login": true,
145
- "template": false,
146
- "visibility": "unlisted",
147
- "yaml": "name: Test App
148
- defaultPage: Test Page
149
- resources:
150
- test:
151
- schema:
152
- additionalProperties: false
153
- type: object
154
- properties:
155
- test:
156
- type: string
157
- format: binary
158
- pages:
159
- - name: Test Page
160
- blocks:
161
- - type: test
162
- version: 0.0.0
163
- ",
164
- }
165
- `);
166
- expect(app.icon).toStrictEqual(await readFixture('apps/test/icon.png'));
167
- expect(app.maskableIcon).toStrictEqual(await readFixture('apps/test/maskable-icon.png'));
168
- const appBlockStyle = await AppBlockStyle.findOne();
169
- expect(appBlockStyle.style).toBe(`.tux {
170
- color: rgb(0 0 0);
171
- }`);
172
- const appScreenshot = await AppScreenshot.findOne();
173
- expect(appScreenshot.screenshot).toStrictEqual(await readFixture('apps/test/screenshots/test_en-us.png'));
174
- const appMessages = await AppMessages.findAll({ order: [['language', 'DESC']] });
175
- expect(appMessages.map(({ language, messages }) => [language, messages])).toStrictEqual([
176
- ['nl', JSON.parse(String(await readFixture('apps/test/i18n/nl.json')))],
177
- ['en', JSON.parse(String(await readFixture('apps/test/i18n/en.json')))],
178
- ]);
179
- const resource = await Resource.findAll();
180
- expect(resource).toStrictEqual([]);
181
- const asset = await Asset.findAll();
182
- expect(asset).toStrictEqual([]);
183
- });
184
- it('should throw an error if the user doesn’t have enough scope permissions', async () => {
185
- const clientCredentials = await authorizeCLI('', testApp);
186
- vi.useRealTimers();
187
- await expect(() => publishApp({
188
- path: resolveFixture('apps/test'),
189
- organization: organization.id,
190
- remote: testApp.defaults.baseURL,
191
- clientCredentials,
192
- // Required defaults
193
- visibility: 'unlisted',
194
- iconBackground: '#ffffff',
195
- })).rejects.toThrow('Request failed with status code 401');
196
- vi.useFakeTimers();
197
- const app = await App.findOne();
198
- expect(app).toBeNull();
199
- });
200
- it('should not publish if dryRun is specified', async () => {
201
- const clientCredentials = await authorizeCLI('apps:write resources:write', testApp);
202
- await publishApp({
203
- path: resolveFixture('apps/test'),
204
- organization: organization.id,
205
- remote: testApp.defaults.baseURL,
206
- clientCredentials,
207
- // Required defaults
208
- visibility: 'unlisted',
209
- iconBackground: '#ffffff',
210
- dryRun: true,
211
- });
212
- const apps = await App.findAll();
213
- expect(apps).toStrictEqual([]);
214
- });
215
- it('should publish app with resources and assets', async () => {
216
- vi.useRealTimers();
217
- const clientCredentials = await authorizeCLI('apps:write resources:write assets:write', testApp);
218
- await publishApp({
219
- path: resolveFixture('apps/test'),
220
- organization: organization.id,
221
- remote: testApp.defaults.baseURL,
222
- clientCredentials,
223
- // Required defaults
224
- visibility: 'unlisted',
225
- iconBackground: '#ffffff',
226
- // Additional
227
- resources: true,
228
- assets: true,
229
- });
230
- vi.useFakeTimers();
231
- const app = await App.findOne();
232
- expect(app.toJSON()).toMatchInlineSnapshot({
233
- $created: expect.stringMatching(ISODateTimePattern),
234
- $updated: expect.stringMatching(ISODateTimePattern),
235
- iconUrl: expect.any(String),
236
- }, `
237
- {
238
- "$created": StringMatching /\\\\d\\{4\\}\\(\\.\\\\d\\{2\\}\\)\\{2\\}\\(\\\\s\\|T\\)\\(\\\\d\\{2\\}\\.\\)\\{2\\}\\\\d\\{2\\}/,
239
- "$updated": StringMatching /\\\\d\\{4\\}\\(\\.\\\\d\\{2\\}\\)\\{2\\}\\(\\\\s\\|T\\)\\(\\\\d\\{2\\}\\.\\)\\{2\\}\\\\d\\{2\\}/,
240
- "OrganizationId": "testorganization",
241
- "OrganizationName": undefined,
242
- "controllerCode": null,
243
- "controllerImplementations": null,
244
- "coreStyle": ".tux {
245
- color: rgb(0 0 0);
246
- }
247
- ",
248
- "definition": {
249
- "defaultPage": "Test Page",
250
- "name": "Test App",
251
- "pages": [
252
- {
253
- "blocks": [
254
- {
255
- "type": "test",
256
- "version": "0.0.0",
257
- },
258
- ],
259
- "name": "Test Page",
260
- },
261
- ],
262
- "resources": {
263
- "test": {
264
- "schema": {
265
- "additionalProperties": false,
266
- "properties": {
267
- "test": {
268
- "format": "binary",
269
- "type": "string",
270
- },
271
- },
272
- "type": "object",
273
- },
274
- },
275
- },
276
- },
277
- "demoMode": false,
278
- "domain": null,
279
- "emailName": null,
280
- "enableSelfRegistration": true,
281
- "enableUnsecuredServiceSecrets": false,
282
- "googleAnalyticsID": null,
283
- "hasClonableAssets": undefined,
284
- "hasClonableResources": undefined,
285
- "hasIcon": true,
286
- "hasMaskableIcon": true,
287
- "iconBackground": "#ffffff",
288
- "iconUrl": Any<String>,
289
- "id": 1,
290
- "locked": "unlocked",
291
- "messages": undefined,
292
- "path": "test-app",
293
- "rating": undefined,
294
- "readmeUrl": undefined,
295
- "screenshotUrls": undefined,
296
- "sentryDsn": null,
297
- "sentryEnvironment": null,
298
- "sharedStyle": ".tux {
299
- color: rgb(0 0 0);
300
- }
301
- ",
302
- "showAppDefinition": true,
303
- "showAppsembleLogin": false,
304
- "showAppsembleOAuth2Login": true,
305
- "template": false,
306
- "visibility": "unlisted",
307
- "yaml": "name: Test App
308
- defaultPage: Test Page
309
- resources:
310
- test:
311
- schema:
312
- additionalProperties: false
313
- type: object
314
- properties:
315
- test:
316
- type: string
317
- format: binary
318
- pages:
319
- - name: Test Page
320
- blocks:
321
- - type: test
322
- version: 0.0.0
323
- ",
324
- }
325
- `);
326
- expect(app.icon).toStrictEqual(await readFixture('apps/test/icon.png'));
327
- expect(app.maskableIcon).toStrictEqual(await readFixture('apps/test/maskable-icon.png'));
328
- const appBlockStyle = await AppBlockStyle.findOne();
329
- expect(appBlockStyle.style).toBe(`.tux {
330
- color: rgb(0 0 0);
331
- }`);
332
- const appScreenshot = await AppScreenshot.findOne();
333
- expect(appScreenshot.screenshot).toStrictEqual(await readFixture('apps/test/screenshots/test_en-us.png'));
334
- const appMessages = await AppMessages.findAll({ order: [['language', 'DESC']] });
335
- expect(appMessages.map(({ language, messages }) => [language, messages])).toStrictEqual([
336
- ['nl', JSON.parse(String(await readFixture('apps/test/i18n/nl.json')))],
337
- ['en', JSON.parse(String(await readFixture('apps/test/i18n/en.json')))],
338
- ]);
339
- const resources = await Resource.findAll();
340
- expect(resources.map((r) => r.toJSON())).toMatchInlineSnapshot([
341
- {
342
- $created: expect.stringMatching(ISODateTimePattern),
343
- $updated: expect.stringMatching(ISODateTimePattern),
344
- },
345
- ], `
346
- [
347
- {
348
- "$created": StringMatching /\\\\d\\{4\\}\\(\\.\\\\d\\{2\\}\\)\\{2\\}\\(\\\\s\\|T\\)\\(\\\\d\\{2\\}\\.\\)\\{2\\}\\\\d\\{2\\}/,
349
- "$updated": StringMatching /\\\\d\\{4\\}\\(\\.\\\\d\\{2\\}\\)\\{2\\}\\(\\\\s\\|T\\)\\(\\\\d\\{2\\}\\.\\)\\{2\\}\\\\d\\{2\\}/,
350
- "id": 1,
351
- "test": "tux",
352
- },
353
- ]
354
- `);
355
- const assets = await Asset.findAll({ order: [['filename', 'ASC']] });
356
- const tuxData = await readFixture('apps/test/assets/tux.png');
357
- expect(assets.map((a) => a.data)).toStrictEqual([
358
- await sharp(tuxData).toFormat('avif').toBuffer(),
359
- tuxData,
360
- ]);
361
- });
362
- it('should publish app with runtime config', async () => {
363
- await AppCollection.create({
364
- name: 'test',
365
- expertName: 'Expert',
366
- expertProfileImage: Buffer.alloc(0),
367
- expertProfileImageMimeType: 'image/png',
368
- headerImage: Buffer.alloc(0),
369
- headerImageMimeType: 'image/png',
370
- OrganizationId: organization.id,
371
- visibility: 'public',
372
- });
373
- vi.useRealTimers();
374
- const clientCredentials = await authorizeCLI('apps:write resources:write assets:write', testApp);
375
- await publishApp({
376
- path: resolveFixture('apps/test'),
377
- organization: organization.id,
378
- remote: testApp.defaults.baseURL,
379
- clientCredentials,
380
- // Required defaults
381
- visibility: 'unlisted',
382
- iconBackground: '#ffffff',
383
- // Define context
384
- context: 'test',
385
- });
386
- vi.useFakeTimers();
387
- const app = await App.findOne();
388
- expect(app.toJSON()).toMatchInlineSnapshot({
389
- $created: expect.stringMatching(ISODateTimePattern),
390
- $updated: expect.stringMatching(ISODateTimePattern),
391
- iconUrl: expect.any(String),
392
- }, `
393
- {
394
- "$created": StringMatching /\\\\d\\{4\\}\\(\\.\\\\d\\{2\\}\\)\\{2\\}\\(\\\\s\\|T\\)\\(\\\\d\\{2\\}\\.\\)\\{2\\}\\\\d\\{2\\}/,
395
- "$updated": StringMatching /\\\\d\\{4\\}\\(\\.\\\\d\\{2\\}\\)\\{2\\}\\(\\\\s\\|T\\)\\(\\\\d\\{2\\}\\.\\)\\{2\\}\\\\d\\{2\\}/,
396
- "OrganizationId": "testorganization",
397
- "OrganizationName": undefined,
398
- "controllerCode": null,
399
- "controllerImplementations": null,
400
- "coreStyle": ".tux {
401
- color: rgb(0 0 0);
402
- }
403
- ",
404
- "definition": {
405
- "defaultPage": "Test Page",
406
- "name": "Test App",
407
- "pages": [
408
- {
409
- "blocks": [
410
- {
411
- "type": "test",
412
- "version": "0.0.0",
413
- },
414
- ],
415
- "name": "Test Page",
416
- },
417
- ],
418
- "resources": {
419
- "test": {
420
- "schema": {
421
- "additionalProperties": false,
422
- "properties": {
423
- "test": {
424
- "format": "binary",
425
- "type": "string",
426
- },
427
- },
428
- "type": "object",
429
- },
430
- },
431
- },
432
- },
433
- "demoMode": true,
434
- "domain": null,
435
- "emailName": null,
436
- "enableSelfRegistration": true,
437
- "enableUnsecuredServiceSecrets": false,
438
- "googleAnalyticsID": "test",
439
- "hasClonableAssets": undefined,
440
- "hasClonableResources": undefined,
441
- "hasIcon": true,
442
- "hasMaskableIcon": true,
443
- "iconBackground": "#000000",
444
- "iconUrl": Any<String>,
445
- "id": 1,
446
- "locked": "unlocked",
447
- "messages": undefined,
448
- "path": "test-app",
449
- "rating": undefined,
450
- "readmeUrl": undefined,
451
- "screenshotUrls": undefined,
452
- "sentryDsn": "https://public@sentry.example.com/1",
453
- "sentryEnvironment": "test",
454
- "sharedStyle": ".tux {
455
- color: rgb(0 0 0);
456
- }
457
- ",
458
- "showAppDefinition": true,
459
- "showAppsembleLogin": false,
460
- "showAppsembleOAuth2Login": true,
461
- "template": true,
462
- "visibility": "public",
463
- "yaml": "name: Test App
464
- defaultPage: Test Page
465
- resources:
466
- test:
467
- schema:
468
- additionalProperties: false
469
- type: object
470
- properties:
471
- test:
472
- type: string
473
- format: binary
474
- pages:
475
- - name: Test Page
476
- blocks:
477
- - type: test
478
- version: 0.0.0
479
- ",
480
- }
481
- `);
482
- expect(app.icon).toStrictEqual(await readFixture('apps/test/icon.png'));
483
- expect(app.maskableIcon).toStrictEqual(await readFixture('apps/test/maskable-icon.png'));
484
- const appBlockStyle = await AppBlockStyle.findOne();
485
- expect(appBlockStyle.style).toBe(`.tux {
486
- color: rgb(0 0 0);
487
- }`);
488
- const appScreenshot = await AppScreenshot.findOne();
489
- expect(appScreenshot.screenshot).toStrictEqual(await readFixture('apps/test/screenshots/test_en-us.png'));
490
- const appMessages = await AppMessages.findAll({ order: [['language', 'DESC']] });
491
- expect(appMessages.map(({ language, messages }) => [language, messages])).toStrictEqual([
492
- ['nl', JSON.parse(String(await readFixture('apps/test/i18n/nl.json')))],
493
- ['en', JSON.parse(String(await readFixture('apps/test/i18n/en.json')))],
494
- ]);
495
- const resources = await Resource.findAll();
496
- expect(resources.map((r) => r.toJSON())).toMatchInlineSnapshot([
497
- {
498
- $created: expect.stringMatching(ISODateTimePattern),
499
- $updated: expect.stringMatching(ISODateTimePattern),
500
- },
501
- {
502
- $created: expect.stringMatching(ISODateTimePattern),
503
- $updated: expect.stringMatching(ISODateTimePattern),
504
- },
505
- ], `
506
- [
507
- {
508
- "$created": StringMatching /\\\\d\\{4\\}\\(\\.\\\\d\\{2\\}\\)\\{2\\}\\(\\\\s\\|T\\)\\(\\\\d\\{2\\}\\.\\)\\{2\\}\\\\d\\{2\\}/,
509
- "$updated": StringMatching /\\\\d\\{4\\}\\(\\.\\\\d\\{2\\}\\)\\{2\\}\\(\\\\s\\|T\\)\\(\\\\d\\{2\\}\\.\\)\\{2\\}\\\\d\\{2\\}/,
510
- "id": 1,
511
- "test": "tux",
512
- },
513
- {
514
- "$created": StringMatching /\\\\d\\{4\\}\\(\\.\\\\d\\{2\\}\\)\\{2\\}\\(\\\\s\\|T\\)\\(\\\\d\\{2\\}\\.\\)\\{2\\}\\\\d\\{2\\}/,
515
- "$ephemeral": true,
516
- "$updated": StringMatching /\\\\d\\{4\\}\\(\\.\\\\d\\{2\\}\\)\\{2\\}\\(\\\\s\\|T\\)\\(\\\\d\\{2\\}\\.\\)\\{2\\}\\\\d\\{2\\}/,
517
- "id": 2,
518
- "test": "tux",
519
- },
520
- ]
521
- `);
522
- const assets = await Asset.findAll({ order: [['filename', 'ASC']] });
523
- const tuxData = await readFixture('apps/test/assets/tux.png');
524
- const tuxAvifData = await sharp(tuxData).toFormat('avif').toBuffer();
525
- expect(assets.map((a) => a.data)).toStrictEqual([tuxAvifData, tuxAvifData, tuxData, tuxData]);
526
- const appCollectionApp = await AppCollectionApp.findOne();
527
- expect(appCollectionApp.AppId).toBe(1);
528
- expect(appCollectionApp.AppCollectionId).toBe(1);
529
- });
530
- it('should publish app with app variant patches applied', async () => {
531
- vi.useRealTimers();
532
- const clientCredentials = await authorizeCLI('apps:write resources:write assets:write', testApp);
533
- await publishApp({
534
- path: resolveFixture('apps/test'),
535
- organization: organization.id,
536
- remote: testApp.defaults.baseURL,
537
- clientCredentials,
538
- // Required defaults
539
- visibility: 'unlisted',
540
- iconBackground: '#ffffff',
541
- // Define app variant
542
- variant: 'tux',
543
- resources: true,
544
- assets: true,
545
- });
546
- vi.useFakeTimers();
547
- const app = await App.findOne();
548
- expect(app.toJSON()).toMatchInlineSnapshot({
549
- $created: expect.stringMatching(ISODateTimePattern),
550
- $updated: expect.stringMatching(ISODateTimePattern),
551
- iconUrl: expect.any(String),
552
- }, `
553
- {
554
- "$created": StringMatching /\\\\d\\{4\\}\\(\\.\\\\d\\{2\\}\\)\\{2\\}\\(\\\\s\\|T\\)\\(\\\\d\\{2\\}\\.\\)\\{2\\}\\\\d\\{2\\}/,
555
- "$updated": StringMatching /\\\\d\\{4\\}\\(\\.\\\\d\\{2\\}\\)\\{2\\}\\(\\\\s\\|T\\)\\(\\\\d\\{2\\}\\.\\)\\{2\\}\\\\d\\{2\\}/,
556
- "OrganizationId": "testorganization",
557
- "OrganizationName": undefined,
558
- "controllerCode": null,
559
- "controllerImplementations": null,
560
- "coreStyle": ".tux{color:rgb(1 2 3)}",
561
- "definition": {
562
- "defaultPage": "Test Page",
563
- "name": "Test App",
564
- "pages": [
565
- {
566
- "blocks": [
567
- {
568
- "type": "test",
569
- "version": "0.0.0",
570
- },
571
- ],
572
- "name": "Test Page",
573
- },
574
- ],
575
- "resources": {
576
- "test": {
577
- "schema": {
578
- "additionalProperties": false,
579
- "properties": {
580
- "tux": {
581
- "format": "binary",
582
- "type": "string",
583
- },
584
- },
585
- "type": "object",
586
- },
587
- },
588
- },
589
- },
590
- "demoMode": false,
591
- "domain": null,
592
- "emailName": null,
593
- "enableSelfRegistration": true,
594
- "enableUnsecuredServiceSecrets": false,
595
- "googleAnalyticsID": null,
596
- "hasClonableAssets": undefined,
597
- "hasClonableResources": undefined,
598
- "hasIcon": true,
599
- "hasMaskableIcon": true,
600
- "iconBackground": "#ffffff",
601
- "iconUrl": Any<String>,
602
- "id": 1,
603
- "locked": "unlocked",
604
- "messages": undefined,
605
- "path": "test-app",
606
- "rating": undefined,
607
- "readmeUrl": undefined,
608
- "screenshotUrls": undefined,
609
- "sentryDsn": null,
610
- "sentryEnvironment": null,
611
- "sharedStyle": ".tux{color:rgb(1 2 3)}",
612
- "showAppDefinition": true,
613
- "showAppsembleLogin": false,
614
- "showAppsembleOAuth2Login": true,
615
- "template": false,
616
- "visibility": "unlisted",
617
- "yaml": "name: Test App
618
- defaultPage: Test Page
619
- resources:
620
- test:
621
- schema:
622
- additionalProperties: false
623
- type: object
624
- properties:
625
- tux:
626
- type: string
627
- format: binary
628
- pages:
629
- - name: Test Page
630
- blocks:
631
- - type: test
632
- version: 0.0.0
633
- ",
634
- }
635
- `);
636
- expect(app.icon).toStrictEqual(await readFixture('apps/test/variants/tux/icon.png'));
637
- expect(app.maskableIcon).toStrictEqual(await readFixture('apps/test/variants/tux/maskable-icon.png'));
638
- const appBlockStyle = await AppBlockStyle.findOne();
639
- expect(appBlockStyle.style).toBe('.tux{color:rgb(1 2 3)}');
640
- const appScreenshot = await AppScreenshot.findOne();
641
- expect(appScreenshot.screenshot).toStrictEqual(await readFixture('apps/test/variants/tux/screenshots/tux.png'));
642
- const appMessages = await AppMessages.findAll({ order: [['language', 'DESC']] });
643
- const messages = JSON.parse(String(await readFixture('apps/test/variants/tux/patches/messages.json')));
644
- expect(appMessages.map(({ language, messages: msgs }) => [language, msgs])).toStrictEqual([
645
- ['nl', messages.nl],
646
- ['en', messages.en],
647
- ]);
648
- const resources = await Resource.findAll();
649
- expect(resources.map((r) => r.toJSON())).toMatchInlineSnapshot([
650
- {
651
- $created: expect.stringMatching(ISODateTimePattern),
652
- $updated: expect.stringMatching(ISODateTimePattern),
653
- },
654
- ], `
655
- [
656
- {
657
- "$created": StringMatching /\\\\d\\{4\\}\\(\\.\\\\d\\{2\\}\\)\\{2\\}\\(\\\\s\\|T\\)\\(\\\\d\\{2\\}\\.\\)\\{2\\}\\\\d\\{2\\}/,
658
- "$updated": StringMatching /\\\\d\\{4\\}\\(\\.\\\\d\\{2\\}\\)\\{2\\}\\(\\\\s\\|T\\)\\(\\\\d\\{2\\}\\.\\)\\{2\\}\\\\d\\{2\\}/,
659
- "id": 1,
660
- "tux": "small-tux",
661
- },
662
- ]
663
- `);
664
- const assets = await Asset.findAll({ order: [['filename', 'ASC']] });
665
- const tuxData = await readFixture('apps/test/variants/tux/assets/small-tux.png');
666
- expect(assets.map((a) => a.data)).toStrictEqual([
667
- await sharp(tuxData).toFormat('avif').toBuffer(),
668
- tuxData,
669
- ]);
670
- });
671
- it('should publish app variables and secrets', async () => {
672
- vi.useRealTimers();
673
- const clientCredentials = await authorizeCLI('apps:write resources:write', testApp);
674
- process.env.TEST_VARIABLE_1 = 'test-variable-1';
675
- process.env.TEST_VARIABLE_2 = 'test-variable-2';
676
- process.env.TEST_SECRET = 'test-secret';
677
- process.env.TEST_SERVICE_URL_PATTERNS = 'http://localhost:1234';
678
- process.env.TEST_SERVICE_IDENTIFIER = 'Authorization';
679
- process.env.TEST_SERVICE_SECRET = 'secret';
680
- process.env.TEST_SAML_IDP_CERTIFICATE = 'certificate';
681
- process.env.TEST_SAML_ENTITY_ID = 'http://localhost:1234';
682
- process.env.TEST_SAML_SSO_URL = 'http://localhost:1234';
683
- process.env.TEST_OAUTH2_AUTHORIZATION_URL = 'http://localhost:1234';
684
- process.env.TEST_OAUTH2_TOKEN_URL = 'http://localhost:1234';
685
- process.env.TEST_OAUTH2_USER_INFO_URL = 'http://localhost:1234';
686
- process.env.TEST_OAUTH2_CLIENT_ID = 'id';
687
- process.env.TEST_OAUTH2_CLIENT_SECRET = 'secret';
688
- process.env.TEST_SCIM_TOKEN = 'token';
689
- process.env.TEST_SSL_KEY = 'key';
690
- process.env.TEST_SSL_CERTIFICATE = 'certificate';
691
- await publishApp({
692
- path: resolveFixture('apps/test'),
693
- organization: organization.id,
694
- remote: testApp.defaults.baseURL,
695
- clientCredentials,
696
- visibility: 'unlisted',
697
- iconBackground: '#ffffff',
698
- });
699
- vi.useFakeTimers();
700
- const app = await App.findOne({
701
- attributes: ['scimEnabled', 'scimToken', 'sslKey', 'sslCertificate'],
702
- where: {
703
- id: 1,
704
- },
705
- });
706
- expect({
707
- scimEnabled: app.scimEnabled,
708
- scimToken: app.scimToken,
709
- sslKey: app.sslKey,
710
- sslCertificate: app.sslCertificate,
711
- }).toStrictEqual({
712
- scimEnabled: true,
713
- scimToken: expect.any(Buffer),
714
- sslKey: 'key',
715
- sslCertificate: 'certificate',
716
- });
717
- const appVariables = await AppVariable.findAll({
718
- attributes: ['name', 'value'],
719
- where: {
720
- AppId: 1,
721
- },
722
- });
723
- expect(appVariables[0].toJSON()).toStrictEqual({
724
- name: 'test-variable-inline',
725
- value: 'inline',
726
- });
727
- expect(appVariables[1].toJSON()).toStrictEqual({
728
- name: 'test-variable',
729
- value: 'test-variable-1.test-variable-2',
730
- });
731
- const appServiceSecret = await AppServiceSecret.findOne({
732
- attributes: ['name', 'authenticationMethod', 'urlPatterns', 'identifier', 'secret'],
733
- where: {
734
- AppId: 1,
735
- },
736
- });
737
- expect(appServiceSecret.toJSON()).toStrictEqual({
738
- name: 'test-service-secret',
739
- authenticationMethod: 'custom-header',
740
- urlPatterns: 'http://localhost:1234',
741
- identifier: 'Authorization',
742
- secret: expect.any(Buffer),
743
- });
744
- const appSamlSecret = await AppSamlSecret.findOne({
745
- attributes: [
746
- 'name',
747
- 'icon',
748
- 'nameAttribute',
749
- 'emailAttribute',
750
- 'idpCertificate',
751
- 'entityId',
752
- 'ssoUrl',
753
- ],
754
- where: {
755
- AppId: 1,
756
- },
757
- });
758
- expect(appSamlSecret.toJSON()).toStrictEqual({
759
- emailAttribute: 'email',
760
- emailVerifiedAttribute: null,
761
- entityId: 'http://localhost:1234',
762
- icon: 'redhat',
763
- id: 1,
764
- idpCertificate: 'certificate',
765
- name: 'test-saml-secret',
766
- nameAttribute: 'name',
767
- ssoUrl: 'http://localhost:1234',
768
- spCertificate: expect.any(String),
769
- });
770
- const appOauth2Secret = await AppOAuth2Secret.findOne({
771
- attributes: [
772
- 'name',
773
- 'icon',
774
- 'scope',
775
- 'remapper',
776
- 'authorizationUrl',
777
- 'tokenUrl',
778
- 'userInfoUrl',
779
- 'clientId',
780
- 'clientSecret',
781
- ],
782
- where: {
783
- AppId: 1,
784
- },
785
- });
786
- expect({
787
- authorizationUrl: appOauth2Secret.authorizationUrl,
788
- clientId: appOauth2Secret.clientId,
789
- clientSecret: appOauth2Secret.clientSecret,
790
- icon: appOauth2Secret.icon,
791
- name: appOauth2Secret.name,
792
- remapper: appOauth2Secret.remapper,
793
- scope: appOauth2Secret.scope,
794
- tokenUrl: appOauth2Secret.tokenUrl,
795
- userInfoUrl: appOauth2Secret.userInfoUrl,
796
- }).toStrictEqual({
797
- authorizationUrl: 'http://localhost:1234',
798
- clientId: 'id',
799
- clientSecret: 'secret',
800
- icon: 'redhat',
801
- name: 'test-oauth2-secret',
802
- remapper: [{ prop: 'email' }],
803
- scope: 'email openid profile',
804
- tokenUrl: 'http://localhost:1234',
805
- userInfoUrl: 'http://localhost:1234',
806
- });
807
- });
808
- it('should validate and throw if there’s an error before publishing an app', async () => {
809
- await BlockVersion.destroy({
810
- where: {
811
- version: '0.0.0',
812
- OrganizationId: 'appsemble',
813
- name: 'test',
814
- },
815
- });
816
- vi.useRealTimers();
817
- const clientCredentials = await authorizeCLI('apps:write resources:write', testApp);
818
- await expect(() => publishApp({
819
- path: resolveFixture('apps/test'),
820
- organization: organization.id,
821
- remote: testApp.defaults.baseURL,
822
- clientCredentials,
823
- // Required defaults
824
- visibility: 'unlisted',
825
- iconBackground: '#ffffff',
826
- })).rejects.toThrow('is not a known block type');
827
- const app = await App.findOne();
828
- expect(app).toBeNull();
829
- });
830
- });
831
- describe('updateApp', () => {
832
- let app;
833
- beforeEach(async () => {
834
- app = await App.create({
835
- path: 'test-app',
836
- definition: { name: 'Test App', defaultPage: 'Test Page' },
837
- vapidPublicKey: 'a',
838
- vapidPrivateKey: 'b',
839
- visibility: 'public',
840
- OrganizationId: organization.id,
841
- });
842
- });
843
- it('should update app', async () => {
844
- vi.useRealTimers();
845
- const clientCredentials = await authorizeCLI('apps:write resources:write', testApp);
846
- await updateApp({
847
- id: app.id,
848
- path: resolveFixture('apps/test'),
849
- remote: testApp.defaults.baseURL,
850
- clientCredentials,
851
- // Required defaults
852
- visibility: 'unlisted',
853
- iconBackground: '#ffffff',
854
- force: false,
855
- });
856
- vi.useFakeTimers();
857
- await app.reload();
858
- expect(app.toJSON()).toMatchInlineSnapshot({
859
- $created: expect.stringMatching(ISODateTimePattern),
860
- $updated: expect.stringMatching(ISODateTimePattern),
861
- iconUrl: expect.any(String),
862
- }, `
863
- {
864
- "$created": StringMatching /\\\\d\\{4\\}\\(\\.\\\\d\\{2\\}\\)\\{2\\}\\(\\\\s\\|T\\)\\(\\\\d\\{2\\}\\.\\)\\{2\\}\\\\d\\{2\\}/,
865
- "$updated": StringMatching /\\\\d\\{4\\}\\(\\.\\\\d\\{2\\}\\)\\{2\\}\\(\\\\s\\|T\\)\\(\\\\d\\{2\\}\\.\\)\\{2\\}\\\\d\\{2\\}/,
866
- "OrganizationId": "testorganization",
867
- "OrganizationName": undefined,
868
- "controllerCode": null,
869
- "controllerImplementations": null,
870
- "coreStyle": ".tux {
871
- color: rgb(0 0 0);
872
- }
873
- ",
874
- "definition": {
875
- "defaultPage": "Test Page",
876
- "name": "Test App",
877
- "pages": [
878
- {
879
- "blocks": [
880
- {
881
- "type": "test",
882
- "version": "0.0.0",
883
- },
884
- ],
885
- "name": "Test Page",
886
- },
887
- ],
888
- "resources": {
889
- "test": {
890
- "schema": {
891
- "additionalProperties": false,
892
- "properties": {
893
- "test": {
894
- "format": "binary",
895
- "type": "string",
896
- },
897
- },
898
- "type": "object",
899
- },
900
- },
901
- },
902
- },
903
- "demoMode": false,
904
- "domain": null,
905
- "emailName": null,
906
- "enableSelfRegistration": true,
907
- "enableUnsecuredServiceSecrets": false,
908
- "googleAnalyticsID": null,
909
- "hasClonableAssets": undefined,
910
- "hasClonableResources": undefined,
911
- "hasIcon": true,
912
- "hasMaskableIcon": true,
913
- "iconBackground": "#ffffff",
914
- "iconUrl": Any<String>,
915
- "id": 1,
916
- "locked": "unlocked",
917
- "messages": undefined,
918
- "path": "test-app",
919
- "rating": undefined,
920
- "readmeUrl": undefined,
921
- "screenshotUrls": undefined,
922
- "sentryDsn": null,
923
- "sentryEnvironment": null,
924
- "sharedStyle": ".tux {
925
- color: rgb(0 0 0);
926
- }
927
- ",
928
- "showAppDefinition": false,
929
- "showAppsembleLogin": false,
930
- "showAppsembleOAuth2Login": true,
931
- "template": false,
932
- "visibility": "unlisted",
933
- "yaml": "name: Test App
934
- defaultPage: Test Page
935
- resources:
936
- test:
937
- schema:
938
- additionalProperties: false
939
- type: object
940
- properties:
941
- test:
942
- type: string
943
- format: binary
944
- pages:
945
- - name: Test Page
946
- blocks:
947
- - type: test
948
- version: 0.0.0
949
- ",
950
- }
951
- `);
952
- expect(app.icon).toStrictEqual(await readFixture('apps/test/icon.png'));
953
- expect(app.maskableIcon).toStrictEqual(await readFixture('apps/test/maskable-icon.png'));
954
- const appBlockStyle = await AppBlockStyle.findOne();
955
- expect(appBlockStyle.style).toBe(`.tux {
956
- color: rgb(0 0 0);
957
- }`);
958
- const appScreenshot = await AppScreenshot.findOne();
959
- expect(appScreenshot.screenshot).toStrictEqual(await readFixture('apps/test/screenshots/test_en-us.png'));
960
- const appMessages = await AppMessages.findAll({ order: [['language', 'DESC']] });
961
- expect(appMessages.map(({ language, messages }) => [language, messages])).toStrictEqual([
962
- ['nl', JSON.parse(String(await readFixture('apps/test/i18n/nl.json')))],
963
- ['en', JSON.parse(String(await readFixture('apps/test/i18n/en.json')))],
964
- ]);
965
- const resource = await Resource.findAll();
966
- expect(resource).toStrictEqual([]);
967
- const asset = await Asset.findAll();
968
- expect(asset).toStrictEqual([]);
969
- });
970
- it('should throw an error if the user doesn’t have enough scope permissions', async () => {
971
- const clientCredentials = await authorizeCLI('', testApp);
972
- vi.useRealTimers();
973
- await expect(() => updateApp({
974
- id: app.id,
975
- force: false,
976
- path: resolveFixture('apps/test'),
977
- remote: testApp.defaults.baseURL,
978
- clientCredentials,
979
- // Required defaults
980
- visibility: 'unlisted',
981
- iconBackground: '#ffffff',
982
- })).rejects.toThrow('Request failed with status code 401');
983
- vi.useFakeTimers();
984
- await app.reload();
985
- expect(app).toMatchObject({
986
- path: 'test-app',
987
- definition: { name: 'Test App', defaultPage: 'Test Page' },
988
- vapidPublicKey: 'a',
989
- vapidPrivateKey: 'b',
990
- visibility: 'public',
991
- OrganizationId: organization.id,
992
- });
993
- });
994
- it('should update app with resources and assets', async () => {
995
- vi.useRealTimers();
996
- const clientCredentials = await authorizeCLI('apps:write resources:write assets:write', testApp);
997
- await updateApp({
998
- id: app.id,
999
- path: resolveFixture('apps/test'),
1000
- remote: testApp.defaults.baseURL,
1001
- clientCredentials,
1002
- // Required defaults
1003
- visibility: 'unlisted',
1004
- iconBackground: '#ffffff',
1005
- force: false,
1006
- // Additional
1007
- resources: true,
1008
- assets: true,
1009
- });
1010
- vi.useFakeTimers();
1011
- await app.reload();
1012
- expect(app.toJSON()).toMatchInlineSnapshot({
1013
- $created: expect.stringMatching(ISODateTimePattern),
1014
- $updated: expect.stringMatching(ISODateTimePattern),
1015
- iconUrl: expect.any(String),
1016
- }, `
1017
- {
1018
- "$created": StringMatching /\\\\d\\{4\\}\\(\\.\\\\d\\{2\\}\\)\\{2\\}\\(\\\\s\\|T\\)\\(\\\\d\\{2\\}\\.\\)\\{2\\}\\\\d\\{2\\}/,
1019
- "$updated": StringMatching /\\\\d\\{4\\}\\(\\.\\\\d\\{2\\}\\)\\{2\\}\\(\\\\s\\|T\\)\\(\\\\d\\{2\\}\\.\\)\\{2\\}\\\\d\\{2\\}/,
1020
- "OrganizationId": "testorganization",
1021
- "OrganizationName": undefined,
1022
- "controllerCode": null,
1023
- "controllerImplementations": null,
1024
- "coreStyle": ".tux {
1025
- color: rgb(0 0 0);
1026
- }
1027
- ",
1028
- "definition": {
1029
- "defaultPage": "Test Page",
1030
- "name": "Test App",
1031
- "pages": [
1032
- {
1033
- "blocks": [
1034
- {
1035
- "type": "test",
1036
- "version": "0.0.0",
1037
- },
1038
- ],
1039
- "name": "Test Page",
1040
- },
1041
- ],
1042
- "resources": {
1043
- "test": {
1044
- "schema": {
1045
- "additionalProperties": false,
1046
- "properties": {
1047
- "test": {
1048
- "format": "binary",
1049
- "type": "string",
1050
- },
1051
- },
1052
- "type": "object",
1053
- },
1054
- },
1055
- },
1056
- },
1057
- "demoMode": false,
1058
- "domain": null,
1059
- "emailName": null,
1060
- "enableSelfRegistration": true,
1061
- "enableUnsecuredServiceSecrets": false,
1062
- "googleAnalyticsID": null,
1063
- "hasClonableAssets": undefined,
1064
- "hasClonableResources": undefined,
1065
- "hasIcon": true,
1066
- "hasMaskableIcon": true,
1067
- "iconBackground": "#ffffff",
1068
- "iconUrl": Any<String>,
1069
- "id": 1,
1070
- "locked": "unlocked",
1071
- "messages": undefined,
1072
- "path": "test-app",
1073
- "rating": undefined,
1074
- "readmeUrl": undefined,
1075
- "screenshotUrls": undefined,
1076
- "sentryDsn": null,
1077
- "sentryEnvironment": null,
1078
- "sharedStyle": ".tux {
1079
- color: rgb(0 0 0);
1080
- }
1081
- ",
1082
- "showAppDefinition": false,
1083
- "showAppsembleLogin": false,
1084
- "showAppsembleOAuth2Login": true,
1085
- "template": false,
1086
- "visibility": "unlisted",
1087
- "yaml": "name: Test App
1088
- defaultPage: Test Page
1089
- resources:
1090
- test:
1091
- schema:
1092
- additionalProperties: false
1093
- type: object
1094
- properties:
1095
- test:
1096
- type: string
1097
- format: binary
1098
- pages:
1099
- - name: Test Page
1100
- blocks:
1101
- - type: test
1102
- version: 0.0.0
1103
- ",
1104
- }
1105
- `);
1106
- expect(app.icon).toStrictEqual(await readFixture('apps/test/icon.png'));
1107
- expect(app.maskableIcon).toStrictEqual(await readFixture('apps/test/maskable-icon.png'));
1108
- const appBlockStyle = await AppBlockStyle.findOne();
1109
- expect(appBlockStyle.style).toBe(`.tux {
1110
- color: rgb(0 0 0);
1111
- }`);
1112
- const appScreenshot = await AppScreenshot.findOne();
1113
- expect(appScreenshot.screenshot).toStrictEqual(await readFixture('apps/test/screenshots/test_en-us.png'));
1114
- const appMessages = await AppMessages.findAll({ order: [['language', 'DESC']] });
1115
- expect(appMessages.map(({ language, messages }) => [language, messages])).toStrictEqual([
1116
- ['nl', JSON.parse(String(await readFixture('apps/test/i18n/nl.json')))],
1117
- ['en', JSON.parse(String(await readFixture('apps/test/i18n/en.json')))],
1118
- ]);
1119
- const resources = await Resource.findAll();
1120
- expect(resources.map((r) => r.toJSON())).toMatchInlineSnapshot([
1121
- {
1122
- $created: expect.stringMatching(ISODateTimePattern),
1123
- $updated: expect.stringMatching(ISODateTimePattern),
1124
- },
1125
- ], `
1126
- [
1127
- {
1128
- "$created": StringMatching /\\\\d\\{4\\}\\(\\.\\\\d\\{2\\}\\)\\{2\\}\\(\\\\s\\|T\\)\\(\\\\d\\{2\\}\\.\\)\\{2\\}\\\\d\\{2\\}/,
1129
- "$updated": StringMatching /\\\\d\\{4\\}\\(\\.\\\\d\\{2\\}\\)\\{2\\}\\(\\\\s\\|T\\)\\(\\\\d\\{2\\}\\.\\)\\{2\\}\\\\d\\{2\\}/,
1130
- "id": 1,
1131
- "test": "tux",
1132
- },
1133
- ]
1134
- `);
1135
- const assets = await Asset.findAll({ order: [['filename', 'ASC']] });
1136
- const tuxData = await readFixture('apps/test/assets/tux.png');
1137
- expect(assets.map((a) => a.data)).toStrictEqual([
1138
- await sharp(tuxData).toFormat('avif').toBuffer(),
1139
- tuxData,
1140
- ]);
1141
- });
1142
- it('should update app with runtime config', async () => {
1143
- await AppCollection.create({
1144
- name: 'test',
1145
- expertName: 'Expert',
1146
- expertProfileImage: Buffer.alloc(0),
1147
- expertProfileImageMimeType: 'image/png',
1148
- headerImage: Buffer.alloc(0),
1149
- headerImageMimeType: 'image/png',
1150
- OrganizationId: organization.id,
1151
- visibility: 'public',
1152
- });
1153
- vi.useRealTimers();
1154
- const clientCredentials = await authorizeCLI('apps:write resources:write assets:write', testApp);
1155
- await updateApp({
1156
- path: resolveFixture('apps/test'),
1157
- id: app.id,
1158
- remote: testApp.defaults.baseURL,
1159
- clientCredentials,
1160
- // Required defaults
1161
- visibility: 'unlisted',
1162
- iconBackground: '#ffffff',
1163
- force: false,
1164
- // Define context
1165
- context: 'test',
1166
- });
1167
- vi.useFakeTimers();
1168
- await app.reload();
1169
- expect(app.toJSON()).toMatchInlineSnapshot({
1170
- $created: expect.stringMatching(ISODateTimePattern),
1171
- $updated: expect.stringMatching(ISODateTimePattern),
1172
- iconUrl: expect.any(String),
1173
- }, `
1174
- {
1175
- "$created": StringMatching /\\\\d\\{4\\}\\(\\.\\\\d\\{2\\}\\)\\{2\\}\\(\\\\s\\|T\\)\\(\\\\d\\{2\\}\\.\\)\\{2\\}\\\\d\\{2\\}/,
1176
- "$updated": StringMatching /\\\\d\\{4\\}\\(\\.\\\\d\\{2\\}\\)\\{2\\}\\(\\\\s\\|T\\)\\(\\\\d\\{2\\}\\.\\)\\{2\\}\\\\d\\{2\\}/,
1177
- "OrganizationId": "testorganization",
1178
- "OrganizationName": undefined,
1179
- "controllerCode": null,
1180
- "controllerImplementations": null,
1181
- "coreStyle": ".tux {
1182
- color: rgb(0 0 0);
1183
- }
1184
- ",
1185
- "definition": {
1186
- "defaultPage": "Test Page",
1187
- "name": "Test App",
1188
- "pages": [
1189
- {
1190
- "blocks": [
1191
- {
1192
- "type": "test",
1193
- "version": "0.0.0",
1194
- },
1195
- ],
1196
- "name": "Test Page",
1197
- },
1198
- ],
1199
- "resources": {
1200
- "test": {
1201
- "schema": {
1202
- "additionalProperties": false,
1203
- "properties": {
1204
- "test": {
1205
- "format": "binary",
1206
- "type": "string",
1207
- },
1208
- },
1209
- "type": "object",
1210
- },
1211
- },
1212
- },
1213
- },
1214
- "demoMode": true,
1215
- "domain": null,
1216
- "emailName": null,
1217
- "enableSelfRegistration": true,
1218
- "enableUnsecuredServiceSecrets": false,
1219
- "googleAnalyticsID": "test",
1220
- "hasClonableAssets": undefined,
1221
- "hasClonableResources": undefined,
1222
- "hasIcon": true,
1223
- "hasMaskableIcon": true,
1224
- "iconBackground": "#000000",
1225
- "iconUrl": Any<String>,
1226
- "id": 1,
1227
- "locked": "studioLock",
1228
- "messages": undefined,
1229
- "path": "test-app",
1230
- "rating": undefined,
1231
- "readmeUrl": undefined,
1232
- "screenshotUrls": undefined,
1233
- "sentryDsn": "https://public@sentry.example.com/1",
1234
- "sentryEnvironment": "test",
1235
- "sharedStyle": ".tux {
1236
- color: rgb(0 0 0);
1237
- }
1238
- ",
1239
- "showAppDefinition": false,
1240
- "showAppsembleLogin": false,
1241
- "showAppsembleOAuth2Login": true,
1242
- "template": true,
1243
- "visibility": "public",
1244
- "yaml": "name: Test App
1245
- defaultPage: Test Page
1246
- resources:
1247
- test:
1248
- schema:
1249
- additionalProperties: false
1250
- type: object
1251
- properties:
1252
- test:
1253
- type: string
1254
- format: binary
1255
- pages:
1256
- - name: Test Page
1257
- blocks:
1258
- - type: test
1259
- version: 0.0.0
1260
- ",
1261
- }
1262
- `);
1263
- expect(app.icon).toStrictEqual(await readFixture('apps/test/icon.png'));
1264
- expect(app.maskableIcon).toStrictEqual(await readFixture('apps/test/maskable-icon.png'));
1265
- const appBlockStyle = await AppBlockStyle.findOne();
1266
- expect(appBlockStyle.style).toBe(`.tux {
1267
- color: rgb(0 0 0);
1268
- }`);
1269
- const appScreenshot = await AppScreenshot.findOne();
1270
- expect(appScreenshot.screenshot).toStrictEqual(await readFixture('apps/test/screenshots/test_en-us.png'));
1271
- const appMessages = await AppMessages.findAll({ order: [['language', 'DESC']] });
1272
- expect(appMessages.map(({ language, messages }) => [language, messages])).toStrictEqual([
1273
- ['nl', JSON.parse(String(await readFixture('apps/test/i18n/nl.json')))],
1274
- ['en', JSON.parse(String(await readFixture('apps/test/i18n/en.json')))],
1275
- ]);
1276
- const resources = await Resource.findAll();
1277
- expect(resources.map((r) => r.toJSON())).toMatchInlineSnapshot([
1278
- {
1279
- $created: expect.stringMatching(ISODateTimePattern),
1280
- $updated: expect.stringMatching(ISODateTimePattern),
1281
- },
1282
- {
1283
- $created: expect.stringMatching(ISODateTimePattern),
1284
- $updated: expect.stringMatching(ISODateTimePattern),
1285
- },
1286
- ], `
1287
- [
1288
- {
1289
- "$created": StringMatching /\\\\d\\{4\\}\\(\\.\\\\d\\{2\\}\\)\\{2\\}\\(\\\\s\\|T\\)\\(\\\\d\\{2\\}\\.\\)\\{2\\}\\\\d\\{2\\}/,
1290
- "$updated": StringMatching /\\\\d\\{4\\}\\(\\.\\\\d\\{2\\}\\)\\{2\\}\\(\\\\s\\|T\\)\\(\\\\d\\{2\\}\\.\\)\\{2\\}\\\\d\\{2\\}/,
1291
- "id": 1,
1292
- "test": "tux",
1293
- },
1294
- {
1295
- "$created": StringMatching /\\\\d\\{4\\}\\(\\.\\\\d\\{2\\}\\)\\{2\\}\\(\\\\s\\|T\\)\\(\\\\d\\{2\\}\\.\\)\\{2\\}\\\\d\\{2\\}/,
1296
- "$ephemeral": true,
1297
- "$updated": StringMatching /\\\\d\\{4\\}\\(\\.\\\\d\\{2\\}\\)\\{2\\}\\(\\\\s\\|T\\)\\(\\\\d\\{2\\}\\.\\)\\{2\\}\\\\d\\{2\\}/,
1298
- "id": 2,
1299
- "test": "tux",
1300
- },
1301
- ]
1302
- `);
1303
- const assets = await Asset.findAll({ order: [['filename', 'ASC']] });
1304
- const tuxData = await readFixture('apps/test/assets/tux.png');
1305
- const tuxAvifData = await sharp(tuxData).toFormat('avif').toBuffer();
1306
- expect(assets.map((a) => a.data)).toStrictEqual([tuxAvifData, tuxAvifData, tuxData, tuxData]);
1307
- // TODO: not yet implemented
1308
- // const appCollectionApp = await AppCollectionApp.findOne();
1309
- // expect(appCollectionApp.AppId).toBe(1);
1310
- // expect(appCollectionApp.AppCollectionId).toBe(1);
1311
- });
1312
- it('should update app with app variant patches applied', async () => {
1313
- vi.useRealTimers();
1314
- const clientCredentials = await authorizeCLI('apps:write resources:write assets:write', testApp);
1315
- await updateApp({
1316
- path: resolveFixture('apps/test'),
1317
- id: app.id,
1318
- remote: testApp.defaults.baseURL,
1319
- clientCredentials,
1320
- // Required defaults
1321
- visibility: 'unlisted',
1322
- iconBackground: '#ffffff',
1323
- force: false,
1324
- // Define app variant
1325
- variant: 'tux',
1326
- resources: true,
1327
- assets: true,
1328
- });
1329
- vi.useFakeTimers();
1330
- await app.reload();
1331
- expect(app.toJSON()).toMatchInlineSnapshot({
1332
- $created: expect.stringMatching(ISODateTimePattern),
1333
- $updated: expect.stringMatching(ISODateTimePattern),
1334
- iconUrl: expect.any(String),
1335
- }, `
1336
- {
1337
- "$created": StringMatching /\\\\d\\{4\\}\\(\\.\\\\d\\{2\\}\\)\\{2\\}\\(\\\\s\\|T\\)\\(\\\\d\\{2\\}\\.\\)\\{2\\}\\\\d\\{2\\}/,
1338
- "$updated": StringMatching /\\\\d\\{4\\}\\(\\.\\\\d\\{2\\}\\)\\{2\\}\\(\\\\s\\|T\\)\\(\\\\d\\{2\\}\\.\\)\\{2\\}\\\\d\\{2\\}/,
1339
- "OrganizationId": "testorganization",
1340
- "OrganizationName": undefined,
1341
- "controllerCode": null,
1342
- "controllerImplementations": null,
1343
- "coreStyle": ".tux{color:rgb(1 2 3)}",
1344
- "definition": {
1345
- "defaultPage": "Test Page",
1346
- "name": "Test App",
1347
- "pages": [
1348
- {
1349
- "blocks": [
1350
- {
1351
- "type": "test",
1352
- "version": "0.0.0",
1353
- },
1354
- ],
1355
- "name": "Test Page",
1356
- },
1357
- ],
1358
- "resources": {
1359
- "test": {
1360
- "schema": {
1361
- "additionalProperties": false,
1362
- "properties": {
1363
- "tux": {
1364
- "format": "binary",
1365
- "type": "string",
1366
- },
1367
- },
1368
- "type": "object",
1369
- },
1370
- },
1371
- },
1372
- },
1373
- "demoMode": false,
1374
- "domain": null,
1375
- "emailName": null,
1376
- "enableSelfRegistration": true,
1377
- "enableUnsecuredServiceSecrets": false,
1378
- "googleAnalyticsID": null,
1379
- "hasClonableAssets": undefined,
1380
- "hasClonableResources": undefined,
1381
- "hasIcon": true,
1382
- "hasMaskableIcon": true,
1383
- "iconBackground": "#ffffff",
1384
- "iconUrl": Any<String>,
1385
- "id": 1,
1386
- "locked": "unlocked",
1387
- "messages": undefined,
1388
- "path": "test-app",
1389
- "rating": undefined,
1390
- "readmeUrl": undefined,
1391
- "screenshotUrls": undefined,
1392
- "sentryDsn": null,
1393
- "sentryEnvironment": null,
1394
- "sharedStyle": ".tux{color:rgb(1 2 3)}",
1395
- "showAppDefinition": false,
1396
- "showAppsembleLogin": false,
1397
- "showAppsembleOAuth2Login": true,
1398
- "template": false,
1399
- "visibility": "unlisted",
1400
- "yaml": "name: Test App
1401
- defaultPage: Test Page
1402
- resources:
1403
- test:
1404
- schema:
1405
- additionalProperties: false
1406
- type: object
1407
- properties:
1408
- tux:
1409
- type: string
1410
- format: binary
1411
- pages:
1412
- - name: Test Page
1413
- blocks:
1414
- - type: test
1415
- version: 0.0.0
1416
- ",
1417
- }
1418
- `);
1419
- expect(app.icon).toStrictEqual(await readFixture('apps/test/variants/tux/icon.png'));
1420
- expect(app.maskableIcon).toStrictEqual(await readFixture('apps/test/variants/tux/maskable-icon.png'));
1421
- const appBlockStyle = await AppBlockStyle.findOne();
1422
- expect(appBlockStyle.style).toBe('.tux{color:rgb(1 2 3)}');
1423
- const appScreenshot = await AppScreenshot.findOne();
1424
- expect(appScreenshot.screenshot).toStrictEqual(await readFixture('apps/test/variants/tux/screenshots/tux.png'));
1425
- const appMessages = await AppMessages.findAll({ order: [['language', 'DESC']] });
1426
- const messages = JSON.parse(String(await readFixture('apps/test/variants/tux/patches/messages.json')));
1427
- expect(appMessages.map(({ language, messages: msgs }) => [language, msgs])).toStrictEqual([
1428
- ['nl', messages.nl],
1429
- ['en', messages.en],
1430
- ]);
1431
- const resources = await Resource.findAll();
1432
- expect(resources.map((r) => r.toJSON())).toMatchInlineSnapshot([
1433
- {
1434
- $created: expect.stringMatching(ISODateTimePattern),
1435
- $updated: expect.stringMatching(ISODateTimePattern),
1436
- },
1437
- ], `
1438
- [
1439
- {
1440
- "$created": StringMatching /\\\\d\\{4\\}\\(\\.\\\\d\\{2\\}\\)\\{2\\}\\(\\\\s\\|T\\)\\(\\\\d\\{2\\}\\.\\)\\{2\\}\\\\d\\{2\\}/,
1441
- "$updated": StringMatching /\\\\d\\{4\\}\\(\\.\\\\d\\{2\\}\\)\\{2\\}\\(\\\\s\\|T\\)\\(\\\\d\\{2\\}\\.\\)\\{2\\}\\\\d\\{2\\}/,
1442
- "id": 1,
1443
- "tux": "small-tux",
1444
- },
1445
- ]
1446
- `);
1447
- const assets = await Asset.findAll();
1448
- expect(assets.map((a) => a.filename)).toStrictEqual(['small-tux.png', 'small-tux.avif']);
1449
- });
1450
- it('should update app variables and secrets', async () => {
1451
- vi.useRealTimers();
1452
- const clientCredentials = await authorizeCLI('apps:write resources:write', testApp);
1453
- process.env.TEST_VARIABLE_1 = 'test-variable-1';
1454
- process.env.TEST_VARIABLE_2 = 'test-variable-2';
1455
- process.env.TEST_SECRET = 'test-secret';
1456
- process.env.TEST_SERVICE_URL_PATTERNS = 'http://localhost:1234';
1457
- process.env.TEST_SERVICE_IDENTIFIER = 'Authorization';
1458
- process.env.TEST_SERVICE_SECRET = 'secret';
1459
- process.env.TEST_SAML_IDP_CERTIFICATE = 'certificate';
1460
- process.env.TEST_SAML_ENTITY_ID = 'http://localhost:1234';
1461
- process.env.TEST_SAML_SSO_URL = 'http://localhost:1234';
1462
- process.env.TEST_OAUTH2_AUTHORIZATION_URL = 'http://localhost:1234';
1463
- process.env.TEST_OAUTH2_TOKEN_URL = 'http://localhost:1234';
1464
- process.env.TEST_OAUTH2_USER_INFO_URL = 'http://localhost:1234';
1465
- process.env.TEST_OAUTH2_CLIENT_ID = 'id';
1466
- process.env.TEST_OAUTH2_CLIENT_SECRET = 'secret';
1467
- process.env.TEST_SCIM_TOKEN = 'token';
1468
- process.env.TEST_SSL_KEY = 'key';
1469
- process.env.TEST_SSL_CERTIFICATE = 'certificate';
1470
- await updateApp({
1471
- id: 1,
1472
- path: resolveFixture('apps/test'),
1473
- remote: testApp.defaults.baseURL,
1474
- clientCredentials,
1475
- visibility: 'unlisted',
1476
- iconBackground: '#ffffff',
1477
- force: true,
1478
- });
1479
- vi.useFakeTimers();
1480
- const updatedApp = await App.findOne({
1481
- attributes: ['scimEnabled', 'scimToken', 'sslKey', 'sslCertificate'],
1482
- where: {
1483
- id: 1,
1484
- },
1485
- });
1486
- expect({
1487
- scimEnabled: updatedApp.scimEnabled,
1488
- scimToken: updatedApp.scimToken,
1489
- sslKey: updatedApp.sslKey,
1490
- sslCertificate: updatedApp.sslCertificate,
1491
- }).toStrictEqual({
1492
- scimEnabled: true,
1493
- scimToken: expect.any(Buffer),
1494
- sslKey: 'key',
1495
- sslCertificate: 'certificate',
1496
- });
1497
- const appVariables = await AppVariable.findAll({
1498
- attributes: ['name', 'value'],
1499
- where: {
1500
- AppId: 1,
1501
- },
1502
- });
1503
- expect(appVariables[0].toJSON()).toStrictEqual({
1504
- name: 'test-variable-inline',
1505
- value: 'inline',
1506
- });
1507
- expect(appVariables[1].toJSON()).toStrictEqual({
1508
- name: 'test-variable',
1509
- value: 'test-variable-1.test-variable-2',
1510
- });
1511
- const appServiceSecret = await AppServiceSecret.findOne({
1512
- attributes: ['name', 'authenticationMethod', 'urlPatterns', 'identifier', 'secret'],
1513
- where: {
1514
- AppId: 1,
1515
- },
1516
- });
1517
- expect(appServiceSecret.toJSON()).toStrictEqual({
1518
- name: 'test-service-secret',
1519
- authenticationMethod: 'custom-header',
1520
- urlPatterns: 'http://localhost:1234',
1521
- identifier: 'Authorization',
1522
- secret: expect.any(Buffer),
1523
- });
1524
- const appSamlSecret = await AppSamlSecret.findOne({
1525
- attributes: [
1526
- 'name',
1527
- 'icon',
1528
- 'nameAttribute',
1529
- 'emailAttribute',
1530
- 'idpCertificate',
1531
- 'entityId',
1532
- 'ssoUrl',
1533
- ],
1534
- where: {
1535
- AppId: 1,
1536
- },
1537
- });
1538
- expect(appSamlSecret.toJSON()).toStrictEqual({
1539
- emailAttribute: 'email',
1540
- emailVerifiedAttribute: null,
1541
- entityId: 'http://localhost:1234',
1542
- icon: 'redhat',
1543
- id: 1,
1544
- idpCertificate: 'certificate',
1545
- name: 'test-saml-secret',
1546
- nameAttribute: 'name',
1547
- ssoUrl: 'http://localhost:1234',
1548
- spCertificate: expect.any(String),
1549
- });
1550
- const appOauth2Secret = await AppOAuth2Secret.findOne({
1551
- attributes: [
1552
- 'name',
1553
- 'icon',
1554
- 'scope',
1555
- 'remapper',
1556
- 'authorizationUrl',
1557
- 'tokenUrl',
1558
- 'userInfoUrl',
1559
- 'clientId',
1560
- 'clientSecret',
1561
- ],
1562
- where: {
1563
- AppId: 1,
1564
- },
1565
- });
1566
- expect({
1567
- authorizationUrl: appOauth2Secret.authorizationUrl,
1568
- clientId: appOauth2Secret.clientId,
1569
- clientSecret: appOauth2Secret.clientSecret,
1570
- icon: appOauth2Secret.icon,
1571
- name: appOauth2Secret.name,
1572
- remapper: appOauth2Secret.remapper,
1573
- scope: appOauth2Secret.scope,
1574
- tokenUrl: appOauth2Secret.tokenUrl,
1575
- userInfoUrl: appOauth2Secret.userInfoUrl,
1576
- }).toStrictEqual({
1577
- authorizationUrl: 'http://localhost:1234',
1578
- clientId: 'id',
1579
- clientSecret: 'secret',
1580
- icon: 'redhat',
1581
- name: 'test-oauth2-secret',
1582
- remapper: [{ prop: 'email' }],
1583
- scope: 'email openid profile',
1584
- tokenUrl: 'http://localhost:1234',
1585
- userInfoUrl: 'http://localhost:1234',
1586
- });
1587
- });
1588
- it('should update an app with `app.locked` set to `fullLock` if force is specified', async () => {
1589
- await app.update({ locked: 'fullLock' });
1590
- const clientCredentials = await authorizeCLI('apps:write', testApp);
1591
- await updateApp({
1592
- path: resolveFixture('apps/test'),
1593
- force: true,
1594
- remote: testApp.defaults.baseURL,
1595
- id: app.id,
1596
- clientCredentials,
1597
- visibility: 'public',
1598
- iconBackground: '#fff999',
1599
- });
1600
- await app.reload();
1601
- expect(app).toMatchInlineSnapshot(`
1602
- {
1603
- "$created": "1970-01-01T00:00:00.000Z",
1604
- "$updated": "1970-01-01T00:00:00.000Z",
1605
- "OrganizationId": "testorganization",
1606
- "OrganizationName": undefined,
1607
- "controllerCode": null,
1608
- "controllerImplementations": null,
1609
- "coreStyle": ".tux {
1610
- color: rgb(0 0 0);
1611
- }
1612
- ",
1613
- "definition": {
1614
- "defaultPage": "Test Page",
1615
- "name": "Test App",
1616
- "pages": [
1617
- {
1618
- "blocks": [
1619
- {
1620
- "type": "test",
1621
- "version": "0.0.0",
1622
- },
1623
- ],
1624
- "name": "Test Page",
1625
- },
1626
- ],
1627
- "resources": {
1628
- "test": {
1629
- "schema": {
1630
- "additionalProperties": false,
1631
- "properties": {
1632
- "test": {
1633
- "format": "binary",
1634
- "type": "string",
1635
- },
1636
- },
1637
- "type": "object",
1638
- },
1639
- },
1640
- },
1641
- },
1642
- "demoMode": false,
1643
- "domain": null,
1644
- "emailName": null,
1645
- "enableSelfRegistration": true,
1646
- "enableUnsecuredServiceSecrets": false,
1647
- "googleAnalyticsID": null,
1648
- "hasClonableAssets": undefined,
1649
- "hasClonableResources": undefined,
1650
- "hasIcon": true,
1651
- "hasMaskableIcon": true,
1652
- "iconBackground": "#fff999",
1653
- "iconUrl": "/api/apps/1/icon?maskable=true&updated=1970-01-01T00%3A00%3A00.000Z",
1654
- "id": 1,
1655
- "locked": "fullLock",
1656
- "messages": undefined,
1657
- "path": "test-app",
1658
- "rating": undefined,
1659
- "readmeUrl": undefined,
1660
- "screenshotUrls": undefined,
1661
- "sentryDsn": null,
1662
- "sentryEnvironment": null,
1663
- "sharedStyle": ".tux {
1664
- color: rgb(0 0 0);
1665
- }
1666
- ",
1667
- "showAppDefinition": false,
1668
- "showAppsembleLogin": false,
1669
- "showAppsembleOAuth2Login": true,
1670
- "template": false,
1671
- "visibility": "public",
1672
- "yaml": "name: Test App
1673
- defaultPage: Test Page
1674
- resources:
1675
- test:
1676
- schema:
1677
- additionalProperties: false
1678
- type: object
1679
- properties:
1680
- test:
1681
- type: string
1682
- format: binary
1683
- pages:
1684
- - name: Test Page
1685
- blocks:
1686
- - type: test
1687
- version: 0.0.0
1688
- ",
1689
- }
1690
- `);
1691
- });
1692
- it('should not update an app if the `app.locked` is set to `fullLock`', async () => {
1693
- await app.update({ locked: 'fullLock' });
1694
- const clientCredentials = await authorizeCLI('apps:write', testApp);
1695
- await expect(updateApp({
1696
- path: resolveFixture('apps/test'),
1697
- force: false,
1698
- remote: testApp.defaults.baseURL,
1699
- id: app.id,
1700
- clientCredentials,
1701
- visibility: 'public',
1702
- iconBackground: '#fff999',
1703
- })).rejects.toThrow('Request failed with status code 403');
1704
- await app.reload();
1705
- expect(app).toMatchInlineSnapshot(`
1706
- {
1707
- "$created": "1970-01-01T00:00:00.000Z",
1708
- "$updated": "1970-01-01T00:00:00.000Z",
1709
- "OrganizationId": "testorganization",
1710
- "OrganizationName": undefined,
1711
- "controllerCode": null,
1712
- "controllerImplementations": null,
1713
- "coreStyle": undefined,
1714
- "definition": {
1715
- "defaultPage": "Test Page",
1716
- "name": "Test App",
1717
- },
1718
- "demoMode": false,
1719
- "domain": null,
1720
- "emailName": null,
1721
- "enableSelfRegistration": true,
1722
- "enableUnsecuredServiceSecrets": false,
1723
- "googleAnalyticsID": null,
1724
- "hasClonableAssets": undefined,
1725
- "hasClonableResources": undefined,
1726
- "hasIcon": false,
1727
- "hasMaskableIcon": false,
1728
- "iconBackground": "#ffffff",
1729
- "iconUrl": null,
1730
- "id": 1,
1731
- "locked": "fullLock",
1732
- "messages": undefined,
1733
- "path": "test-app",
1734
- "rating": undefined,
1735
- "readmeUrl": undefined,
1736
- "screenshotUrls": undefined,
1737
- "sentryDsn": null,
1738
- "sentryEnvironment": null,
1739
- "sharedStyle": undefined,
1740
- "showAppDefinition": false,
1741
- "showAppsembleLogin": false,
1742
- "showAppsembleOAuth2Login": true,
1743
- "template": false,
1744
- "visibility": "public",
1745
- "yaml": "name: Test App
1746
- defaultPage: Test Page
1747
- ",
1748
- }
1749
- `);
1750
- });
1751
- it('should validate and throw if there’s an error before updating an app', async () => {
1752
- await BlockVersion.destroy({
1753
- where: {
1754
- version: '0.0.0',
1755
- OrganizationId: 'appsemble',
1756
- name: 'test',
1757
- },
1758
- });
1759
- vi.useRealTimers();
1760
- const clientCredentials = await authorizeCLI('apps:write resources:write', testApp);
1761
- await expect(updateApp({
1762
- path: resolveFixture('apps/test'),
1763
- id: app.id,
1764
- force: false,
1765
- remote: testApp.defaults.baseURL,
1766
- clientCredentials,
1767
- // Required defaults
1768
- visibility: 'unlisted',
1769
- iconBackground: '#ffffff',
1770
- })).rejects.toThrow('is not a known block type');
1771
- vi.useFakeTimers();
1772
- await app.reload();
1773
- expect(app.dataValues).toMatchInlineSnapshot(`
1774
- {
1775
- "OrganizationId": "testorganization",
1776
- "containers": null,
1777
- "controllerCode": null,
1778
- "controllerImplementations": null,
1779
- "coreStyle": null,
1780
- "created": 1970-01-01T00:00:00.000Z,
1781
- "definition": {
1782
- "defaultPage": "Test Page",
1783
- "name": "Test App",
1784
- },
1785
- "deleted": null,
1786
- "demoMode": false,
1787
- "domain": null,
1788
- "emailHost": null,
1789
- "emailName": null,
1790
- "emailPassword": null,
1791
- "emailPort": 587,
1792
- "emailSecure": true,
1793
- "emailUser": null,
1794
- "enableSelfRegistration": true,
1795
- "enableUnsecuredServiceSecrets": false,
1796
- "googleAnalyticsID": null,
1797
- "icon": null,
1798
- "iconBackground": null,
1799
- "id": 1,
1800
- "locked": "unlocked",
1801
- "maskableIcon": null,
1802
- "path": "test-app",
1803
- "registry": null,
1804
- "scimEnabled": false,
1805
- "scimToken": null,
1806
- "sentryDsn": null,
1807
- "sentryEnvironment": null,
1808
- "sharedStyle": null,
1809
- "showAppDefinition": false,
1810
- "showAppsembleLogin": false,
1811
- "showAppsembleOAuth2Login": true,
1812
- "sslCertificate": null,
1813
- "sslKey": null,
1814
- "template": false,
1815
- "updated": 1970-01-01T00:00:00.000Z,
1816
- "vapidPrivateKey": "b",
1817
- "vapidPublicKey": "a",
1818
- "visibility": "public",
1819
- }
1820
- `);
1821
- });
1822
- });
1823
- describe('patchApp', () => {
1824
- it('should apply patches to app', async () => {
1825
- const patches = {
1826
- demoMode: true,
1827
- enableSelfRegistration: true,
1828
- iconBackground: '#FFFFFF',
1829
- locked: 'fullLock',
1830
- path: 'updated-path',
1831
- showAppDefinition: true,
1832
- showAppsembleLogin: true,
1833
- showAppsembleOAuth2Login: true,
1834
- template: true,
1835
- visibility: 'private',
1836
- };
1837
- const app = await App.create({
1838
- path: 'test-app',
1839
- definition: { name: 'Test App', defaultPage: 'Test Page' },
1840
- vapidPublicKey: 'a',
1841
- vapidPrivateKey: 'b',
1842
- visibility: 'public',
1843
- OrganizationId: organization.id,
1844
- }, { raw: true });
1845
- await authorizeCLI('apps:write', testApp);
1846
- await patchApp({
1847
- ...patches,
1848
- remote: testApp.defaults.baseURL,
1849
- id: app.id,
1850
- });
1851
- await app.reload();
1852
- expect(app.dataValues).toStrictEqual(expect.objectContaining({ ...app.dataValues, ...patches }));
1853
- expect(app.dataValues).toMatchInlineSnapshot(`
1854
- {
1855
- "OrganizationId": "testorganization",
1856
- "containers": null,
1857
- "controllerCode": null,
1858
- "controllerImplementations": null,
1859
- "coreStyle": null,
1860
- "created": 1970-01-01T00:00:00.000Z,
1861
- "definition": {
1862
- "defaultPage": "Test Page",
1863
- "name": "Test App",
1864
- },
1865
- "deleted": null,
1866
- "demoMode": true,
1867
- "domain": null,
1868
- "emailHost": null,
1869
- "emailName": null,
1870
- "emailPassword": null,
1871
- "emailPort": 587,
1872
- "emailSecure": true,
1873
- "emailUser": null,
1874
- "enableSelfRegistration": true,
1875
- "enableUnsecuredServiceSecrets": false,
1876
- "googleAnalyticsID": null,
1877
- "icon": null,
1878
- "iconBackground": "#FFFFFF",
1879
- "id": 1,
1880
- "locked": "fullLock",
1881
- "maskableIcon": null,
1882
- "path": "updated-path",
1883
- "registry": null,
1884
- "scimEnabled": false,
1885
- "scimToken": null,
1886
- "sentryDsn": null,
1887
- "sentryEnvironment": null,
1888
- "sharedStyle": null,
1889
- "showAppDefinition": true,
1890
- "showAppsembleLogin": true,
1891
- "showAppsembleOAuth2Login": true,
1892
- "sslCertificate": null,
1893
- "sslKey": null,
1894
- "template": true,
1895
- "updated": 1970-01-01T00:00:00.000Z,
1896
- "vapidPrivateKey": "b",
1897
- "vapidPublicKey": "a",
1898
- "visibility": "private",
1899
- }
1900
- `);
1901
- });
1902
- it.each `
1903
- key | from | to
1904
- ${'demoMode'} | ${false} | ${true}
1905
- ${'demoMode'} | ${true} | ${false}
1906
- ${'enableSelfRegistration'} | ${false} | ${true}
1907
- ${'enableSelfRegistration'} | ${true} | ${false}
1908
- ${'iconBackground'} | ${''} | ${'#FFFFFF'}
1909
- ${'path'} | ${'before'} | ${'after'}
1910
- ${'locked'} | ${'unlocked'} | ${'studioLock'}
1911
- ${'locked'} | ${'unlocked'} | ${'fullLock'}
1912
- ${'locked'} | ${'fullLock'} | ${'unlocked'}
1913
- ${'showAppDefinition'} | ${false} | ${true}
1914
- ${'showAppDefinition'} | ${true} | ${false}
1915
- ${'showAppsembleLogin'} | ${false} | ${true}
1916
- ${'showAppsembleLogin'} | ${true} | ${false}
1917
- ${'showAppsembleOAuth2Login'} | ${false} | ${true}
1918
- ${'showAppsembleOAuth2Login'} | ${true} | ${false}
1919
- ${'template'} | ${false} | ${true}
1920
- ${'template'} | ${true} | ${false}
1921
- ${'visibility'} | ${'public'} | ${'unlisted'}
1922
- ${'visibility'} | ${'public'} | ${'private'}
1923
- ${'visibility'} | ${'private'} | ${'public'}
1924
- `('should patch $key from `$from` to `$to`', async ({ from, key, to }) => {
1925
- const app = await App.create({
1926
- path: 'test-app',
1927
- definition: { name: 'Test App', defaultPage: 'Test Page' },
1928
- vapidPublicKey: 'a',
1929
- vapidPrivateKey: 'b',
1930
- visibility: 'public',
1931
- OrganizationId: organization.id,
1932
- [key]: from,
1933
- }, { raw: true });
1934
- await authorizeCLI('apps:write', testApp);
1935
- await patchApp({
1936
- [key]: to,
1937
- remote: testApp.defaults.baseURL,
1938
- id: app.id,
1939
- });
1940
- expect(app.dataValues[key]).toStrictEqual(from);
1941
- await app.reload();
1942
- expect(app.dataValues[key]).toStrictEqual(to);
1943
- });
1944
- it('should not apply a patch if the `app.locked` is set to `fullLock`', async () => {
1945
- const app = await App.create({
1946
- path: 'test-app',
1947
- definition: { name: 'Test App', defaultPage: 'Test Page' },
1948
- vapidPublicKey: 'a',
1949
- vapidPrivateKey: 'b',
1950
- visibility: 'public',
1951
- OrganizationId: organization.id,
1952
- locked: 'fullLock',
1953
- showAppDefinition: false,
1954
- }, { raw: true });
1955
- await authorizeCLI('apps:write', testApp);
1956
- await patchApp({
1957
- remote: testApp.defaults.baseURL,
1958
- id: app.id,
1959
- showAppDefinition: true,
1960
- });
1961
- expect(app.dataValues.showAppDefinition).toBe(false);
1962
- await app.reload();
1963
- expect(app.dataValues.showAppDefinition).toBe(false);
1964
- });
1965
- it('should apply a patch if the `app.locked` is set to `fullLock` and `force` is specified', async () => {
1966
- const app = await App.create({
1967
- path: 'test-app',
1968
- definition: { name: 'Test App', defaultPage: 'Test Page' },
1969
- vapidPublicKey: 'a',
1970
- vapidPrivateKey: 'b',
1971
- visibility: 'public',
1972
- OrganizationId: organization.id,
1973
- locked: 'fullLock',
1974
- showAppDefinition: false,
1975
- }, { raw: true });
1976
- await authorizeCLI('apps:write', testApp);
1977
- await patchApp({
1978
- remote: testApp.defaults.baseURL,
1979
- id: app.id,
1980
- showAppDefinition: true,
1981
- force: true,
1982
- });
1983
- expect(app.dataValues.showAppDefinition).toBe(false);
1984
- await app.reload();
1985
- expect(app.dataValues.showAppDefinition).toBe(true);
1986
- });
1987
- });
1988
- describe('traverseAppDirectory', () => {
1989
- it('should read the app definition and appsembleRC from the directory', async () => {
1990
- const formData = new FormData();
1991
- const [appsembleContext, appsembleRC, yaml, app] = await traverseAppDirectory(resolveFixture('apps/test'), 'test', formData);
1992
- expect(appsembleContext).toMatchInlineSnapshot({
1993
- icon: expect.any(String),
1994
- maskableIcon: expect.any(String),
1995
- }, `
1996
- {
1997
- "appLock": "studioLock",
1998
- "assets": true,
1999
- "assetsClonable": true,
2000
- "collections": [
2001
- 1,
2002
- ],
2003
- "demoMode": true,
2004
- "googleAnalyticsId": "test",
2005
- "icon": Any<String>,
2006
- "iconBackground": "#000000",
2007
- "id": 1,
2008
- "maskableIcon": Any<String>,
2009
- "organization": "testorganization",
2010
- "resources": true,
2011
- "sentryDsn": "https://public@sentry.example.com/1",
2012
- "sentryEnvironment": "test",
2013
- "showAppDefinition": true,
2014
- "template": true,
2015
- "visibility": "public",
2016
- }
2017
- `);
2018
- expect(appsembleRC).toMatchInlineSnapshot({
2019
- context: {
2020
- test: {
2021
- icon: expect.any(String),
2022
- maskableIcon: expect.any(String),
2023
- },
2024
- },
2025
- }, `
2026
- {
2027
- "context": {
2028
- "notFound": {
2029
- "organization": "testorganization",
2030
- },
2031
- "resolve": {
2032
- "id": 5,
2033
- "remote": "http://localhost:5555",
2034
- },
2035
- "test": {
2036
- "appLock": "studioLock",
2037
- "assets": true,
2038
- "assetsClonable": true,
2039
- "collections": [
2040
- 1,
2041
- ],
2042
- "demoMode": true,
2043
- "googleAnalyticsId": "test",
2044
- "icon": Any<String>,
2045
- "iconBackground": "#000000",
2046
- "id": 1,
2047
- "maskableIcon": Any<String>,
2048
- "organization": "testorganization",
2049
- "resources": true,
2050
- "sentryDsn": "https://public@sentry.example.com/1",
2051
- "sentryEnvironment": "test",
2052
- "showAppDefinition": true,
2053
- "template": true,
2054
- "visibility": "public",
2055
- },
2056
- },
2057
- }
2058
- `);
2059
- expect(yaml).toMatchInlineSnapshot(`
2060
- "name: Test App
2061
- defaultPage: Test Page
2062
-
2063
- resources:
2064
- test:
2065
- schema:
2066
- additionalProperties: false
2067
- type: object
2068
- properties:
2069
- test:
2070
- type: string
2071
- format: binary
2072
-
2073
- pages:
2074
- - name: Test Page
2075
- blocks:
2076
- - type: test
2077
- version: 0.0.0
2078
- "
2079
- `);
2080
- expect(app).toMatchInlineSnapshot({
2081
- iconUrl: expect.any(String),
2082
- }, `
2083
- {
2084
- "coreStyle": ".tux {
2085
- color: rgb(0 0 0);
2086
- }
2087
- ",
2088
- "definition": {
2089
- "defaultPage": "Test Page",
2090
- "name": "Test App",
2091
- "pages": [
2092
- {
2093
- "blocks": [
2094
- {
2095
- "type": "test",
2096
- "version": "0.0.0",
2097
- },
2098
- ],
2099
- "name": "Test Page",
2100
- },
2101
- ],
2102
- "resources": {
2103
- "test": {
2104
- "schema": {
2105
- "additionalProperties": false,
2106
- "properties": {
2107
- "test": {
2108
- "format": "binary",
2109
- "type": "string",
2110
- },
2111
- },
2112
- "type": "object",
2113
- },
2114
- },
2115
- },
2116
- },
2117
- "iconUrl": Any<String>,
2118
- "readmeUrl": "README.md",
2119
- "screenshotUrls": [
2120
- "test_en-us.png",
2121
- ],
2122
- "sharedStyle": ".tux {
2123
- color: rgb(0 0 0);
2124
- }
2125
- ",
2126
- }
2127
- `);
2128
- });
2129
- it('should return an error if no app-definition is found in the given directory', async () => {
2130
- const formData = new FormData();
2131
- await expect(() => traverseAppDirectory(resolveFixture('apps/empty'), null, formData)).rejects.toThrow('No app definition found');
2132
- });
2133
- });
2134
- describe('resolveAppIdAndRemote', () => {
2135
- it('should find and return app id and remote from appsembleRC', async () => {
2136
- const [appId, remote] = await resolveAppIdAndRemote(resolveFixture('apps/test'), 'resolve', 'http://localhost:8888', 55);
2137
- expect(appId).toBe(5);
2138
- expect(remote).toBe('http://localhost:5555/');
2139
- });
2140
- it('should fallback to default appId and remote if not found in context', async () => {
2141
- const [appId, remote] = await resolveAppIdAndRemote(resolveFixture('apps/test'), 'notFound', 'http://localhost:8888', 55);
2142
- expect(appId).toBe(55);
2143
- expect(remote).toBe('http://localhost:8888/');
2144
- });
2145
- it('should throw if the appId is not found in context and defaultAppId is not specified', async () => {
2146
- await expect(() => resolveAppIdAndRemote(resolveFixture('apps/test'), 'notFound', 'http://localhost:8888', null)).rejects.toThrow('App ID was not found');
2147
- });
2148
- it('should throw if the remote is not found in context and defaultAppRemote is not specified', async () => {
2149
- const app = await App.create({
2150
- path: 'test-app',
2151
- definition: { name: 'Test App', defaultPage: 'Test Page' },
2152
- vapidPublicKey: 'a',
2153
- vapidPrivateKey: 'b',
2154
- visibility: 'public',
2155
- OrganizationId: organization.id,
2156
- });
2157
- await expect(() => resolveAppIdAndRemote(resolveFixture('apps/test'), 'notFound', null, app.id)).rejects.toThrow('App remote was not found');
2158
- });
2159
- });
2160
- describe('deleteApp', () => {
2161
- it('should throw if an error occurs', async () => {
2162
- const clientCredentials = await authorizeCLI('apps:delete', testApp);
2163
- await expect(() => deleteApp({ id: 1, remote: testApp.defaults.baseURL, clientCredentials })).rejects.toThrow('Request failed with status code 404');
2164
- });
2165
- it('should delete an app', async () => {
2166
- const app = await App.create({
2167
- path: 'test-app',
2168
- definition: { name: 'Test App', defaultPage: 'Test Page' },
2169
- vapidPublicKey: 'a',
2170
- vapidPrivateKey: 'b',
2171
- visibility: 'public',
2172
- OrganizationId: organization.id,
2173
- });
2174
- const clientCredentials = await authorizeCLI('apps:delete', testApp);
2175
- await deleteApp({ id: app.id, remote: testApp.defaults.baseURL, clientCredentials });
2176
- const foundApps = await App.findAll();
2177
- expect(foundApps).toStrictEqual([]);
2178
- });
2179
- });
2180
- describe('writeAppMessages', () => {
2181
- it('should extract messages from app-definition and write to file in i18n directory', async () => {
2182
- const initialMessages = JSON.parse(String(await readFixture('apps/test-messages/i18n/nl.json')));
2183
- expect(initialMessages).toMatchObject({
2184
- app: {},
2185
- });
2186
- await writeAppMessages(resolveFixture('apps/test-messages'), ['nl'], [], 'json');
2187
- const messages = JSON.parse(String(await readFixture('apps/test-messages/i18n/nl.json')));
2188
- expect(messages).toMatchObject({
2189
- app: {
2190
- description: '',
2191
- name: '',
2192
- 'pages.test-page': '',
2193
- },
2194
- });
2195
- await writeFile(resolveFixture('apps/test-messages/i18n/nl.json'), JSON.stringify(initialMessages, null, '\t'));
2196
- });
2197
- it('should throw if the language file does not exist', async () => {
2198
- await expect(() => writeAppMessages(resolveFixture('apps/test-messages'), ['hr'], [], 'json')).rejects.toThrow('Missing translations file');
2199
- });
2200
- it('should throw if there are empty messages in a verified file', async () => {
2201
- const initialMessages = JSON.parse(String(await readFixture('apps/test-messages/i18n/nl.json')));
2202
- expect(initialMessages).toMatchObject({
2203
- app: {},
2204
- });
2205
- await expect(() => writeAppMessages(resolveFixture('apps/test-messages'), ['nl'], ['nl'], 'json')).rejects.toThrow('Missing translation');
2206
- });
2207
- it('should throw if app definition does not exist in the app folder', async () => {
2208
- await expect(() => writeAppMessages(resolveFixture('apps/empty'), ['nl'], [], 'json')).rejects.toThrow('Couldn’t find app definition');
2209
- });
2210
- });
2211
- });
2212
- //# sourceMappingURL=app.test.js.map