@appsemble/cli 0.27.9 → 0.27.11

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,1350 @@
1
+ import { authenticate, readFixture, resolveFixture } from '@appsemble/node-utils';
2
+ import { authorizeClientCredentials, createServer, createTestUser, models, setArgv, useTestDatabase, } from '@appsemble/server';
3
+ import { ISODateTimePattern } from '@appsemble/utils';
4
+ import { setTestApp } from 'axios-test-instance';
5
+ import { hash } from 'bcrypt';
6
+ import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';
7
+ import { patchApp, publishApp, updateApp } from './app.js';
8
+ import { initAxios } from './initAxios.js';
9
+ const { App, AppBlockStyle, AppCollection, AppCollectionApp, AppMessages, AppScreenshot, Asset, BlockVersion, Organization, OrganizationMember, Resource, } = models;
10
+ const argv = { host: 'http://localhost', secret: 'test', aesSecret: 'testSecret' };
11
+ let user;
12
+ let organization;
13
+ let testApp;
14
+ async function authorizeCLI(scopes) {
15
+ const OAuth2AuthorizationCode = await authorizeClientCredentials(scopes);
16
+ const { id, secret } = OAuth2AuthorizationCode;
17
+ await OAuth2AuthorizationCode.update({ secret: await hash(secret, 10) });
18
+ await authenticate(testApp.defaults.baseURL, scopes, `${id}:${secret}`);
19
+ return `${id}:${secret}`;
20
+ }
21
+ useTestDatabase(import.meta);
22
+ beforeAll(() => {
23
+ vi.useFakeTimers();
24
+ setArgv(argv);
25
+ });
26
+ beforeEach(async () => {
27
+ vi.clearAllTimers();
28
+ vi.setSystemTime(0);
29
+ const server = await createServer();
30
+ testApp = await setTestApp(server);
31
+ initAxios({ remote: testApp.defaults.baseURL });
32
+ user = await createTestUser();
33
+ organization = await Organization.create({
34
+ id: 'testorganization',
35
+ name: 'Test Organization',
36
+ });
37
+ await OrganizationMember.create({
38
+ OrganizationId: organization.id,
39
+ UserId: user.id,
40
+ role: 'Owner',
41
+ });
42
+ await Organization.create({ id: 'appsemble', name: 'Appsemble' });
43
+ await BlockVersion.create({
44
+ name: 'test',
45
+ OrganizationId: 'appsemble',
46
+ version: '0.0.0',
47
+ parameters: {
48
+ type: 'object',
49
+ properties: {
50
+ foo: {
51
+ type: 'number',
52
+ },
53
+ },
54
+ },
55
+ });
56
+ });
57
+ afterAll(() => {
58
+ vi.useRealTimers();
59
+ });
60
+ describe('publishApp', () => {
61
+ it('should publish app', async () => {
62
+ vi.useRealTimers();
63
+ const clientCredentials = await authorizeCLI('apps:write resources:write');
64
+ await publishApp({
65
+ path: resolveFixture('apps/test'),
66
+ organization: organization.id,
67
+ remote: testApp.defaults.baseURL,
68
+ clientCredentials,
69
+ // Required defaults
70
+ visibility: 'unlisted',
71
+ iconBackground: '#ffffff',
72
+ });
73
+ vi.useFakeTimers();
74
+ const app = await App.findOne();
75
+ expect(app.toJSON()).toMatchInlineSnapshot({
76
+ $created: expect.stringMatching(ISODateTimePattern),
77
+ $updated: expect.stringMatching(ISODateTimePattern),
78
+ iconUrl: expect.any(String),
79
+ }, `
80
+ {
81
+ "$created": StringMatching /\\\\d\\{4\\}\\(\\.\\\\d\\{2\\}\\)\\{2\\}\\(\\\\s\\|T\\)\\(\\\\d\\{2\\}\\.\\)\\{2\\}\\\\d\\{2\\}/,
82
+ "$updated": StringMatching /\\\\d\\{4\\}\\(\\.\\\\d\\{2\\}\\)\\{2\\}\\(\\\\s\\|T\\)\\(\\\\d\\{2\\}\\.\\)\\{2\\}\\\\d\\{2\\}/,
83
+ "OrganizationId": "testorganization",
84
+ "OrganizationName": undefined,
85
+ "controllerCode": null,
86
+ "controllerImplementations": null,
87
+ "coreStyle": ".tux {
88
+ color: rgb(0 0 0);
89
+ }
90
+ ",
91
+ "definition": {
92
+ "defaultPage": "Test Page",
93
+ "name": "Test App",
94
+ "pages": [
95
+ {
96
+ "blocks": [
97
+ {
98
+ "type": "test",
99
+ "version": "0.0.0",
100
+ },
101
+ ],
102
+ "name": "Test Page",
103
+ },
104
+ ],
105
+ "resources": {
106
+ "test": {
107
+ "schema": {
108
+ "additionalProperties": false,
109
+ "properties": {
110
+ "test": {
111
+ "format": "binary",
112
+ "type": "string",
113
+ },
114
+ },
115
+ "type": "object",
116
+ },
117
+ },
118
+ },
119
+ },
120
+ "demoMode": false,
121
+ "domain": null,
122
+ "emailName": null,
123
+ "enableSelfRegistration": true,
124
+ "enableUnsecuredServiceSecrets": false,
125
+ "googleAnalyticsID": null,
126
+ "hasClonableAssets": undefined,
127
+ "hasClonableResources": undefined,
128
+ "hasIcon": true,
129
+ "hasMaskableIcon": true,
130
+ "iconBackground": "#ffffff",
131
+ "iconUrl": Any<String>,
132
+ "id": 1,
133
+ "locked": "unlocked",
134
+ "messages": undefined,
135
+ "path": "test-app",
136
+ "rating": undefined,
137
+ "readmeUrl": undefined,
138
+ "screenshotUrls": undefined,
139
+ "sentryDsn": null,
140
+ "sentryEnvironment": null,
141
+ "sharedStyle": ".tux {
142
+ color: rgb(0 0 0);
143
+ }
144
+ ",
145
+ "showAppDefinition": true,
146
+ "showAppsembleLogin": false,
147
+ "showAppsembleOAuth2Login": true,
148
+ "visibility": "unlisted",
149
+ "yaml": "name: Test App
150
+ defaultPage: Test Page
151
+ resources:
152
+ test:
153
+ schema:
154
+ additionalProperties: false
155
+ type: object
156
+ properties:
157
+ test:
158
+ type: string
159
+ format: binary
160
+ pages:
161
+ - name: Test Page
162
+ blocks:
163
+ - type: test
164
+ version: 0.0.0
165
+ ",
166
+ }
167
+ `);
168
+ expect(app.icon).toStrictEqual(await readFixture('apps/test/icon.png'));
169
+ expect(app.maskableIcon).toStrictEqual(await readFixture('apps/test/maskable-icon.png'));
170
+ const appBlockStyle = await AppBlockStyle.findOne();
171
+ expect(appBlockStyle.style).toBe(`.tux {
172
+ color: rgb(0 0 0);
173
+ }`);
174
+ const appScreenshot = await AppScreenshot.findOne();
175
+ expect(appScreenshot.screenshot).toStrictEqual(await readFixture('apps/test/screenshots/test_en-us.png'));
176
+ const appMessages = await AppMessages.findAll({ order: [['language', 'DESC']] });
177
+ expect(appMessages.map(({ language, messages }) => [language, messages])).toStrictEqual([
178
+ ['nl', JSON.parse(String(await readFixture('apps/test/i18n/nl.json')))],
179
+ ['en', JSON.parse(String(await readFixture('apps/test/i18n/en.json')))],
180
+ ]);
181
+ const resource = await Resource.findAll();
182
+ expect(resource).toStrictEqual([]);
183
+ const asset = await Asset.findAll();
184
+ expect(asset).toStrictEqual([]);
185
+ });
186
+ it('should publish app with resources and assets', async () => {
187
+ vi.useRealTimers();
188
+ const clientCredentials = await authorizeCLI('apps:write resources:write');
189
+ await publishApp({
190
+ path: resolveFixture('apps/test'),
191
+ organization: organization.id,
192
+ remote: testApp.defaults.baseURL,
193
+ clientCredentials,
194
+ // Required defaults
195
+ visibility: 'unlisted',
196
+ iconBackground: '#ffffff',
197
+ // Additional
198
+ resources: true,
199
+ assets: true,
200
+ });
201
+ vi.useFakeTimers();
202
+ const app = await App.findOne();
203
+ expect(app.toJSON()).toMatchInlineSnapshot({
204
+ $created: expect.stringMatching(ISODateTimePattern),
205
+ $updated: expect.stringMatching(ISODateTimePattern),
206
+ iconUrl: expect.any(String),
207
+ }, `
208
+ {
209
+ "$created": StringMatching /\\\\d\\{4\\}\\(\\.\\\\d\\{2\\}\\)\\{2\\}\\(\\\\s\\|T\\)\\(\\\\d\\{2\\}\\.\\)\\{2\\}\\\\d\\{2\\}/,
210
+ "$updated": StringMatching /\\\\d\\{4\\}\\(\\.\\\\d\\{2\\}\\)\\{2\\}\\(\\\\s\\|T\\)\\(\\\\d\\{2\\}\\.\\)\\{2\\}\\\\d\\{2\\}/,
211
+ "OrganizationId": "testorganization",
212
+ "OrganizationName": undefined,
213
+ "controllerCode": null,
214
+ "controllerImplementations": null,
215
+ "coreStyle": ".tux {
216
+ color: rgb(0 0 0);
217
+ }
218
+ ",
219
+ "definition": {
220
+ "defaultPage": "Test Page",
221
+ "name": "Test App",
222
+ "pages": [
223
+ {
224
+ "blocks": [
225
+ {
226
+ "type": "test",
227
+ "version": "0.0.0",
228
+ },
229
+ ],
230
+ "name": "Test Page",
231
+ },
232
+ ],
233
+ "resources": {
234
+ "test": {
235
+ "schema": {
236
+ "additionalProperties": false,
237
+ "properties": {
238
+ "test": {
239
+ "format": "binary",
240
+ "type": "string",
241
+ },
242
+ },
243
+ "type": "object",
244
+ },
245
+ },
246
+ },
247
+ },
248
+ "demoMode": false,
249
+ "domain": null,
250
+ "emailName": null,
251
+ "enableSelfRegistration": true,
252
+ "enableUnsecuredServiceSecrets": false,
253
+ "googleAnalyticsID": null,
254
+ "hasClonableAssets": undefined,
255
+ "hasClonableResources": undefined,
256
+ "hasIcon": true,
257
+ "hasMaskableIcon": true,
258
+ "iconBackground": "#ffffff",
259
+ "iconUrl": Any<String>,
260
+ "id": 1,
261
+ "locked": "unlocked",
262
+ "messages": undefined,
263
+ "path": "test-app",
264
+ "rating": undefined,
265
+ "readmeUrl": undefined,
266
+ "screenshotUrls": undefined,
267
+ "sentryDsn": null,
268
+ "sentryEnvironment": null,
269
+ "sharedStyle": ".tux {
270
+ color: rgb(0 0 0);
271
+ }
272
+ ",
273
+ "showAppDefinition": true,
274
+ "showAppsembleLogin": false,
275
+ "showAppsembleOAuth2Login": true,
276
+ "visibility": "unlisted",
277
+ "yaml": "name: Test App
278
+ defaultPage: Test Page
279
+ resources:
280
+ test:
281
+ schema:
282
+ additionalProperties: false
283
+ type: object
284
+ properties:
285
+ test:
286
+ type: string
287
+ format: binary
288
+ pages:
289
+ - name: Test Page
290
+ blocks:
291
+ - type: test
292
+ version: 0.0.0
293
+ ",
294
+ }
295
+ `);
296
+ expect(app.icon).toStrictEqual(await readFixture('apps/test/icon.png'));
297
+ expect(app.maskableIcon).toStrictEqual(await readFixture('apps/test/maskable-icon.png'));
298
+ const appBlockStyle = await AppBlockStyle.findOne();
299
+ expect(appBlockStyle.style).toBe(`.tux {
300
+ color: rgb(0 0 0);
301
+ }`);
302
+ const appScreenshot = await AppScreenshot.findOne();
303
+ expect(appScreenshot.screenshot).toStrictEqual(await readFixture('apps/test/screenshots/test_en-us.png'));
304
+ const appMessages = await AppMessages.findAll({ order: [['language', 'DESC']] });
305
+ expect(appMessages.map(({ language, messages }) => [language, messages])).toStrictEqual([
306
+ ['nl', JSON.parse(String(await readFixture('apps/test/i18n/nl.json')))],
307
+ ['en', JSON.parse(String(await readFixture('apps/test/i18n/en.json')))],
308
+ ]);
309
+ const resources = await Resource.findAll();
310
+ expect(resources.map((r) => r.toJSON())).toMatchInlineSnapshot([
311
+ {
312
+ $created: expect.stringMatching(ISODateTimePattern),
313
+ $updated: expect.stringMatching(ISODateTimePattern),
314
+ },
315
+ ], `
316
+ [
317
+ {
318
+ "$created": StringMatching /\\\\d\\{4\\}\\(\\.\\\\d\\{2\\}\\)\\{2\\}\\(\\\\s\\|T\\)\\(\\\\d\\{2\\}\\.\\)\\{2\\}\\\\d\\{2\\}/,
319
+ "$updated": StringMatching /\\\\d\\{4\\}\\(\\.\\\\d\\{2\\}\\)\\{2\\}\\(\\\\s\\|T\\)\\(\\\\d\\{2\\}\\.\\)\\{2\\}\\\\d\\{2\\}/,
320
+ "id": 1,
321
+ "test": "tux",
322
+ },
323
+ ]
324
+ `);
325
+ const assets = await Asset.findAll();
326
+ expect(assets.map((a) => a.data)).toStrictEqual([
327
+ await readFixture('apps/test/assets/tux.png'),
328
+ ]);
329
+ });
330
+ it('should publish app with runtime config', async () => {
331
+ await AppCollection.create({
332
+ name: 'test',
333
+ expertName: 'Expert',
334
+ expertProfileImage: Buffer.alloc(0),
335
+ expertProfileImageMimeType: 'image/png',
336
+ headerImage: Buffer.alloc(0),
337
+ headerImageMimeType: 'image/png',
338
+ OrganizationId: organization.id,
339
+ visibility: 'public',
340
+ });
341
+ vi.useRealTimers();
342
+ const clientCredentials = await authorizeCLI('apps:write resources:write');
343
+ await publishApp({
344
+ path: resolveFixture('apps/test'),
345
+ organization: organization.id,
346
+ remote: testApp.defaults.baseURL,
347
+ clientCredentials,
348
+ // Required defaults
349
+ visibility: 'unlisted',
350
+ iconBackground: '#ffffff',
351
+ // Define context
352
+ context: 'test',
353
+ });
354
+ vi.useFakeTimers();
355
+ const app = await App.findOne();
356
+ expect(app.toJSON()).toMatchInlineSnapshot({
357
+ $created: expect.stringMatching(ISODateTimePattern),
358
+ $updated: expect.stringMatching(ISODateTimePattern),
359
+ iconUrl: expect.any(String),
360
+ }, `
361
+ {
362
+ "$created": StringMatching /\\\\d\\{4\\}\\(\\.\\\\d\\{2\\}\\)\\{2\\}\\(\\\\s\\|T\\)\\(\\\\d\\{2\\}\\.\\)\\{2\\}\\\\d\\{2\\}/,
363
+ "$updated": StringMatching /\\\\d\\{4\\}\\(\\.\\\\d\\{2\\}\\)\\{2\\}\\(\\\\s\\|T\\)\\(\\\\d\\{2\\}\\.\\)\\{2\\}\\\\d\\{2\\}/,
364
+ "OrganizationId": "testorganization",
365
+ "OrganizationName": undefined,
366
+ "controllerCode": null,
367
+ "controllerImplementations": null,
368
+ "coreStyle": ".tux {
369
+ color: rgb(0 0 0);
370
+ }
371
+ ",
372
+ "definition": {
373
+ "defaultPage": "Test Page",
374
+ "name": "Test App",
375
+ "pages": [
376
+ {
377
+ "blocks": [
378
+ {
379
+ "type": "test",
380
+ "version": "0.0.0",
381
+ },
382
+ ],
383
+ "name": "Test Page",
384
+ },
385
+ ],
386
+ "resources": {
387
+ "test": {
388
+ "schema": {
389
+ "additionalProperties": false,
390
+ "properties": {
391
+ "test": {
392
+ "format": "binary",
393
+ "type": "string",
394
+ },
395
+ },
396
+ "type": "object",
397
+ },
398
+ },
399
+ },
400
+ },
401
+ "demoMode": true,
402
+ "domain": null,
403
+ "emailName": null,
404
+ "enableSelfRegistration": true,
405
+ "enableUnsecuredServiceSecrets": false,
406
+ "googleAnalyticsID": "test",
407
+ "hasClonableAssets": undefined,
408
+ "hasClonableResources": undefined,
409
+ "hasIcon": true,
410
+ "hasMaskableIcon": true,
411
+ "iconBackground": "#000000",
412
+ "iconUrl": Any<String>,
413
+ "id": 1,
414
+ "locked": "unlocked",
415
+ "messages": undefined,
416
+ "path": "test-app",
417
+ "rating": undefined,
418
+ "readmeUrl": undefined,
419
+ "screenshotUrls": undefined,
420
+ "sentryDsn": "https://public@sentry.example.com/1",
421
+ "sentryEnvironment": "test",
422
+ "sharedStyle": ".tux {
423
+ color: rgb(0 0 0);
424
+ }
425
+ ",
426
+ "showAppDefinition": true,
427
+ "showAppsembleLogin": false,
428
+ "showAppsembleOAuth2Login": true,
429
+ "visibility": "public",
430
+ "yaml": "name: Test App
431
+ defaultPage: Test Page
432
+ resources:
433
+ test:
434
+ schema:
435
+ additionalProperties: false
436
+ type: object
437
+ properties:
438
+ test:
439
+ type: string
440
+ format: binary
441
+ pages:
442
+ - name: Test Page
443
+ blocks:
444
+ - type: test
445
+ version: 0.0.0
446
+ ",
447
+ }
448
+ `);
449
+ expect(app.icon).toStrictEqual(await readFixture('apps/test/icon.png'));
450
+ expect(app.maskableIcon).toStrictEqual(await readFixture('apps/test/maskable-icon.png'));
451
+ const appBlockStyle = await AppBlockStyle.findOne();
452
+ expect(appBlockStyle.style).toBe(`.tux {
453
+ color: rgb(0 0 0);
454
+ }`);
455
+ const appScreenshot = await AppScreenshot.findOne();
456
+ expect(appScreenshot.screenshot).toStrictEqual(await readFixture('apps/test/screenshots/test_en-us.png'));
457
+ const appMessages = await AppMessages.findAll({ order: [['language', 'DESC']] });
458
+ expect(appMessages.map(({ language, messages }) => [language, messages])).toStrictEqual([
459
+ ['nl', JSON.parse(String(await readFixture('apps/test/i18n/nl.json')))],
460
+ ['en', JSON.parse(String(await readFixture('apps/test/i18n/en.json')))],
461
+ ]);
462
+ const resources = await Resource.findAll();
463
+ expect(resources.map((r) => r.toJSON())).toMatchInlineSnapshot([
464
+ {
465
+ $created: expect.stringMatching(ISODateTimePattern),
466
+ $updated: expect.stringMatching(ISODateTimePattern),
467
+ },
468
+ {
469
+ $created: expect.stringMatching(ISODateTimePattern),
470
+ $updated: expect.stringMatching(ISODateTimePattern),
471
+ },
472
+ ], `
473
+ [
474
+ {
475
+ "$created": StringMatching /\\\\d\\{4\\}\\(\\.\\\\d\\{2\\}\\)\\{2\\}\\(\\\\s\\|T\\)\\(\\\\d\\{2\\}\\.\\)\\{2\\}\\\\d\\{2\\}/,
476
+ "$updated": StringMatching /\\\\d\\{4\\}\\(\\.\\\\d\\{2\\}\\)\\{2\\}\\(\\\\s\\|T\\)\\(\\\\d\\{2\\}\\.\\)\\{2\\}\\\\d\\{2\\}/,
477
+ "id": 1,
478
+ "test": "tux",
479
+ },
480
+ {
481
+ "$created": StringMatching /\\\\d\\{4\\}\\(\\.\\\\d\\{2\\}\\)\\{2\\}\\(\\\\s\\|T\\)\\(\\\\d\\{2\\}\\.\\)\\{2\\}\\\\d\\{2\\}/,
482
+ "$ephemeral": true,
483
+ "$updated": StringMatching /\\\\d\\{4\\}\\(\\.\\\\d\\{2\\}\\)\\{2\\}\\(\\\\s\\|T\\)\\(\\\\d\\{2\\}\\.\\)\\{2\\}\\\\d\\{2\\}/,
484
+ "id": 2,
485
+ "test": "tux",
486
+ },
487
+ ]
488
+ `);
489
+ const assets = await Asset.findAll();
490
+ expect(assets.map((a) => a.data)).toStrictEqual([
491
+ await readFixture('apps/test/assets/tux.png'),
492
+ await readFixture('apps/test/assets/tux.png'),
493
+ ]);
494
+ const appCollectionApp = await AppCollectionApp.findOne();
495
+ expect(appCollectionApp.AppId).toBe(1);
496
+ expect(appCollectionApp.AppCollectionId).toBe(1);
497
+ });
498
+ it('should publish app with app variant patches applied', async () => {
499
+ vi.useRealTimers();
500
+ const clientCredentials = await authorizeCLI('apps:write resources:write');
501
+ await publishApp({
502
+ path: resolveFixture('apps/test'),
503
+ organization: organization.id,
504
+ remote: testApp.defaults.baseURL,
505
+ clientCredentials,
506
+ // Required defaults
507
+ visibility: 'unlisted',
508
+ iconBackground: '#ffffff',
509
+ // Define app variant
510
+ variant: 'tux',
511
+ resources: true,
512
+ assets: true,
513
+ });
514
+ vi.useFakeTimers();
515
+ const app = await App.findOne();
516
+ expect(app.toJSON()).toMatchInlineSnapshot({
517
+ $created: expect.stringMatching(ISODateTimePattern),
518
+ $updated: expect.stringMatching(ISODateTimePattern),
519
+ iconUrl: expect.any(String),
520
+ }, `
521
+ {
522
+ "$created": StringMatching /\\\\d\\{4\\}\\(\\.\\\\d\\{2\\}\\)\\{2\\}\\(\\\\s\\|T\\)\\(\\\\d\\{2\\}\\.\\)\\{2\\}\\\\d\\{2\\}/,
523
+ "$updated": StringMatching /\\\\d\\{4\\}\\(\\.\\\\d\\{2\\}\\)\\{2\\}\\(\\\\s\\|T\\)\\(\\\\d\\{2\\}\\.\\)\\{2\\}\\\\d\\{2\\}/,
524
+ "OrganizationId": "testorganization",
525
+ "OrganizationName": undefined,
526
+ "controllerCode": null,
527
+ "controllerImplementations": null,
528
+ "coreStyle": ".tux{color:rgb(1 2 3)}",
529
+ "definition": {
530
+ "defaultPage": "Test Page",
531
+ "name": "Test App",
532
+ "pages": [
533
+ {
534
+ "blocks": [
535
+ {
536
+ "type": "test",
537
+ "version": "0.0.0",
538
+ },
539
+ ],
540
+ "name": "Test Page",
541
+ },
542
+ ],
543
+ "resources": {
544
+ "test": {
545
+ "schema": {
546
+ "additionalProperties": false,
547
+ "properties": {
548
+ "tux": {
549
+ "format": "binary",
550
+ "type": "string",
551
+ },
552
+ },
553
+ "type": "object",
554
+ },
555
+ },
556
+ },
557
+ },
558
+ "demoMode": false,
559
+ "domain": null,
560
+ "emailName": null,
561
+ "enableSelfRegistration": true,
562
+ "enableUnsecuredServiceSecrets": false,
563
+ "googleAnalyticsID": null,
564
+ "hasClonableAssets": undefined,
565
+ "hasClonableResources": undefined,
566
+ "hasIcon": true,
567
+ "hasMaskableIcon": true,
568
+ "iconBackground": "#ffffff",
569
+ "iconUrl": Any<String>,
570
+ "id": 1,
571
+ "locked": "unlocked",
572
+ "messages": undefined,
573
+ "path": "test-app",
574
+ "rating": undefined,
575
+ "readmeUrl": undefined,
576
+ "screenshotUrls": undefined,
577
+ "sentryDsn": null,
578
+ "sentryEnvironment": null,
579
+ "sharedStyle": ".tux{color:rgb(1 2 3)}",
580
+ "showAppDefinition": true,
581
+ "showAppsembleLogin": false,
582
+ "showAppsembleOAuth2Login": true,
583
+ "visibility": "unlisted",
584
+ "yaml": "name: Test App
585
+ defaultPage: Test Page
586
+ resources:
587
+ test:
588
+ schema:
589
+ additionalProperties: false
590
+ type: object
591
+ properties:
592
+ tux:
593
+ type: string
594
+ format: binary
595
+ pages:
596
+ - name: Test Page
597
+ blocks:
598
+ - type: test
599
+ version: 0.0.0
600
+ ",
601
+ }
602
+ `);
603
+ expect(app.icon).toStrictEqual(await readFixture('apps/test/variants/tux/icon.png'));
604
+ expect(app.maskableIcon).toStrictEqual(await readFixture('apps/test/variants/tux/maskable-icon.png'));
605
+ const appBlockStyle = await AppBlockStyle.findOne();
606
+ expect(appBlockStyle.style).toBe('.tux{color:rgb(1 2 3)}');
607
+ const appScreenshot = await AppScreenshot.findOne();
608
+ expect(appScreenshot.screenshot).toStrictEqual(await readFixture('apps/test/variants/tux/screenshots/tux.png'));
609
+ const appMessages = await AppMessages.findAll({ order: [['language', 'DESC']] });
610
+ const messages = JSON.parse(String(await readFixture('apps/test/variants/tux/patches/messages.json')));
611
+ expect(appMessages.map(({ language, messages: msgs }) => [language, msgs])).toStrictEqual([
612
+ ['nl', messages.nl],
613
+ ['en', messages.en],
614
+ ]);
615
+ const resources = await Resource.findAll();
616
+ expect(resources.map((r) => r.toJSON())).toMatchInlineSnapshot([
617
+ {
618
+ $created: expect.stringMatching(ISODateTimePattern),
619
+ $updated: expect.stringMatching(ISODateTimePattern),
620
+ },
621
+ ], `
622
+ [
623
+ {
624
+ "$created": StringMatching /\\\\d\\{4\\}\\(\\.\\\\d\\{2\\}\\)\\{2\\}\\(\\\\s\\|T\\)\\(\\\\d\\{2\\}\\.\\)\\{2\\}\\\\d\\{2\\}/,
625
+ "$updated": StringMatching /\\\\d\\{4\\}\\(\\.\\\\d\\{2\\}\\)\\{2\\}\\(\\\\s\\|T\\)\\(\\\\d\\{2\\}\\.\\)\\{2\\}\\\\d\\{2\\}/,
626
+ "id": 1,
627
+ "tux": "small-tux",
628
+ },
629
+ ]
630
+ `);
631
+ const assets = await Asset.findAll();
632
+ expect(assets.map((a) => a.data)).toStrictEqual([
633
+ await readFixture('apps/test/variants/tux/assets/small-tux.png'),
634
+ ]);
635
+ });
636
+ });
637
+ describe('updateApp', () => {
638
+ let app;
639
+ beforeEach(async () => {
640
+ app = await App.create({
641
+ path: 'test-app',
642
+ definition: { name: 'Test App', defaultPage: 'Test Page' },
643
+ vapidPublicKey: 'a',
644
+ vapidPrivateKey: 'b',
645
+ visibility: 'public',
646
+ OrganizationId: organization.id,
647
+ });
648
+ });
649
+ it('should update app', async () => {
650
+ vi.useRealTimers();
651
+ const clientCredentials = await authorizeCLI('apps:write resources:write');
652
+ await updateApp({
653
+ id: app.id,
654
+ path: resolveFixture('apps/test'),
655
+ remote: testApp.defaults.baseURL,
656
+ clientCredentials,
657
+ // Required defaults
658
+ visibility: 'unlisted',
659
+ iconBackground: '#ffffff',
660
+ force: false,
661
+ });
662
+ vi.useFakeTimers();
663
+ await app.reload();
664
+ expect(app.toJSON()).toMatchInlineSnapshot({
665
+ $created: expect.stringMatching(ISODateTimePattern),
666
+ $updated: expect.stringMatching(ISODateTimePattern),
667
+ iconUrl: expect.any(String),
668
+ }, `
669
+ {
670
+ "$created": StringMatching /\\\\d\\{4\\}\\(\\.\\\\d\\{2\\}\\)\\{2\\}\\(\\\\s\\|T\\)\\(\\\\d\\{2\\}\\.\\)\\{2\\}\\\\d\\{2\\}/,
671
+ "$updated": StringMatching /\\\\d\\{4\\}\\(\\.\\\\d\\{2\\}\\)\\{2\\}\\(\\\\s\\|T\\)\\(\\\\d\\{2\\}\\.\\)\\{2\\}\\\\d\\{2\\}/,
672
+ "OrganizationId": "testorganization",
673
+ "OrganizationName": undefined,
674
+ "controllerCode": null,
675
+ "controllerImplementations": null,
676
+ "coreStyle": ".tux {
677
+ color: rgb(0 0 0);
678
+ }
679
+ ",
680
+ "definition": {
681
+ "defaultPage": "Test Page",
682
+ "name": "Test App",
683
+ "pages": [
684
+ {
685
+ "blocks": [
686
+ {
687
+ "type": "test",
688
+ "version": "0.0.0",
689
+ },
690
+ ],
691
+ "name": "Test Page",
692
+ },
693
+ ],
694
+ "resources": {
695
+ "test": {
696
+ "schema": {
697
+ "additionalProperties": false,
698
+ "properties": {
699
+ "test": {
700
+ "format": "binary",
701
+ "type": "string",
702
+ },
703
+ },
704
+ "type": "object",
705
+ },
706
+ },
707
+ },
708
+ },
709
+ "demoMode": false,
710
+ "domain": null,
711
+ "emailName": null,
712
+ "enableSelfRegistration": true,
713
+ "enableUnsecuredServiceSecrets": false,
714
+ "googleAnalyticsID": null,
715
+ "hasClonableAssets": undefined,
716
+ "hasClonableResources": undefined,
717
+ "hasIcon": true,
718
+ "hasMaskableIcon": true,
719
+ "iconBackground": "#ffffff",
720
+ "iconUrl": Any<String>,
721
+ "id": 1,
722
+ "locked": "unlocked",
723
+ "messages": undefined,
724
+ "path": "test-app",
725
+ "rating": undefined,
726
+ "readmeUrl": undefined,
727
+ "screenshotUrls": undefined,
728
+ "sentryDsn": null,
729
+ "sentryEnvironment": null,
730
+ "sharedStyle": ".tux {
731
+ color: rgb(0 0 0);
732
+ }
733
+ ",
734
+ "showAppDefinition": false,
735
+ "showAppsembleLogin": false,
736
+ "showAppsembleOAuth2Login": true,
737
+ "visibility": "unlisted",
738
+ "yaml": "name: Test App
739
+ defaultPage: Test Page
740
+ resources:
741
+ test:
742
+ schema:
743
+ additionalProperties: false
744
+ type: object
745
+ properties:
746
+ test:
747
+ type: string
748
+ format: binary
749
+ pages:
750
+ - name: Test Page
751
+ blocks:
752
+ - type: test
753
+ version: 0.0.0
754
+ ",
755
+ }
756
+ `);
757
+ expect(app.icon).toStrictEqual(await readFixture('apps/test/icon.png'));
758
+ expect(app.maskableIcon).toStrictEqual(await readFixture('apps/test/maskable-icon.png'));
759
+ const appBlockStyle = await AppBlockStyle.findOne();
760
+ expect(appBlockStyle.style).toBe(`.tux {
761
+ color: rgb(0 0 0);
762
+ }`);
763
+ const appScreenshot = await AppScreenshot.findOne();
764
+ expect(appScreenshot.screenshot).toStrictEqual(await readFixture('apps/test/screenshots/test_en-us.png'));
765
+ const appMessages = await AppMessages.findAll({ order: [['language', 'DESC']] });
766
+ expect(appMessages.map(({ language, messages }) => [language, messages])).toStrictEqual([
767
+ ['nl', JSON.parse(String(await readFixture('apps/test/i18n/nl.json')))],
768
+ ['en', JSON.parse(String(await readFixture('apps/test/i18n/en.json')))],
769
+ ]);
770
+ const resource = await Resource.findAll();
771
+ expect(resource).toStrictEqual([]);
772
+ const asset = await Asset.findAll();
773
+ expect(asset).toStrictEqual([]);
774
+ });
775
+ it('should update app with resources and assets', async () => {
776
+ vi.useRealTimers();
777
+ const clientCredentials = await authorizeCLI('apps:write resources:write');
778
+ await updateApp({
779
+ id: app.id,
780
+ path: resolveFixture('apps/test'),
781
+ remote: testApp.defaults.baseURL,
782
+ clientCredentials,
783
+ // Required defaults
784
+ visibility: 'unlisted',
785
+ iconBackground: '#ffffff',
786
+ force: false,
787
+ // Additional
788
+ resources: true,
789
+ assets: true,
790
+ });
791
+ vi.useFakeTimers();
792
+ await app.reload();
793
+ expect(app.toJSON()).toMatchInlineSnapshot({
794
+ $created: expect.stringMatching(ISODateTimePattern),
795
+ $updated: expect.stringMatching(ISODateTimePattern),
796
+ iconUrl: expect.any(String),
797
+ }, `
798
+ {
799
+ "$created": StringMatching /\\\\d\\{4\\}\\(\\.\\\\d\\{2\\}\\)\\{2\\}\\(\\\\s\\|T\\)\\(\\\\d\\{2\\}\\.\\)\\{2\\}\\\\d\\{2\\}/,
800
+ "$updated": StringMatching /\\\\d\\{4\\}\\(\\.\\\\d\\{2\\}\\)\\{2\\}\\(\\\\s\\|T\\)\\(\\\\d\\{2\\}\\.\\)\\{2\\}\\\\d\\{2\\}/,
801
+ "OrganizationId": "testorganization",
802
+ "OrganizationName": undefined,
803
+ "controllerCode": null,
804
+ "controllerImplementations": null,
805
+ "coreStyle": ".tux {
806
+ color: rgb(0 0 0);
807
+ }
808
+ ",
809
+ "definition": {
810
+ "defaultPage": "Test Page",
811
+ "name": "Test App",
812
+ "pages": [
813
+ {
814
+ "blocks": [
815
+ {
816
+ "type": "test",
817
+ "version": "0.0.0",
818
+ },
819
+ ],
820
+ "name": "Test Page",
821
+ },
822
+ ],
823
+ "resources": {
824
+ "test": {
825
+ "schema": {
826
+ "additionalProperties": false,
827
+ "properties": {
828
+ "test": {
829
+ "format": "binary",
830
+ "type": "string",
831
+ },
832
+ },
833
+ "type": "object",
834
+ },
835
+ },
836
+ },
837
+ },
838
+ "demoMode": false,
839
+ "domain": null,
840
+ "emailName": null,
841
+ "enableSelfRegistration": true,
842
+ "enableUnsecuredServiceSecrets": false,
843
+ "googleAnalyticsID": null,
844
+ "hasClonableAssets": undefined,
845
+ "hasClonableResources": undefined,
846
+ "hasIcon": true,
847
+ "hasMaskableIcon": true,
848
+ "iconBackground": "#ffffff",
849
+ "iconUrl": Any<String>,
850
+ "id": 1,
851
+ "locked": "unlocked",
852
+ "messages": undefined,
853
+ "path": "test-app",
854
+ "rating": undefined,
855
+ "readmeUrl": undefined,
856
+ "screenshotUrls": undefined,
857
+ "sentryDsn": null,
858
+ "sentryEnvironment": null,
859
+ "sharedStyle": ".tux {
860
+ color: rgb(0 0 0);
861
+ }
862
+ ",
863
+ "showAppDefinition": false,
864
+ "showAppsembleLogin": false,
865
+ "showAppsembleOAuth2Login": true,
866
+ "visibility": "unlisted",
867
+ "yaml": "name: Test App
868
+ defaultPage: Test Page
869
+ resources:
870
+ test:
871
+ schema:
872
+ additionalProperties: false
873
+ type: object
874
+ properties:
875
+ test:
876
+ type: string
877
+ format: binary
878
+ pages:
879
+ - name: Test Page
880
+ blocks:
881
+ - type: test
882
+ version: 0.0.0
883
+ ",
884
+ }
885
+ `);
886
+ expect(app.icon).toStrictEqual(await readFixture('apps/test/icon.png'));
887
+ expect(app.maskableIcon).toStrictEqual(await readFixture('apps/test/maskable-icon.png'));
888
+ const appBlockStyle = await AppBlockStyle.findOne();
889
+ expect(appBlockStyle.style).toBe(`.tux {
890
+ color: rgb(0 0 0);
891
+ }`);
892
+ const appScreenshot = await AppScreenshot.findOne();
893
+ expect(appScreenshot.screenshot).toStrictEqual(await readFixture('apps/test/screenshots/test_en-us.png'));
894
+ const appMessages = await AppMessages.findAll({ order: [['language', 'DESC']] });
895
+ expect(appMessages.map(({ language, messages }) => [language, messages])).toStrictEqual([
896
+ ['nl', JSON.parse(String(await readFixture('apps/test/i18n/nl.json')))],
897
+ ['en', JSON.parse(String(await readFixture('apps/test/i18n/en.json')))],
898
+ ]);
899
+ const resources = await Resource.findAll();
900
+ expect(resources.map((r) => r.toJSON())).toMatchInlineSnapshot([
901
+ {
902
+ $created: expect.stringMatching(ISODateTimePattern),
903
+ $updated: expect.stringMatching(ISODateTimePattern),
904
+ },
905
+ ], `
906
+ [
907
+ {
908
+ "$created": StringMatching /\\\\d\\{4\\}\\(\\.\\\\d\\{2\\}\\)\\{2\\}\\(\\\\s\\|T\\)\\(\\\\d\\{2\\}\\.\\)\\{2\\}\\\\d\\{2\\}/,
909
+ "$updated": StringMatching /\\\\d\\{4\\}\\(\\.\\\\d\\{2\\}\\)\\{2\\}\\(\\\\s\\|T\\)\\(\\\\d\\{2\\}\\.\\)\\{2\\}\\\\d\\{2\\}/,
910
+ "id": 1,
911
+ "test": "tux",
912
+ },
913
+ ]
914
+ `);
915
+ const assets = await Asset.findAll();
916
+ expect(assets.map((a) => a.data)).toStrictEqual([
917
+ await readFixture('apps/test/assets/tux.png'),
918
+ ]);
919
+ });
920
+ it('should update app with runtime config', async () => {
921
+ await AppCollection.create({
922
+ name: 'test',
923
+ expertName: 'Expert',
924
+ expertProfileImage: Buffer.alloc(0),
925
+ expertProfileImageMimeType: 'image/png',
926
+ headerImage: Buffer.alloc(0),
927
+ headerImageMimeType: 'image/png',
928
+ OrganizationId: organization.id,
929
+ visibility: 'public',
930
+ });
931
+ vi.useRealTimers();
932
+ const clientCredentials = await authorizeCLI('apps:write resources:write');
933
+ await updateApp({
934
+ path: resolveFixture('apps/test'),
935
+ id: app.id,
936
+ remote: testApp.defaults.baseURL,
937
+ clientCredentials,
938
+ // Required defaults
939
+ visibility: 'unlisted',
940
+ iconBackground: '#ffffff',
941
+ force: false,
942
+ // Define context
943
+ context: 'test',
944
+ });
945
+ vi.useFakeTimers();
946
+ await app.reload();
947
+ expect(app.toJSON()).toMatchInlineSnapshot({
948
+ $created: expect.stringMatching(ISODateTimePattern),
949
+ $updated: expect.stringMatching(ISODateTimePattern),
950
+ iconUrl: expect.any(String),
951
+ }, `
952
+ {
953
+ "$created": StringMatching /\\\\d\\{4\\}\\(\\.\\\\d\\{2\\}\\)\\{2\\}\\(\\\\s\\|T\\)\\(\\\\d\\{2\\}\\.\\)\\{2\\}\\\\d\\{2\\}/,
954
+ "$updated": StringMatching /\\\\d\\{4\\}\\(\\.\\\\d\\{2\\}\\)\\{2\\}\\(\\\\s\\|T\\)\\(\\\\d\\{2\\}\\.\\)\\{2\\}\\\\d\\{2\\}/,
955
+ "OrganizationId": "testorganization",
956
+ "OrganizationName": undefined,
957
+ "controllerCode": null,
958
+ "controllerImplementations": null,
959
+ "coreStyle": ".tux {
960
+ color: rgb(0 0 0);
961
+ }
962
+ ",
963
+ "definition": {
964
+ "defaultPage": "Test Page",
965
+ "name": "Test App",
966
+ "pages": [
967
+ {
968
+ "blocks": [
969
+ {
970
+ "type": "test",
971
+ "version": "0.0.0",
972
+ },
973
+ ],
974
+ "name": "Test Page",
975
+ },
976
+ ],
977
+ "resources": {
978
+ "test": {
979
+ "schema": {
980
+ "additionalProperties": false,
981
+ "properties": {
982
+ "test": {
983
+ "format": "binary",
984
+ "type": "string",
985
+ },
986
+ },
987
+ "type": "object",
988
+ },
989
+ },
990
+ },
991
+ },
992
+ "demoMode": true,
993
+ "domain": null,
994
+ "emailName": null,
995
+ "enableSelfRegistration": true,
996
+ "enableUnsecuredServiceSecrets": false,
997
+ "googleAnalyticsID": "test",
998
+ "hasClonableAssets": undefined,
999
+ "hasClonableResources": undefined,
1000
+ "hasIcon": true,
1001
+ "hasMaskableIcon": true,
1002
+ "iconBackground": "#000000",
1003
+ "iconUrl": Any<String>,
1004
+ "id": 1,
1005
+ "locked": "studioLock",
1006
+ "messages": undefined,
1007
+ "path": "test-app",
1008
+ "rating": undefined,
1009
+ "readmeUrl": undefined,
1010
+ "screenshotUrls": undefined,
1011
+ "sentryDsn": "https://public@sentry.example.com/1",
1012
+ "sentryEnvironment": "test",
1013
+ "sharedStyle": ".tux {
1014
+ color: rgb(0 0 0);
1015
+ }
1016
+ ",
1017
+ "showAppDefinition": false,
1018
+ "showAppsembleLogin": false,
1019
+ "showAppsembleOAuth2Login": true,
1020
+ "visibility": "public",
1021
+ "yaml": "name: Test App
1022
+ defaultPage: Test Page
1023
+ resources:
1024
+ test:
1025
+ schema:
1026
+ additionalProperties: false
1027
+ type: object
1028
+ properties:
1029
+ test:
1030
+ type: string
1031
+ format: binary
1032
+ pages:
1033
+ - name: Test Page
1034
+ blocks:
1035
+ - type: test
1036
+ version: 0.0.0
1037
+ ",
1038
+ }
1039
+ `);
1040
+ expect(app.icon).toStrictEqual(await readFixture('apps/test/icon.png'));
1041
+ expect(app.maskableIcon).toStrictEqual(await readFixture('apps/test/maskable-icon.png'));
1042
+ const appBlockStyle = await AppBlockStyle.findOne();
1043
+ expect(appBlockStyle.style).toBe(`.tux {
1044
+ color: rgb(0 0 0);
1045
+ }`);
1046
+ const appScreenshot = await AppScreenshot.findOne();
1047
+ expect(appScreenshot.screenshot).toStrictEqual(await readFixture('apps/test/screenshots/test_en-us.png'));
1048
+ const appMessages = await AppMessages.findAll({ order: [['language', 'DESC']] });
1049
+ expect(appMessages.map(({ language, messages }) => [language, messages])).toStrictEqual([
1050
+ ['nl', JSON.parse(String(await readFixture('apps/test/i18n/nl.json')))],
1051
+ ['en', JSON.parse(String(await readFixture('apps/test/i18n/en.json')))],
1052
+ ]);
1053
+ const resources = await Resource.findAll();
1054
+ expect(resources.map((r) => r.toJSON())).toMatchInlineSnapshot([
1055
+ {
1056
+ $created: expect.stringMatching(ISODateTimePattern),
1057
+ $updated: expect.stringMatching(ISODateTimePattern),
1058
+ },
1059
+ {
1060
+ $created: expect.stringMatching(ISODateTimePattern),
1061
+ $updated: expect.stringMatching(ISODateTimePattern),
1062
+ },
1063
+ ], `
1064
+ [
1065
+ {
1066
+ "$created": StringMatching /\\\\d\\{4\\}\\(\\.\\\\d\\{2\\}\\)\\{2\\}\\(\\\\s\\|T\\)\\(\\\\d\\{2\\}\\.\\)\\{2\\}\\\\d\\{2\\}/,
1067
+ "$updated": StringMatching /\\\\d\\{4\\}\\(\\.\\\\d\\{2\\}\\)\\{2\\}\\(\\\\s\\|T\\)\\(\\\\d\\{2\\}\\.\\)\\{2\\}\\\\d\\{2\\}/,
1068
+ "id": 1,
1069
+ "test": "tux",
1070
+ },
1071
+ {
1072
+ "$created": StringMatching /\\\\d\\{4\\}\\(\\.\\\\d\\{2\\}\\)\\{2\\}\\(\\\\s\\|T\\)\\(\\\\d\\{2\\}\\.\\)\\{2\\}\\\\d\\{2\\}/,
1073
+ "$ephemeral": true,
1074
+ "$updated": StringMatching /\\\\d\\{4\\}\\(\\.\\\\d\\{2\\}\\)\\{2\\}\\(\\\\s\\|T\\)\\(\\\\d\\{2\\}\\.\\)\\{2\\}\\\\d\\{2\\}/,
1075
+ "id": 2,
1076
+ "test": "tux",
1077
+ },
1078
+ ]
1079
+ `);
1080
+ const assets = await Asset.findAll();
1081
+ expect(assets.map((a) => a.data)).toStrictEqual([
1082
+ await readFixture('apps/test/assets/tux.png'),
1083
+ await readFixture('apps/test/assets/tux.png'),
1084
+ ]);
1085
+ // TODO: not yet implemented
1086
+ // const appCollectionApp = await AppCollectionApp.findOne();
1087
+ // expect(appCollectionApp.AppId).toBe(1);
1088
+ // expect(appCollectionApp.AppCollectionId).toBe(1);
1089
+ });
1090
+ it('should update app with app variant patches applied', async () => {
1091
+ vi.useRealTimers();
1092
+ const clientCredentials = await authorizeCLI('apps:write resources:write');
1093
+ await updateApp({
1094
+ path: resolveFixture('apps/test'),
1095
+ id: app.id,
1096
+ remote: testApp.defaults.baseURL,
1097
+ clientCredentials,
1098
+ // Required defaults
1099
+ visibility: 'unlisted',
1100
+ iconBackground: '#ffffff',
1101
+ force: false,
1102
+ // Define app variant
1103
+ variant: 'tux',
1104
+ resources: true,
1105
+ assets: true,
1106
+ });
1107
+ vi.useFakeTimers();
1108
+ await app.reload();
1109
+ expect(app.toJSON()).toMatchInlineSnapshot({
1110
+ $created: expect.stringMatching(ISODateTimePattern),
1111
+ $updated: expect.stringMatching(ISODateTimePattern),
1112
+ iconUrl: expect.any(String),
1113
+ }, `
1114
+ {
1115
+ "$created": StringMatching /\\\\d\\{4\\}\\(\\.\\\\d\\{2\\}\\)\\{2\\}\\(\\\\s\\|T\\)\\(\\\\d\\{2\\}\\.\\)\\{2\\}\\\\d\\{2\\}/,
1116
+ "$updated": StringMatching /\\\\d\\{4\\}\\(\\.\\\\d\\{2\\}\\)\\{2\\}\\(\\\\s\\|T\\)\\(\\\\d\\{2\\}\\.\\)\\{2\\}\\\\d\\{2\\}/,
1117
+ "OrganizationId": "testorganization",
1118
+ "OrganizationName": undefined,
1119
+ "controllerCode": null,
1120
+ "controllerImplementations": null,
1121
+ "coreStyle": ".tux{color:rgb(1 2 3)}",
1122
+ "definition": {
1123
+ "defaultPage": "Test Page",
1124
+ "name": "Test App",
1125
+ "pages": [
1126
+ {
1127
+ "blocks": [
1128
+ {
1129
+ "type": "test",
1130
+ "version": "0.0.0",
1131
+ },
1132
+ ],
1133
+ "name": "Test Page",
1134
+ },
1135
+ ],
1136
+ "resources": {
1137
+ "test": {
1138
+ "schema": {
1139
+ "additionalProperties": false,
1140
+ "properties": {
1141
+ "tux": {
1142
+ "format": "binary",
1143
+ "type": "string",
1144
+ },
1145
+ },
1146
+ "type": "object",
1147
+ },
1148
+ },
1149
+ },
1150
+ },
1151
+ "demoMode": false,
1152
+ "domain": null,
1153
+ "emailName": null,
1154
+ "enableSelfRegistration": true,
1155
+ "enableUnsecuredServiceSecrets": false,
1156
+ "googleAnalyticsID": null,
1157
+ "hasClonableAssets": undefined,
1158
+ "hasClonableResources": undefined,
1159
+ "hasIcon": true,
1160
+ "hasMaskableIcon": true,
1161
+ "iconBackground": "#ffffff",
1162
+ "iconUrl": Any<String>,
1163
+ "id": 1,
1164
+ "locked": "unlocked",
1165
+ "messages": undefined,
1166
+ "path": "test-app",
1167
+ "rating": undefined,
1168
+ "readmeUrl": undefined,
1169
+ "screenshotUrls": undefined,
1170
+ "sentryDsn": null,
1171
+ "sentryEnvironment": null,
1172
+ "sharedStyle": ".tux{color:rgb(1 2 3)}",
1173
+ "showAppDefinition": false,
1174
+ "showAppsembleLogin": false,
1175
+ "showAppsembleOAuth2Login": true,
1176
+ "visibility": "unlisted",
1177
+ "yaml": "name: Test App
1178
+ defaultPage: Test Page
1179
+ resources:
1180
+ test:
1181
+ schema:
1182
+ additionalProperties: false
1183
+ type: object
1184
+ properties:
1185
+ tux:
1186
+ type: string
1187
+ format: binary
1188
+ pages:
1189
+ - name: Test Page
1190
+ blocks:
1191
+ - type: test
1192
+ version: 0.0.0
1193
+ ",
1194
+ }
1195
+ `);
1196
+ expect(app.icon).toStrictEqual(await readFixture('apps/test/variants/tux/icon.png'));
1197
+ expect(app.maskableIcon).toStrictEqual(await readFixture('apps/test/variants/tux/maskable-icon.png'));
1198
+ const appBlockStyle = await AppBlockStyle.findOne();
1199
+ expect(appBlockStyle.style).toBe('.tux{color:rgb(1 2 3)}');
1200
+ const appScreenshot = await AppScreenshot.findOne();
1201
+ expect(appScreenshot.screenshot).toStrictEqual(await readFixture('apps/test/variants/tux/screenshots/tux.png'));
1202
+ const appMessages = await AppMessages.findAll({ order: [['language', 'DESC']] });
1203
+ const messages = JSON.parse(String(await readFixture('apps/test/variants/tux/patches/messages.json')));
1204
+ expect(appMessages.map(({ language, messages: msgs }) => [language, msgs])).toStrictEqual([
1205
+ ['nl', messages.nl],
1206
+ ['en', messages.en],
1207
+ ]);
1208
+ const resources = await Resource.findAll();
1209
+ expect(resources.map((r) => r.toJSON())).toMatchInlineSnapshot([
1210
+ {
1211
+ $created: expect.stringMatching(ISODateTimePattern),
1212
+ $updated: expect.stringMatching(ISODateTimePattern),
1213
+ },
1214
+ ], `
1215
+ [
1216
+ {
1217
+ "$created": StringMatching /\\\\d\\{4\\}\\(\\.\\\\d\\{2\\}\\)\\{2\\}\\(\\\\s\\|T\\)\\(\\\\d\\{2\\}\\.\\)\\{2\\}\\\\d\\{2\\}/,
1218
+ "$updated": StringMatching /\\\\d\\{4\\}\\(\\.\\\\d\\{2\\}\\)\\{2\\}\\(\\\\s\\|T\\)\\(\\\\d\\{2\\}\\.\\)\\{2\\}\\\\d\\{2\\}/,
1219
+ "id": 1,
1220
+ "tux": "small-tux",
1221
+ },
1222
+ ]
1223
+ `);
1224
+ const assets = await Asset.findAll();
1225
+ expect(assets.map((a) => a.data)).toStrictEqual([
1226
+ await readFixture('apps/test/variants/tux/assets/small-tux.png'),
1227
+ ]);
1228
+ });
1229
+ });
1230
+ describe('patchApp', () => {
1231
+ it('should apply patches to app', async () => {
1232
+ const patches = {
1233
+ demoMode: true,
1234
+ enableSelfRegistration: true,
1235
+ iconBackground: '#FFFFFF',
1236
+ locked: 'fullLock',
1237
+ path: 'updated-path',
1238
+ showAppDefinition: true,
1239
+ showAppsembleLogin: true,
1240
+ showAppsembleOAuth2Login: true,
1241
+ template: true,
1242
+ visibility: 'private',
1243
+ };
1244
+ const app = await App.create({
1245
+ path: 'test-app',
1246
+ definition: { name: 'Test App', defaultPage: 'Test Page' },
1247
+ vapidPublicKey: 'a',
1248
+ vapidPrivateKey: 'b',
1249
+ visibility: 'public',
1250
+ OrganizationId: organization.id,
1251
+ }, { raw: true });
1252
+ await authorizeCLI('apps:write');
1253
+ await patchApp({
1254
+ ...patches,
1255
+ remote: testApp.defaults.baseURL,
1256
+ id: app.id,
1257
+ });
1258
+ await app.reload();
1259
+ expect(app.dataValues).toStrictEqual(expect.objectContaining({ ...app.dataValues, ...patches }));
1260
+ expect(app.dataValues).toMatchInlineSnapshot(`
1261
+ {
1262
+ "OrganizationId": "testorganization",
1263
+ "controllerCode": null,
1264
+ "controllerImplementations": null,
1265
+ "coreStyle": null,
1266
+ "created": 1970-01-01T00:00:00.000Z,
1267
+ "definition": {
1268
+ "defaultPage": "Test Page",
1269
+ "name": "Test App",
1270
+ },
1271
+ "deleted": null,
1272
+ "demoMode": true,
1273
+ "domain": null,
1274
+ "emailHost": null,
1275
+ "emailName": null,
1276
+ "emailPassword": null,
1277
+ "emailPort": 587,
1278
+ "emailSecure": true,
1279
+ "emailUser": null,
1280
+ "enableSelfRegistration": true,
1281
+ "enableUnsecuredServiceSecrets": false,
1282
+ "googleAnalyticsID": null,
1283
+ "icon": null,
1284
+ "iconBackground": "#FFFFFF",
1285
+ "id": 1,
1286
+ "locked": "fullLock",
1287
+ "maskableIcon": null,
1288
+ "path": "updated-path",
1289
+ "scimEnabled": false,
1290
+ "scimToken": null,
1291
+ "sentryDsn": null,
1292
+ "sentryEnvironment": null,
1293
+ "sharedStyle": null,
1294
+ "showAppDefinition": true,
1295
+ "showAppsembleLogin": true,
1296
+ "showAppsembleOAuth2Login": true,
1297
+ "sslCertificate": null,
1298
+ "sslKey": null,
1299
+ "template": true,
1300
+ "updated": 1970-01-01T00:00:00.000Z,
1301
+ "vapidPrivateKey": "b",
1302
+ "vapidPublicKey": "a",
1303
+ "visibility": "private",
1304
+ }
1305
+ `);
1306
+ });
1307
+ it.each `
1308
+ key | from | to
1309
+ ${'demoMode'} | ${false} | ${true}
1310
+ ${'demoMode'} | ${true} | ${false}
1311
+ ${'enableSelfRegistration'} | ${false} | ${true}
1312
+ ${'enableSelfRegistration'} | ${true} | ${false}
1313
+ ${'iconBackground'} | ${''} | ${'#FFFFFF'}
1314
+ ${'path'} | ${'before'} | ${'after'}
1315
+ ${'locked'} | ${'unlocked'} | ${'studioLock'}
1316
+ ${'locked'} | ${'unlocked'} | ${'fullLock'}
1317
+ ${'locked'} | ${'fullLock'} | ${'unlocked'}
1318
+ ${'showAppDefinition'} | ${false} | ${true}
1319
+ ${'showAppDefinition'} | ${true} | ${false}
1320
+ ${'showAppsembleLogin'} | ${false} | ${true}
1321
+ ${'showAppsembleLogin'} | ${true} | ${false}
1322
+ ${'showAppsembleOAuth2Login'} | ${false} | ${true}
1323
+ ${'showAppsembleOAuth2Login'} | ${true} | ${false}
1324
+ ${'template'} | ${false} | ${true}
1325
+ ${'template'} | ${true} | ${false}
1326
+ ${'visibility'} | ${'public'} | ${'unlisted'}
1327
+ ${'visibility'} | ${'public'} | ${'private'}
1328
+ ${'visibility'} | ${'private'} | ${'public'}
1329
+ `('should patch $key from `$from` to `$to`', async ({ from, key, to }) => {
1330
+ const app = await App.create({
1331
+ path: 'test-app',
1332
+ definition: { name: 'Test App', defaultPage: 'Test Page' },
1333
+ vapidPublicKey: 'a',
1334
+ vapidPrivateKey: 'b',
1335
+ visibility: 'public',
1336
+ OrganizationId: organization.id,
1337
+ [key]: from,
1338
+ }, { raw: true });
1339
+ await authorizeCLI('apps:write');
1340
+ await patchApp({
1341
+ [key]: to,
1342
+ remote: testApp.defaults.baseURL,
1343
+ id: app.id,
1344
+ });
1345
+ expect(app.dataValues[key]).toStrictEqual(from);
1346
+ await app.reload();
1347
+ expect(app.dataValues[key]).toStrictEqual(to);
1348
+ });
1349
+ });
1350
+ //# sourceMappingURL=app.test.js.map