@appsemble/cli 0.27.10 → 0.27.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/app.test.js CHANGED
@@ -1,11 +1,12 @@
1
- import { authenticate } from '@appsemble/node-utils';
1
+ import { authenticate, readFixture, resolveFixture } from '@appsemble/node-utils';
2
2
  import { authorizeClientCredentials, createServer, createTestUser, models, setArgv, useTestDatabase, } from '@appsemble/server';
3
+ import { ISODateTimePattern } from '@appsemble/utils';
3
4
  import { setTestApp } from 'axios-test-instance';
4
5
  import { hash } from 'bcrypt';
5
6
  import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';
6
- import { patchApp } from './app.js';
7
+ import { patchApp, publishApp, updateApp } from './app.js';
7
8
  import { initAxios } from './initAxios.js';
8
- const { App, Organization, OrganizationMember } = models;
9
+ const { App, AppBlockStyle, AppCollection, AppCollectionApp, AppMessages, AppOAuth2Secret, AppSamlSecret, AppScreenshot, AppServiceSecret, AppVariable, Asset, BlockVersion, Organization, OrganizationMember, Resource, } = models;
9
10
  const argv = { host: 'http://localhost', secret: 'test', aesSecret: 'testSecret' };
10
11
  let user;
11
12
  let organization;
@@ -15,6 +16,7 @@ async function authorizeCLI(scopes) {
15
16
  const { id, secret } = OAuth2AuthorizationCode;
16
17
  await OAuth2AuthorizationCode.update({ secret: await hash(secret, 10) });
17
18
  await authenticate(testApp.defaults.baseURL, scopes, `${id}:${secret}`);
19
+ return `${id}:${secret}`;
18
20
  }
19
21
  useTestDatabase(import.meta);
20
22
  beforeAll(() => {
@@ -37,10 +39,1467 @@ beforeEach(async () => {
37
39
  UserId: user.id,
38
40
  role: 'Owner',
39
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
+ });
40
56
  });
41
57
  afterAll(() => {
42
58
  vi.useRealTimers();
43
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
+ it('should publish app variables and secrets', async () => {
637
+ vi.useRealTimers();
638
+ const clientCredentials = await authorizeCLI('apps:write resources:write');
639
+ process.env.TEST_VARIABLE_1 = 'test-variable-1';
640
+ process.env.TEST_VARIABLE_2 = 'test-variable-2';
641
+ process.env.TEST_SECRET = 'test-secret';
642
+ process.env.TEST_SERVICE_URL_PATTERNS = 'http://localhost:1234';
643
+ process.env.TEST_SERVICE_IDENTIFIER = 'Authorization';
644
+ process.env.TEST_SERVICE_SECRET = 'secret';
645
+ process.env.TEST_SAML_IDP_CERTIFICATE = 'certificate';
646
+ process.env.TEST_SAML_ENTITY_ID = 'http://localhost:1234';
647
+ process.env.TEST_SAML_SSO_URL = 'http://localhost:1234';
648
+ process.env.TEST_OAUTH2_AUTHORIZATION_URL = 'http://localhost:1234';
649
+ process.env.TEST_OAUTH2_TOKEN_URL = 'http://localhost:1234';
650
+ process.env.TEST_OAUTH2_USER_INFO_URL = 'http://localhost:1234';
651
+ process.env.TEST_OAUTH2_CLIENT_ID = 'id';
652
+ process.env.TEST_OAUTH2_CLIENT_SECRET = 'secret';
653
+ process.env.TEST_SCIM_TOKEN = 'token';
654
+ process.env.TEST_SSL_KEY = 'key';
655
+ process.env.TEST_SSL_CERTIFICATE = 'certificate';
656
+ await publishApp({
657
+ path: resolveFixture('apps/test'),
658
+ organization: organization.id,
659
+ remote: testApp.defaults.baseURL,
660
+ clientCredentials,
661
+ visibility: 'unlisted',
662
+ iconBackground: '#ffffff',
663
+ });
664
+ vi.useFakeTimers();
665
+ const app = await App.findOne({
666
+ attributes: ['scimEnabled', 'scimToken', 'sslKey', 'sslCertificate'],
667
+ where: {
668
+ id: 1,
669
+ },
670
+ });
671
+ expect({
672
+ scimEnabled: app.scimEnabled,
673
+ scimToken: app.scimToken,
674
+ sslKey: app.sslKey,
675
+ sslCertificate: app.sslCertificate,
676
+ }).toStrictEqual({
677
+ scimEnabled: true,
678
+ scimToken: expect.any(Buffer),
679
+ sslKey: 'key',
680
+ sslCertificate: 'certificate',
681
+ });
682
+ const appVariables = await AppVariable.findAll({
683
+ attributes: ['name', 'value'],
684
+ where: {
685
+ AppId: 1,
686
+ },
687
+ });
688
+ expect(appVariables[0].toJSON()).toStrictEqual({
689
+ name: 'test-variable-inline',
690
+ value: 'inline',
691
+ });
692
+ expect(appVariables[1].toJSON()).toStrictEqual({
693
+ name: 'test-variable',
694
+ value: 'test-variable-1.test-variable-2',
695
+ });
696
+ const appServiceSecret = await AppServiceSecret.findOne({
697
+ attributes: ['name', 'authenticationMethod', 'urlPatterns', 'identifier', 'secret'],
698
+ where: {
699
+ AppId: 1,
700
+ },
701
+ });
702
+ expect(appServiceSecret.toJSON()).toStrictEqual({
703
+ name: 'test-service-secret',
704
+ authenticationMethod: 'custom-header',
705
+ urlPatterns: 'http://localhost:1234',
706
+ identifier: 'Authorization',
707
+ secret: expect.any(Buffer),
708
+ });
709
+ const appSamlSecret = await AppSamlSecret.findOne({
710
+ attributes: [
711
+ 'name',
712
+ 'icon',
713
+ 'nameAttribute',
714
+ 'emailAttribute',
715
+ 'idpCertificate',
716
+ 'entityId',
717
+ 'ssoUrl',
718
+ ],
719
+ where: {
720
+ AppId: 1,
721
+ },
722
+ });
723
+ expect(appSamlSecret.toJSON()).toStrictEqual({
724
+ emailAttribute: 'email',
725
+ entityId: 'http://localhost:1234',
726
+ icon: 'redhat',
727
+ id: 1,
728
+ idpCertificate: 'certificate',
729
+ name: 'test-saml-secret',
730
+ nameAttribute: 'name',
731
+ ssoUrl: 'http://localhost:1234',
732
+ spCertificate: expect.any(String),
733
+ });
734
+ const appOauth2Secret = await AppOAuth2Secret.findOne({
735
+ attributes: [
736
+ 'name',
737
+ 'icon',
738
+ 'scope',
739
+ 'remapper',
740
+ 'authorizationUrl',
741
+ 'tokenUrl',
742
+ 'userInfoUrl',
743
+ 'clientId',
744
+ 'clientSecret',
745
+ ],
746
+ where: {
747
+ AppId: 1,
748
+ },
749
+ });
750
+ expect({
751
+ authorizationUrl: appOauth2Secret.authorizationUrl,
752
+ clientId: appOauth2Secret.clientId,
753
+ clientSecret: appOauth2Secret.clientSecret,
754
+ icon: appOauth2Secret.icon,
755
+ name: appOauth2Secret.name,
756
+ remapper: appOauth2Secret.remapper,
757
+ scope: appOauth2Secret.scope,
758
+ tokenUrl: appOauth2Secret.tokenUrl,
759
+ userInfoUrl: appOauth2Secret.userInfoUrl,
760
+ }).toStrictEqual({
761
+ authorizationUrl: 'http://localhost:1234',
762
+ clientId: 'id',
763
+ clientSecret: 'secret',
764
+ icon: 'redhat',
765
+ name: 'test-oauth2-secret',
766
+ remapper: [{ prop: 'email' }],
767
+ scope: 'email openid profile',
768
+ tokenUrl: 'http://localhost:1234',
769
+ userInfoUrl: 'http://localhost:1234',
770
+ });
771
+ });
772
+ });
773
+ describe('updateApp', () => {
774
+ let app;
775
+ beforeEach(async () => {
776
+ app = await App.create({
777
+ path: 'test-app',
778
+ definition: { name: 'Test App', defaultPage: 'Test Page' },
779
+ vapidPublicKey: 'a',
780
+ vapidPrivateKey: 'b',
781
+ visibility: 'public',
782
+ OrganizationId: organization.id,
783
+ });
784
+ });
785
+ it('should update app', async () => {
786
+ vi.useRealTimers();
787
+ const clientCredentials = await authorizeCLI('apps:write resources:write');
788
+ await updateApp({
789
+ id: app.id,
790
+ path: resolveFixture('apps/test'),
791
+ remote: testApp.defaults.baseURL,
792
+ clientCredentials,
793
+ // Required defaults
794
+ visibility: 'unlisted',
795
+ iconBackground: '#ffffff',
796
+ force: false,
797
+ });
798
+ vi.useFakeTimers();
799
+ await app.reload();
800
+ expect(app.toJSON()).toMatchInlineSnapshot({
801
+ $created: expect.stringMatching(ISODateTimePattern),
802
+ $updated: expect.stringMatching(ISODateTimePattern),
803
+ iconUrl: expect.any(String),
804
+ }, `
805
+ {
806
+ "$created": StringMatching /\\\\d\\{4\\}\\(\\.\\\\d\\{2\\}\\)\\{2\\}\\(\\\\s\\|T\\)\\(\\\\d\\{2\\}\\.\\)\\{2\\}\\\\d\\{2\\}/,
807
+ "$updated": StringMatching /\\\\d\\{4\\}\\(\\.\\\\d\\{2\\}\\)\\{2\\}\\(\\\\s\\|T\\)\\(\\\\d\\{2\\}\\.\\)\\{2\\}\\\\d\\{2\\}/,
808
+ "OrganizationId": "testorganization",
809
+ "OrganizationName": undefined,
810
+ "controllerCode": null,
811
+ "controllerImplementations": null,
812
+ "coreStyle": ".tux {
813
+ color: rgb(0 0 0);
814
+ }
815
+ ",
816
+ "definition": {
817
+ "defaultPage": "Test Page",
818
+ "name": "Test App",
819
+ "pages": [
820
+ {
821
+ "blocks": [
822
+ {
823
+ "type": "test",
824
+ "version": "0.0.0",
825
+ },
826
+ ],
827
+ "name": "Test Page",
828
+ },
829
+ ],
830
+ "resources": {
831
+ "test": {
832
+ "schema": {
833
+ "additionalProperties": false,
834
+ "properties": {
835
+ "test": {
836
+ "format": "binary",
837
+ "type": "string",
838
+ },
839
+ },
840
+ "type": "object",
841
+ },
842
+ },
843
+ },
844
+ },
845
+ "demoMode": false,
846
+ "domain": null,
847
+ "emailName": null,
848
+ "enableSelfRegistration": true,
849
+ "enableUnsecuredServiceSecrets": false,
850
+ "googleAnalyticsID": null,
851
+ "hasClonableAssets": undefined,
852
+ "hasClonableResources": undefined,
853
+ "hasIcon": true,
854
+ "hasMaskableIcon": true,
855
+ "iconBackground": "#ffffff",
856
+ "iconUrl": Any<String>,
857
+ "id": 1,
858
+ "locked": "unlocked",
859
+ "messages": undefined,
860
+ "path": "test-app",
861
+ "rating": undefined,
862
+ "readmeUrl": undefined,
863
+ "screenshotUrls": undefined,
864
+ "sentryDsn": null,
865
+ "sentryEnvironment": null,
866
+ "sharedStyle": ".tux {
867
+ color: rgb(0 0 0);
868
+ }
869
+ ",
870
+ "showAppDefinition": false,
871
+ "showAppsembleLogin": false,
872
+ "showAppsembleOAuth2Login": true,
873
+ "visibility": "unlisted",
874
+ "yaml": "name: Test App
875
+ defaultPage: Test Page
876
+ resources:
877
+ test:
878
+ schema:
879
+ additionalProperties: false
880
+ type: object
881
+ properties:
882
+ test:
883
+ type: string
884
+ format: binary
885
+ pages:
886
+ - name: Test Page
887
+ blocks:
888
+ - type: test
889
+ version: 0.0.0
890
+ ",
891
+ }
892
+ `);
893
+ expect(app.icon).toStrictEqual(await readFixture('apps/test/icon.png'));
894
+ expect(app.maskableIcon).toStrictEqual(await readFixture('apps/test/maskable-icon.png'));
895
+ const appBlockStyle = await AppBlockStyle.findOne();
896
+ expect(appBlockStyle.style).toBe(`.tux {
897
+ color: rgb(0 0 0);
898
+ }`);
899
+ const appScreenshot = await AppScreenshot.findOne();
900
+ expect(appScreenshot.screenshot).toStrictEqual(await readFixture('apps/test/screenshots/test_en-us.png'));
901
+ const appMessages = await AppMessages.findAll({ order: [['language', 'DESC']] });
902
+ expect(appMessages.map(({ language, messages }) => [language, messages])).toStrictEqual([
903
+ ['nl', JSON.parse(String(await readFixture('apps/test/i18n/nl.json')))],
904
+ ['en', JSON.parse(String(await readFixture('apps/test/i18n/en.json')))],
905
+ ]);
906
+ const resource = await Resource.findAll();
907
+ expect(resource).toStrictEqual([]);
908
+ const asset = await Asset.findAll();
909
+ expect(asset).toStrictEqual([]);
910
+ });
911
+ it('should update app with resources and assets', async () => {
912
+ vi.useRealTimers();
913
+ const clientCredentials = await authorizeCLI('apps:write resources:write');
914
+ await updateApp({
915
+ id: app.id,
916
+ path: resolveFixture('apps/test'),
917
+ remote: testApp.defaults.baseURL,
918
+ clientCredentials,
919
+ // Required defaults
920
+ visibility: 'unlisted',
921
+ iconBackground: '#ffffff',
922
+ force: false,
923
+ // Additional
924
+ resources: true,
925
+ assets: true,
926
+ });
927
+ vi.useFakeTimers();
928
+ await app.reload();
929
+ expect(app.toJSON()).toMatchInlineSnapshot({
930
+ $created: expect.stringMatching(ISODateTimePattern),
931
+ $updated: expect.stringMatching(ISODateTimePattern),
932
+ iconUrl: expect.any(String),
933
+ }, `
934
+ {
935
+ "$created": StringMatching /\\\\d\\{4\\}\\(\\.\\\\d\\{2\\}\\)\\{2\\}\\(\\\\s\\|T\\)\\(\\\\d\\{2\\}\\.\\)\\{2\\}\\\\d\\{2\\}/,
936
+ "$updated": StringMatching /\\\\d\\{4\\}\\(\\.\\\\d\\{2\\}\\)\\{2\\}\\(\\\\s\\|T\\)\\(\\\\d\\{2\\}\\.\\)\\{2\\}\\\\d\\{2\\}/,
937
+ "OrganizationId": "testorganization",
938
+ "OrganizationName": undefined,
939
+ "controllerCode": null,
940
+ "controllerImplementations": null,
941
+ "coreStyle": ".tux {
942
+ color: rgb(0 0 0);
943
+ }
944
+ ",
945
+ "definition": {
946
+ "defaultPage": "Test Page",
947
+ "name": "Test App",
948
+ "pages": [
949
+ {
950
+ "blocks": [
951
+ {
952
+ "type": "test",
953
+ "version": "0.0.0",
954
+ },
955
+ ],
956
+ "name": "Test Page",
957
+ },
958
+ ],
959
+ "resources": {
960
+ "test": {
961
+ "schema": {
962
+ "additionalProperties": false,
963
+ "properties": {
964
+ "test": {
965
+ "format": "binary",
966
+ "type": "string",
967
+ },
968
+ },
969
+ "type": "object",
970
+ },
971
+ },
972
+ },
973
+ },
974
+ "demoMode": false,
975
+ "domain": null,
976
+ "emailName": null,
977
+ "enableSelfRegistration": true,
978
+ "enableUnsecuredServiceSecrets": false,
979
+ "googleAnalyticsID": null,
980
+ "hasClonableAssets": undefined,
981
+ "hasClonableResources": undefined,
982
+ "hasIcon": true,
983
+ "hasMaskableIcon": true,
984
+ "iconBackground": "#ffffff",
985
+ "iconUrl": Any<String>,
986
+ "id": 1,
987
+ "locked": "unlocked",
988
+ "messages": undefined,
989
+ "path": "test-app",
990
+ "rating": undefined,
991
+ "readmeUrl": undefined,
992
+ "screenshotUrls": undefined,
993
+ "sentryDsn": null,
994
+ "sentryEnvironment": null,
995
+ "sharedStyle": ".tux {
996
+ color: rgb(0 0 0);
997
+ }
998
+ ",
999
+ "showAppDefinition": false,
1000
+ "showAppsembleLogin": false,
1001
+ "showAppsembleOAuth2Login": true,
1002
+ "visibility": "unlisted",
1003
+ "yaml": "name: Test App
1004
+ defaultPage: Test Page
1005
+ resources:
1006
+ test:
1007
+ schema:
1008
+ additionalProperties: false
1009
+ type: object
1010
+ properties:
1011
+ test:
1012
+ type: string
1013
+ format: binary
1014
+ pages:
1015
+ - name: Test Page
1016
+ blocks:
1017
+ - type: test
1018
+ version: 0.0.0
1019
+ ",
1020
+ }
1021
+ `);
1022
+ expect(app.icon).toStrictEqual(await readFixture('apps/test/icon.png'));
1023
+ expect(app.maskableIcon).toStrictEqual(await readFixture('apps/test/maskable-icon.png'));
1024
+ const appBlockStyle = await AppBlockStyle.findOne();
1025
+ expect(appBlockStyle.style).toBe(`.tux {
1026
+ color: rgb(0 0 0);
1027
+ }`);
1028
+ const appScreenshot = await AppScreenshot.findOne();
1029
+ expect(appScreenshot.screenshot).toStrictEqual(await readFixture('apps/test/screenshots/test_en-us.png'));
1030
+ const appMessages = await AppMessages.findAll({ order: [['language', 'DESC']] });
1031
+ expect(appMessages.map(({ language, messages }) => [language, messages])).toStrictEqual([
1032
+ ['nl', JSON.parse(String(await readFixture('apps/test/i18n/nl.json')))],
1033
+ ['en', JSON.parse(String(await readFixture('apps/test/i18n/en.json')))],
1034
+ ]);
1035
+ const resources = await Resource.findAll();
1036
+ expect(resources.map((r) => r.toJSON())).toMatchInlineSnapshot([
1037
+ {
1038
+ $created: expect.stringMatching(ISODateTimePattern),
1039
+ $updated: expect.stringMatching(ISODateTimePattern),
1040
+ },
1041
+ ], `
1042
+ [
1043
+ {
1044
+ "$created": StringMatching /\\\\d\\{4\\}\\(\\.\\\\d\\{2\\}\\)\\{2\\}\\(\\\\s\\|T\\)\\(\\\\d\\{2\\}\\.\\)\\{2\\}\\\\d\\{2\\}/,
1045
+ "$updated": StringMatching /\\\\d\\{4\\}\\(\\.\\\\d\\{2\\}\\)\\{2\\}\\(\\\\s\\|T\\)\\(\\\\d\\{2\\}\\.\\)\\{2\\}\\\\d\\{2\\}/,
1046
+ "id": 1,
1047
+ "test": "tux",
1048
+ },
1049
+ ]
1050
+ `);
1051
+ const assets = await Asset.findAll();
1052
+ expect(assets.map((a) => a.data)).toStrictEqual([
1053
+ await readFixture('apps/test/assets/tux.png'),
1054
+ ]);
1055
+ });
1056
+ it('should update app with runtime config', async () => {
1057
+ await AppCollection.create({
1058
+ name: 'test',
1059
+ expertName: 'Expert',
1060
+ expertProfileImage: Buffer.alloc(0),
1061
+ expertProfileImageMimeType: 'image/png',
1062
+ headerImage: Buffer.alloc(0),
1063
+ headerImageMimeType: 'image/png',
1064
+ OrganizationId: organization.id,
1065
+ visibility: 'public',
1066
+ });
1067
+ vi.useRealTimers();
1068
+ const clientCredentials = await authorizeCLI('apps:write resources:write');
1069
+ await updateApp({
1070
+ path: resolveFixture('apps/test'),
1071
+ id: app.id,
1072
+ remote: testApp.defaults.baseURL,
1073
+ clientCredentials,
1074
+ // Required defaults
1075
+ visibility: 'unlisted',
1076
+ iconBackground: '#ffffff',
1077
+ force: false,
1078
+ // Define context
1079
+ context: 'test',
1080
+ });
1081
+ vi.useFakeTimers();
1082
+ await app.reload();
1083
+ expect(app.toJSON()).toMatchInlineSnapshot({
1084
+ $created: expect.stringMatching(ISODateTimePattern),
1085
+ $updated: expect.stringMatching(ISODateTimePattern),
1086
+ iconUrl: expect.any(String),
1087
+ }, `
1088
+ {
1089
+ "$created": StringMatching /\\\\d\\{4\\}\\(\\.\\\\d\\{2\\}\\)\\{2\\}\\(\\\\s\\|T\\)\\(\\\\d\\{2\\}\\.\\)\\{2\\}\\\\d\\{2\\}/,
1090
+ "$updated": StringMatching /\\\\d\\{4\\}\\(\\.\\\\d\\{2\\}\\)\\{2\\}\\(\\\\s\\|T\\)\\(\\\\d\\{2\\}\\.\\)\\{2\\}\\\\d\\{2\\}/,
1091
+ "OrganizationId": "testorganization",
1092
+ "OrganizationName": undefined,
1093
+ "controllerCode": null,
1094
+ "controllerImplementations": null,
1095
+ "coreStyle": ".tux {
1096
+ color: rgb(0 0 0);
1097
+ }
1098
+ ",
1099
+ "definition": {
1100
+ "defaultPage": "Test Page",
1101
+ "name": "Test App",
1102
+ "pages": [
1103
+ {
1104
+ "blocks": [
1105
+ {
1106
+ "type": "test",
1107
+ "version": "0.0.0",
1108
+ },
1109
+ ],
1110
+ "name": "Test Page",
1111
+ },
1112
+ ],
1113
+ "resources": {
1114
+ "test": {
1115
+ "schema": {
1116
+ "additionalProperties": false,
1117
+ "properties": {
1118
+ "test": {
1119
+ "format": "binary",
1120
+ "type": "string",
1121
+ },
1122
+ },
1123
+ "type": "object",
1124
+ },
1125
+ },
1126
+ },
1127
+ },
1128
+ "demoMode": true,
1129
+ "domain": null,
1130
+ "emailName": null,
1131
+ "enableSelfRegistration": true,
1132
+ "enableUnsecuredServiceSecrets": false,
1133
+ "googleAnalyticsID": "test",
1134
+ "hasClonableAssets": undefined,
1135
+ "hasClonableResources": undefined,
1136
+ "hasIcon": true,
1137
+ "hasMaskableIcon": true,
1138
+ "iconBackground": "#000000",
1139
+ "iconUrl": Any<String>,
1140
+ "id": 1,
1141
+ "locked": "studioLock",
1142
+ "messages": undefined,
1143
+ "path": "test-app",
1144
+ "rating": undefined,
1145
+ "readmeUrl": undefined,
1146
+ "screenshotUrls": undefined,
1147
+ "sentryDsn": "https://public@sentry.example.com/1",
1148
+ "sentryEnvironment": "test",
1149
+ "sharedStyle": ".tux {
1150
+ color: rgb(0 0 0);
1151
+ }
1152
+ ",
1153
+ "showAppDefinition": false,
1154
+ "showAppsembleLogin": false,
1155
+ "showAppsembleOAuth2Login": true,
1156
+ "visibility": "public",
1157
+ "yaml": "name: Test App
1158
+ defaultPage: Test Page
1159
+ resources:
1160
+ test:
1161
+ schema:
1162
+ additionalProperties: false
1163
+ type: object
1164
+ properties:
1165
+ test:
1166
+ type: string
1167
+ format: binary
1168
+ pages:
1169
+ - name: Test Page
1170
+ blocks:
1171
+ - type: test
1172
+ version: 0.0.0
1173
+ ",
1174
+ }
1175
+ `);
1176
+ expect(app.icon).toStrictEqual(await readFixture('apps/test/icon.png'));
1177
+ expect(app.maskableIcon).toStrictEqual(await readFixture('apps/test/maskable-icon.png'));
1178
+ const appBlockStyle = await AppBlockStyle.findOne();
1179
+ expect(appBlockStyle.style).toBe(`.tux {
1180
+ color: rgb(0 0 0);
1181
+ }`);
1182
+ const appScreenshot = await AppScreenshot.findOne();
1183
+ expect(appScreenshot.screenshot).toStrictEqual(await readFixture('apps/test/screenshots/test_en-us.png'));
1184
+ const appMessages = await AppMessages.findAll({ order: [['language', 'DESC']] });
1185
+ expect(appMessages.map(({ language, messages }) => [language, messages])).toStrictEqual([
1186
+ ['nl', JSON.parse(String(await readFixture('apps/test/i18n/nl.json')))],
1187
+ ['en', JSON.parse(String(await readFixture('apps/test/i18n/en.json')))],
1188
+ ]);
1189
+ const resources = await Resource.findAll();
1190
+ expect(resources.map((r) => r.toJSON())).toMatchInlineSnapshot([
1191
+ {
1192
+ $created: expect.stringMatching(ISODateTimePattern),
1193
+ $updated: expect.stringMatching(ISODateTimePattern),
1194
+ },
1195
+ {
1196
+ $created: expect.stringMatching(ISODateTimePattern),
1197
+ $updated: expect.stringMatching(ISODateTimePattern),
1198
+ },
1199
+ ], `
1200
+ [
1201
+ {
1202
+ "$created": StringMatching /\\\\d\\{4\\}\\(\\.\\\\d\\{2\\}\\)\\{2\\}\\(\\\\s\\|T\\)\\(\\\\d\\{2\\}\\.\\)\\{2\\}\\\\d\\{2\\}/,
1203
+ "$updated": StringMatching /\\\\d\\{4\\}\\(\\.\\\\d\\{2\\}\\)\\{2\\}\\(\\\\s\\|T\\)\\(\\\\d\\{2\\}\\.\\)\\{2\\}\\\\d\\{2\\}/,
1204
+ "id": 1,
1205
+ "test": "tux",
1206
+ },
1207
+ {
1208
+ "$created": StringMatching /\\\\d\\{4\\}\\(\\.\\\\d\\{2\\}\\)\\{2\\}\\(\\\\s\\|T\\)\\(\\\\d\\{2\\}\\.\\)\\{2\\}\\\\d\\{2\\}/,
1209
+ "$ephemeral": true,
1210
+ "$updated": StringMatching /\\\\d\\{4\\}\\(\\.\\\\d\\{2\\}\\)\\{2\\}\\(\\\\s\\|T\\)\\(\\\\d\\{2\\}\\.\\)\\{2\\}\\\\d\\{2\\}/,
1211
+ "id": 2,
1212
+ "test": "tux",
1213
+ },
1214
+ ]
1215
+ `);
1216
+ const assets = await Asset.findAll();
1217
+ expect(assets.map((a) => a.data)).toStrictEqual([
1218
+ await readFixture('apps/test/assets/tux.png'),
1219
+ await readFixture('apps/test/assets/tux.png'),
1220
+ ]);
1221
+ // TODO: not yet implemented
1222
+ // const appCollectionApp = await AppCollectionApp.findOne();
1223
+ // expect(appCollectionApp.AppId).toBe(1);
1224
+ // expect(appCollectionApp.AppCollectionId).toBe(1);
1225
+ });
1226
+ it('should update app with app variant patches applied', async () => {
1227
+ vi.useRealTimers();
1228
+ const clientCredentials = await authorizeCLI('apps:write resources:write');
1229
+ await updateApp({
1230
+ path: resolveFixture('apps/test'),
1231
+ id: app.id,
1232
+ remote: testApp.defaults.baseURL,
1233
+ clientCredentials,
1234
+ // Required defaults
1235
+ visibility: 'unlisted',
1236
+ iconBackground: '#ffffff',
1237
+ force: false,
1238
+ // Define app variant
1239
+ variant: 'tux',
1240
+ resources: true,
1241
+ assets: true,
1242
+ });
1243
+ vi.useFakeTimers();
1244
+ await app.reload();
1245
+ expect(app.toJSON()).toMatchInlineSnapshot({
1246
+ $created: expect.stringMatching(ISODateTimePattern),
1247
+ $updated: expect.stringMatching(ISODateTimePattern),
1248
+ iconUrl: expect.any(String),
1249
+ }, `
1250
+ {
1251
+ "$created": StringMatching /\\\\d\\{4\\}\\(\\.\\\\d\\{2\\}\\)\\{2\\}\\(\\\\s\\|T\\)\\(\\\\d\\{2\\}\\.\\)\\{2\\}\\\\d\\{2\\}/,
1252
+ "$updated": StringMatching /\\\\d\\{4\\}\\(\\.\\\\d\\{2\\}\\)\\{2\\}\\(\\\\s\\|T\\)\\(\\\\d\\{2\\}\\.\\)\\{2\\}\\\\d\\{2\\}/,
1253
+ "OrganizationId": "testorganization",
1254
+ "OrganizationName": undefined,
1255
+ "controllerCode": null,
1256
+ "controllerImplementations": null,
1257
+ "coreStyle": ".tux{color:rgb(1 2 3)}",
1258
+ "definition": {
1259
+ "defaultPage": "Test Page",
1260
+ "name": "Test App",
1261
+ "pages": [
1262
+ {
1263
+ "blocks": [
1264
+ {
1265
+ "type": "test",
1266
+ "version": "0.0.0",
1267
+ },
1268
+ ],
1269
+ "name": "Test Page",
1270
+ },
1271
+ ],
1272
+ "resources": {
1273
+ "test": {
1274
+ "schema": {
1275
+ "additionalProperties": false,
1276
+ "properties": {
1277
+ "tux": {
1278
+ "format": "binary",
1279
+ "type": "string",
1280
+ },
1281
+ },
1282
+ "type": "object",
1283
+ },
1284
+ },
1285
+ },
1286
+ },
1287
+ "demoMode": false,
1288
+ "domain": null,
1289
+ "emailName": null,
1290
+ "enableSelfRegistration": true,
1291
+ "enableUnsecuredServiceSecrets": false,
1292
+ "googleAnalyticsID": null,
1293
+ "hasClonableAssets": undefined,
1294
+ "hasClonableResources": undefined,
1295
+ "hasIcon": true,
1296
+ "hasMaskableIcon": true,
1297
+ "iconBackground": "#ffffff",
1298
+ "iconUrl": Any<String>,
1299
+ "id": 1,
1300
+ "locked": "unlocked",
1301
+ "messages": undefined,
1302
+ "path": "test-app",
1303
+ "rating": undefined,
1304
+ "readmeUrl": undefined,
1305
+ "screenshotUrls": undefined,
1306
+ "sentryDsn": null,
1307
+ "sentryEnvironment": null,
1308
+ "sharedStyle": ".tux{color:rgb(1 2 3)}",
1309
+ "showAppDefinition": false,
1310
+ "showAppsembleLogin": false,
1311
+ "showAppsembleOAuth2Login": true,
1312
+ "visibility": "unlisted",
1313
+ "yaml": "name: Test App
1314
+ defaultPage: Test Page
1315
+ resources:
1316
+ test:
1317
+ schema:
1318
+ additionalProperties: false
1319
+ type: object
1320
+ properties:
1321
+ tux:
1322
+ type: string
1323
+ format: binary
1324
+ pages:
1325
+ - name: Test Page
1326
+ blocks:
1327
+ - type: test
1328
+ version: 0.0.0
1329
+ ",
1330
+ }
1331
+ `);
1332
+ expect(app.icon).toStrictEqual(await readFixture('apps/test/variants/tux/icon.png'));
1333
+ expect(app.maskableIcon).toStrictEqual(await readFixture('apps/test/variants/tux/maskable-icon.png'));
1334
+ const appBlockStyle = await AppBlockStyle.findOne();
1335
+ expect(appBlockStyle.style).toBe('.tux{color:rgb(1 2 3)}');
1336
+ const appScreenshot = await AppScreenshot.findOne();
1337
+ expect(appScreenshot.screenshot).toStrictEqual(await readFixture('apps/test/variants/tux/screenshots/tux.png'));
1338
+ const appMessages = await AppMessages.findAll({ order: [['language', 'DESC']] });
1339
+ const messages = JSON.parse(String(await readFixture('apps/test/variants/tux/patches/messages.json')));
1340
+ expect(appMessages.map(({ language, messages: msgs }) => [language, msgs])).toStrictEqual([
1341
+ ['nl', messages.nl],
1342
+ ['en', messages.en],
1343
+ ]);
1344
+ const resources = await Resource.findAll();
1345
+ expect(resources.map((r) => r.toJSON())).toMatchInlineSnapshot([
1346
+ {
1347
+ $created: expect.stringMatching(ISODateTimePattern),
1348
+ $updated: expect.stringMatching(ISODateTimePattern),
1349
+ },
1350
+ ], `
1351
+ [
1352
+ {
1353
+ "$created": StringMatching /\\\\d\\{4\\}\\(\\.\\\\d\\{2\\}\\)\\{2\\}\\(\\\\s\\|T\\)\\(\\\\d\\{2\\}\\.\\)\\{2\\}\\\\d\\{2\\}/,
1354
+ "$updated": StringMatching /\\\\d\\{4\\}\\(\\.\\\\d\\{2\\}\\)\\{2\\}\\(\\\\s\\|T\\)\\(\\\\d\\{2\\}\\.\\)\\{2\\}\\\\d\\{2\\}/,
1355
+ "id": 1,
1356
+ "tux": "small-tux",
1357
+ },
1358
+ ]
1359
+ `);
1360
+ const assets = await Asset.findAll();
1361
+ expect(assets.map((a) => a.data)).toStrictEqual([
1362
+ await readFixture('apps/test/variants/tux/assets/small-tux.png'),
1363
+ ]);
1364
+ });
1365
+ it('should update app variables and secrets', async () => {
1366
+ vi.useRealTimers();
1367
+ const clientCredentials = await authorizeCLI('apps:write resources:write');
1368
+ process.env.TEST_VARIABLE_1 = 'test-variable-1';
1369
+ process.env.TEST_VARIABLE_2 = 'test-variable-2';
1370
+ process.env.TEST_SECRET = 'test-secret';
1371
+ process.env.TEST_SERVICE_URL_PATTERNS = 'http://localhost:1234';
1372
+ process.env.TEST_SERVICE_IDENTIFIER = 'Authorization';
1373
+ process.env.TEST_SERVICE_SECRET = 'secret';
1374
+ process.env.TEST_SAML_IDP_CERTIFICATE = 'certificate';
1375
+ process.env.TEST_SAML_ENTITY_ID = 'http://localhost:1234';
1376
+ process.env.TEST_SAML_SSO_URL = 'http://localhost:1234';
1377
+ process.env.TEST_OAUTH2_AUTHORIZATION_URL = 'http://localhost:1234';
1378
+ process.env.TEST_OAUTH2_TOKEN_URL = 'http://localhost:1234';
1379
+ process.env.TEST_OAUTH2_USER_INFO_URL = 'http://localhost:1234';
1380
+ process.env.TEST_OAUTH2_CLIENT_ID = 'id';
1381
+ process.env.TEST_OAUTH2_CLIENT_SECRET = 'secret';
1382
+ process.env.TEST_SCIM_TOKEN = 'token';
1383
+ process.env.TEST_SSL_KEY = 'key';
1384
+ process.env.TEST_SSL_CERTIFICATE = 'certificate';
1385
+ await updateApp({
1386
+ id: 1,
1387
+ path: resolveFixture('apps/test'),
1388
+ remote: testApp.defaults.baseURL,
1389
+ clientCredentials,
1390
+ visibility: 'unlisted',
1391
+ iconBackground: '#ffffff',
1392
+ force: true,
1393
+ });
1394
+ vi.useFakeTimers();
1395
+ const updatedApp = await App.findOne({
1396
+ attributes: ['scimEnabled', 'scimToken', 'sslKey', 'sslCertificate'],
1397
+ where: {
1398
+ id: 1,
1399
+ },
1400
+ });
1401
+ expect({
1402
+ scimEnabled: updatedApp.scimEnabled,
1403
+ scimToken: updatedApp.scimToken,
1404
+ sslKey: updatedApp.sslKey,
1405
+ sslCertificate: updatedApp.sslCertificate,
1406
+ }).toStrictEqual({
1407
+ scimEnabled: true,
1408
+ scimToken: expect.any(Buffer),
1409
+ sslKey: 'key',
1410
+ sslCertificate: 'certificate',
1411
+ });
1412
+ const appVariables = await AppVariable.findAll({
1413
+ attributes: ['name', 'value'],
1414
+ where: {
1415
+ AppId: 1,
1416
+ },
1417
+ });
1418
+ expect(appVariables[0].toJSON()).toStrictEqual({
1419
+ name: 'test-variable-inline',
1420
+ value: 'inline',
1421
+ });
1422
+ expect(appVariables[1].toJSON()).toStrictEqual({
1423
+ name: 'test-variable',
1424
+ value: 'test-variable-1.test-variable-2',
1425
+ });
1426
+ const appServiceSecret = await AppServiceSecret.findOne({
1427
+ attributes: ['name', 'authenticationMethod', 'urlPatterns', 'identifier', 'secret'],
1428
+ where: {
1429
+ AppId: 1,
1430
+ },
1431
+ });
1432
+ expect(appServiceSecret.toJSON()).toStrictEqual({
1433
+ name: 'test-service-secret',
1434
+ authenticationMethod: 'custom-header',
1435
+ urlPatterns: 'http://localhost:1234',
1436
+ identifier: 'Authorization',
1437
+ secret: expect.any(Buffer),
1438
+ });
1439
+ const appSamlSecret = await AppSamlSecret.findOne({
1440
+ attributes: [
1441
+ 'name',
1442
+ 'icon',
1443
+ 'nameAttribute',
1444
+ 'emailAttribute',
1445
+ 'idpCertificate',
1446
+ 'entityId',
1447
+ 'ssoUrl',
1448
+ ],
1449
+ where: {
1450
+ AppId: 1,
1451
+ },
1452
+ });
1453
+ expect(appSamlSecret.toJSON()).toStrictEqual({
1454
+ emailAttribute: 'email',
1455
+ entityId: 'http://localhost:1234',
1456
+ icon: 'redhat',
1457
+ id: 1,
1458
+ idpCertificate: 'certificate',
1459
+ name: 'test-saml-secret',
1460
+ nameAttribute: 'name',
1461
+ ssoUrl: 'http://localhost:1234',
1462
+ spCertificate: expect.any(String),
1463
+ });
1464
+ const appOauth2Secret = await AppOAuth2Secret.findOne({
1465
+ attributes: [
1466
+ 'name',
1467
+ 'icon',
1468
+ 'scope',
1469
+ 'remapper',
1470
+ 'authorizationUrl',
1471
+ 'tokenUrl',
1472
+ 'userInfoUrl',
1473
+ 'clientId',
1474
+ 'clientSecret',
1475
+ ],
1476
+ where: {
1477
+ AppId: 1,
1478
+ },
1479
+ });
1480
+ expect({
1481
+ authorizationUrl: appOauth2Secret.authorizationUrl,
1482
+ clientId: appOauth2Secret.clientId,
1483
+ clientSecret: appOauth2Secret.clientSecret,
1484
+ icon: appOauth2Secret.icon,
1485
+ name: appOauth2Secret.name,
1486
+ remapper: appOauth2Secret.remapper,
1487
+ scope: appOauth2Secret.scope,
1488
+ tokenUrl: appOauth2Secret.tokenUrl,
1489
+ userInfoUrl: appOauth2Secret.userInfoUrl,
1490
+ }).toStrictEqual({
1491
+ authorizationUrl: 'http://localhost:1234',
1492
+ clientId: 'id',
1493
+ clientSecret: 'secret',
1494
+ icon: 'redhat',
1495
+ name: 'test-oauth2-secret',
1496
+ remapper: [{ prop: 'email' }],
1497
+ scope: 'email openid profile',
1498
+ tokenUrl: 'http://localhost:1234',
1499
+ userInfoUrl: 'http://localhost:1234',
1500
+ });
1501
+ });
1502
+ });
44
1503
  describe('patchApp', () => {
45
1504
  it('should apply patches to app', async () => {
46
1505
  const patches = {