@appsemble/cli 0.27.11 → 0.28.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/app.test.js CHANGED
@@ -1,23 +1,17 @@
1
- import { authenticate, readFixture, resolveFixture } from '@appsemble/node-utils';
2
- import { authorizeClientCredentials, createServer, createTestUser, models, setArgv, useTestDatabase, } from '@appsemble/server';
1
+ import { readFixture, resolveFixture } from '@appsemble/node-utils';
2
+ import { createServer, createTestUser, models, setArgv, useTestDatabase } from '@appsemble/server';
3
3
  import { ISODateTimePattern } from '@appsemble/utils';
4
4
  import { setTestApp } from 'axios-test-instance';
5
- import { hash } from 'bcrypt';
5
+ import FormData from 'form-data';
6
6
  import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';
7
- import { patchApp, publishApp, updateApp } from './app.js';
7
+ import { deleteApp, patchApp, publishApp, traverseAppDirectory, updateApp } from './app.js';
8
8
  import { initAxios } from './initAxios.js';
9
- const { App, AppBlockStyle, AppCollection, AppCollectionApp, AppMessages, AppScreenshot, Asset, BlockVersion, Organization, OrganizationMember, Resource, } = models;
9
+ import { authorizeCLI } from './testUtils.js';
10
+ const { App, AppBlockStyle, AppCollection, AppCollectionApp, AppMessages, AppOAuth2Secret, AppSamlSecret, AppScreenshot, AppServiceSecret, AppVariable, Asset, BlockVersion, Organization, OrganizationMember, Resource, } = models;
10
11
  const argv = { host: 'http://localhost', secret: 'test', aesSecret: 'testSecret' };
11
12
  let user;
12
13
  let organization;
13
14
  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
15
  useTestDatabase(import.meta);
22
16
  beforeAll(() => {
23
17
  vi.useFakeTimers();
@@ -60,7 +54,7 @@ afterAll(() => {
60
54
  describe('publishApp', () => {
61
55
  it('should publish app', async () => {
62
56
  vi.useRealTimers();
63
- const clientCredentials = await authorizeCLI('apps:write resources:write');
57
+ const clientCredentials = await authorizeCLI('apps:write resources:write', testApp);
64
58
  await publishApp({
65
59
  path: resolveFixture('apps/test'),
66
60
  organization: organization.id,
@@ -145,6 +139,7 @@ describe('publishApp', () => {
145
139
  "showAppDefinition": true,
146
140
  "showAppsembleLogin": false,
147
141
  "showAppsembleOAuth2Login": true,
142
+ "template": false,
148
143
  "visibility": "unlisted",
149
144
  "yaml": "name: Test App
150
145
  defaultPage: Test Page
@@ -183,9 +178,23 @@ describe('publishApp', () => {
183
178
  const asset = await Asset.findAll();
184
179
  expect(asset).toStrictEqual([]);
185
180
  });
181
+ it('should throw an error if the user doesn’t have enough scope permissions', async () => {
182
+ const clientCredentials = await authorizeCLI('blocks:write resources:write', testApp);
183
+ await expect(() => publishApp({
184
+ path: resolveFixture('apps/test'),
185
+ organization: organization.id,
186
+ remote: testApp.defaults.baseURL,
187
+ clientCredentials,
188
+ // Required defaults
189
+ visibility: 'unlisted',
190
+ iconBackground: '#ffffff',
191
+ })).rejects.toThrow('write EPIPE');
192
+ const app = await App.findOne();
193
+ expect(app).toBeNull();
194
+ });
186
195
  it('should publish app with resources and assets', async () => {
187
196
  vi.useRealTimers();
188
- const clientCredentials = await authorizeCLI('apps:write resources:write');
197
+ const clientCredentials = await authorizeCLI('apps:write resources:write', testApp);
189
198
  await publishApp({
190
199
  path: resolveFixture('apps/test'),
191
200
  organization: organization.id,
@@ -273,6 +282,7 @@ describe('publishApp', () => {
273
282
  "showAppDefinition": true,
274
283
  "showAppsembleLogin": false,
275
284
  "showAppsembleOAuth2Login": true,
285
+ "template": false,
276
286
  "visibility": "unlisted",
277
287
  "yaml": "name: Test App
278
288
  defaultPage: Test Page
@@ -339,7 +349,7 @@ describe('publishApp', () => {
339
349
  visibility: 'public',
340
350
  });
341
351
  vi.useRealTimers();
342
- const clientCredentials = await authorizeCLI('apps:write resources:write');
352
+ const clientCredentials = await authorizeCLI('apps:write resources:write', testApp);
343
353
  await publishApp({
344
354
  path: resolveFixture('apps/test'),
345
355
  organization: organization.id,
@@ -426,6 +436,7 @@ describe('publishApp', () => {
426
436
  "showAppDefinition": true,
427
437
  "showAppsembleLogin": false,
428
438
  "showAppsembleOAuth2Login": true,
439
+ "template": true,
429
440
  "visibility": "public",
430
441
  "yaml": "name: Test App
431
442
  defaultPage: Test Page
@@ -497,7 +508,7 @@ describe('publishApp', () => {
497
508
  });
498
509
  it('should publish app with app variant patches applied', async () => {
499
510
  vi.useRealTimers();
500
- const clientCredentials = await authorizeCLI('apps:write resources:write');
511
+ const clientCredentials = await authorizeCLI('apps:write resources:write', testApp);
501
512
  await publishApp({
502
513
  path: resolveFixture('apps/test'),
503
514
  organization: organization.id,
@@ -580,6 +591,7 @@ describe('publishApp', () => {
580
591
  "showAppDefinition": true,
581
592
  "showAppsembleLogin": false,
582
593
  "showAppsembleOAuth2Login": true,
594
+ "template": false,
583
595
  "visibility": "unlisted",
584
596
  "yaml": "name: Test App
585
597
  defaultPage: Test Page
@@ -633,6 +645,164 @@ describe('publishApp', () => {
633
645
  await readFixture('apps/test/variants/tux/assets/small-tux.png'),
634
646
  ]);
635
647
  });
648
+ it('should publish app variables and secrets', async () => {
649
+ vi.useRealTimers();
650
+ const clientCredentials = await authorizeCLI('apps:write resources:write', testApp);
651
+ process.env.TEST_VARIABLE_1 = 'test-variable-1';
652
+ process.env.TEST_VARIABLE_2 = 'test-variable-2';
653
+ process.env.TEST_SECRET = 'test-secret';
654
+ process.env.TEST_SERVICE_URL_PATTERNS = 'http://localhost:1234';
655
+ process.env.TEST_SERVICE_IDENTIFIER = 'Authorization';
656
+ process.env.TEST_SERVICE_SECRET = 'secret';
657
+ process.env.TEST_SAML_IDP_CERTIFICATE = 'certificate';
658
+ process.env.TEST_SAML_ENTITY_ID = 'http://localhost:1234';
659
+ process.env.TEST_SAML_SSO_URL = 'http://localhost:1234';
660
+ process.env.TEST_OAUTH2_AUTHORIZATION_URL = 'http://localhost:1234';
661
+ process.env.TEST_OAUTH2_TOKEN_URL = 'http://localhost:1234';
662
+ process.env.TEST_OAUTH2_USER_INFO_URL = 'http://localhost:1234';
663
+ process.env.TEST_OAUTH2_CLIENT_ID = 'id';
664
+ process.env.TEST_OAUTH2_CLIENT_SECRET = 'secret';
665
+ process.env.TEST_SCIM_TOKEN = 'token';
666
+ process.env.TEST_SSL_KEY = 'key';
667
+ process.env.TEST_SSL_CERTIFICATE = 'certificate';
668
+ await publishApp({
669
+ path: resolveFixture('apps/test'),
670
+ organization: organization.id,
671
+ remote: testApp.defaults.baseURL,
672
+ clientCredentials,
673
+ visibility: 'unlisted',
674
+ iconBackground: '#ffffff',
675
+ });
676
+ vi.useFakeTimers();
677
+ const app = await App.findOne({
678
+ attributes: ['scimEnabled', 'scimToken', 'sslKey', 'sslCertificate'],
679
+ where: {
680
+ id: 1,
681
+ },
682
+ });
683
+ expect({
684
+ scimEnabled: app.scimEnabled,
685
+ scimToken: app.scimToken,
686
+ sslKey: app.sslKey,
687
+ sslCertificate: app.sslCertificate,
688
+ }).toStrictEqual({
689
+ scimEnabled: true,
690
+ scimToken: expect.any(Buffer),
691
+ sslKey: 'key',
692
+ sslCertificate: 'certificate',
693
+ });
694
+ const appVariables = await AppVariable.findAll({
695
+ attributes: ['name', 'value'],
696
+ where: {
697
+ AppId: 1,
698
+ },
699
+ });
700
+ expect(appVariables[0].toJSON()).toStrictEqual({
701
+ name: 'test-variable-inline',
702
+ value: 'inline',
703
+ });
704
+ expect(appVariables[1].toJSON()).toStrictEqual({
705
+ name: 'test-variable',
706
+ value: 'test-variable-1.test-variable-2',
707
+ });
708
+ const appServiceSecret = await AppServiceSecret.findOne({
709
+ attributes: ['name', 'authenticationMethod', 'urlPatterns', 'identifier', 'secret'],
710
+ where: {
711
+ AppId: 1,
712
+ },
713
+ });
714
+ expect(appServiceSecret.toJSON()).toStrictEqual({
715
+ name: 'test-service-secret',
716
+ authenticationMethod: 'custom-header',
717
+ urlPatterns: 'http://localhost:1234',
718
+ identifier: 'Authorization',
719
+ secret: expect.any(Buffer),
720
+ });
721
+ const appSamlSecret = await AppSamlSecret.findOne({
722
+ attributes: [
723
+ 'name',
724
+ 'icon',
725
+ 'nameAttribute',
726
+ 'emailAttribute',
727
+ 'idpCertificate',
728
+ 'entityId',
729
+ 'ssoUrl',
730
+ ],
731
+ where: {
732
+ AppId: 1,
733
+ },
734
+ });
735
+ expect(appSamlSecret.toJSON()).toStrictEqual({
736
+ emailAttribute: 'email',
737
+ entityId: 'http://localhost:1234',
738
+ icon: 'redhat',
739
+ id: 1,
740
+ idpCertificate: 'certificate',
741
+ name: 'test-saml-secret',
742
+ nameAttribute: 'name',
743
+ ssoUrl: 'http://localhost:1234',
744
+ spCertificate: expect.any(String),
745
+ });
746
+ const appOauth2Secret = await AppOAuth2Secret.findOne({
747
+ attributes: [
748
+ 'name',
749
+ 'icon',
750
+ 'scope',
751
+ 'remapper',
752
+ 'authorizationUrl',
753
+ 'tokenUrl',
754
+ 'userInfoUrl',
755
+ 'clientId',
756
+ 'clientSecret',
757
+ ],
758
+ where: {
759
+ AppId: 1,
760
+ },
761
+ });
762
+ expect({
763
+ authorizationUrl: appOauth2Secret.authorizationUrl,
764
+ clientId: appOauth2Secret.clientId,
765
+ clientSecret: appOauth2Secret.clientSecret,
766
+ icon: appOauth2Secret.icon,
767
+ name: appOauth2Secret.name,
768
+ remapper: appOauth2Secret.remapper,
769
+ scope: appOauth2Secret.scope,
770
+ tokenUrl: appOauth2Secret.tokenUrl,
771
+ userInfoUrl: appOauth2Secret.userInfoUrl,
772
+ }).toStrictEqual({
773
+ authorizationUrl: 'http://localhost:1234',
774
+ clientId: 'id',
775
+ clientSecret: 'secret',
776
+ icon: 'redhat',
777
+ name: 'test-oauth2-secret',
778
+ remapper: [{ prop: 'email' }],
779
+ scope: 'email openid profile',
780
+ tokenUrl: 'http://localhost:1234',
781
+ userInfoUrl: 'http://localhost:1234',
782
+ });
783
+ });
784
+ it('should validate and throw if there’s an error before publishing an app', async () => {
785
+ await BlockVersion.destroy({
786
+ where: {
787
+ version: '0.0.0',
788
+ OrganizationId: 'appsemble',
789
+ name: 'test',
790
+ },
791
+ });
792
+ vi.useRealTimers();
793
+ const clientCredentials = await authorizeCLI('apps:write resources:write', testApp);
794
+ await expect(() => publishApp({
795
+ path: resolveFixture('apps/test'),
796
+ organization: organization.id,
797
+ remote: testApp.defaults.baseURL,
798
+ clientCredentials,
799
+ // Required defaults
800
+ visibility: 'unlisted',
801
+ iconBackground: '#ffffff',
802
+ })).rejects.toThrow('is not a known block type');
803
+ const app = await App.findOne();
804
+ expect(app).toBeNull();
805
+ });
636
806
  });
637
807
  describe('updateApp', () => {
638
808
  let app;
@@ -648,7 +818,7 @@ describe('updateApp', () => {
648
818
  });
649
819
  it('should update app', async () => {
650
820
  vi.useRealTimers();
651
- const clientCredentials = await authorizeCLI('apps:write resources:write');
821
+ const clientCredentials = await authorizeCLI('apps:write resources:write', testApp);
652
822
  await updateApp({
653
823
  id: app.id,
654
824
  path: resolveFixture('apps/test'),
@@ -734,6 +904,7 @@ describe('updateApp', () => {
734
904
  "showAppDefinition": false,
735
905
  "showAppsembleLogin": false,
736
906
  "showAppsembleOAuth2Login": true,
907
+ "template": false,
737
908
  "visibility": "unlisted",
738
909
  "yaml": "name: Test App
739
910
  defaultPage: Test Page
@@ -774,7 +945,7 @@ describe('updateApp', () => {
774
945
  });
775
946
  it('should update app with resources and assets', async () => {
776
947
  vi.useRealTimers();
777
- const clientCredentials = await authorizeCLI('apps:write resources:write');
948
+ const clientCredentials = await authorizeCLI('apps:write resources:write', testApp);
778
949
  await updateApp({
779
950
  id: app.id,
780
951
  path: resolveFixture('apps/test'),
@@ -863,6 +1034,7 @@ describe('updateApp', () => {
863
1034
  "showAppDefinition": false,
864
1035
  "showAppsembleLogin": false,
865
1036
  "showAppsembleOAuth2Login": true,
1037
+ "template": false,
866
1038
  "visibility": "unlisted",
867
1039
  "yaml": "name: Test App
868
1040
  defaultPage: Test Page
@@ -929,7 +1101,7 @@ describe('updateApp', () => {
929
1101
  visibility: 'public',
930
1102
  });
931
1103
  vi.useRealTimers();
932
- const clientCredentials = await authorizeCLI('apps:write resources:write');
1104
+ const clientCredentials = await authorizeCLI('apps:write resources:write', testApp);
933
1105
  await updateApp({
934
1106
  path: resolveFixture('apps/test'),
935
1107
  id: app.id,
@@ -1017,6 +1189,7 @@ describe('updateApp', () => {
1017
1189
  "showAppDefinition": false,
1018
1190
  "showAppsembleLogin": false,
1019
1191
  "showAppsembleOAuth2Login": true,
1192
+ "template": true,
1020
1193
  "visibility": "public",
1021
1194
  "yaml": "name: Test App
1022
1195
  defaultPage: Test Page
@@ -1089,7 +1262,7 @@ describe('updateApp', () => {
1089
1262
  });
1090
1263
  it('should update app with app variant patches applied', async () => {
1091
1264
  vi.useRealTimers();
1092
- const clientCredentials = await authorizeCLI('apps:write resources:write');
1265
+ const clientCredentials = await authorizeCLI('apps:write resources:write', testApp);
1093
1266
  await updateApp({
1094
1267
  path: resolveFixture('apps/test'),
1095
1268
  id: app.id,
@@ -1173,6 +1346,7 @@ describe('updateApp', () => {
1173
1346
  "showAppDefinition": false,
1174
1347
  "showAppsembleLogin": false,
1175
1348
  "showAppsembleOAuth2Login": true,
1349
+ "template": false,
1176
1350
  "visibility": "unlisted",
1177
1351
  "yaml": "name: Test App
1178
1352
  defaultPage: Test Page
@@ -1226,6 +1400,375 @@ describe('updateApp', () => {
1226
1400
  await readFixture('apps/test/variants/tux/assets/small-tux.png'),
1227
1401
  ]);
1228
1402
  });
1403
+ it('should update app variables and secrets', async () => {
1404
+ vi.useRealTimers();
1405
+ const clientCredentials = await authorizeCLI('apps:write resources:write', testApp);
1406
+ process.env.TEST_VARIABLE_1 = 'test-variable-1';
1407
+ process.env.TEST_VARIABLE_2 = 'test-variable-2';
1408
+ process.env.TEST_SECRET = 'test-secret';
1409
+ process.env.TEST_SERVICE_URL_PATTERNS = 'http://localhost:1234';
1410
+ process.env.TEST_SERVICE_IDENTIFIER = 'Authorization';
1411
+ process.env.TEST_SERVICE_SECRET = 'secret';
1412
+ process.env.TEST_SAML_IDP_CERTIFICATE = 'certificate';
1413
+ process.env.TEST_SAML_ENTITY_ID = 'http://localhost:1234';
1414
+ process.env.TEST_SAML_SSO_URL = 'http://localhost:1234';
1415
+ process.env.TEST_OAUTH2_AUTHORIZATION_URL = 'http://localhost:1234';
1416
+ process.env.TEST_OAUTH2_TOKEN_URL = 'http://localhost:1234';
1417
+ process.env.TEST_OAUTH2_USER_INFO_URL = 'http://localhost:1234';
1418
+ process.env.TEST_OAUTH2_CLIENT_ID = 'id';
1419
+ process.env.TEST_OAUTH2_CLIENT_SECRET = 'secret';
1420
+ process.env.TEST_SCIM_TOKEN = 'token';
1421
+ process.env.TEST_SSL_KEY = 'key';
1422
+ process.env.TEST_SSL_CERTIFICATE = 'certificate';
1423
+ await updateApp({
1424
+ id: 1,
1425
+ path: resolveFixture('apps/test'),
1426
+ remote: testApp.defaults.baseURL,
1427
+ clientCredentials,
1428
+ visibility: 'unlisted',
1429
+ iconBackground: '#ffffff',
1430
+ force: true,
1431
+ });
1432
+ vi.useFakeTimers();
1433
+ const updatedApp = await App.findOne({
1434
+ attributes: ['scimEnabled', 'scimToken', 'sslKey', 'sslCertificate'],
1435
+ where: {
1436
+ id: 1,
1437
+ },
1438
+ });
1439
+ expect({
1440
+ scimEnabled: updatedApp.scimEnabled,
1441
+ scimToken: updatedApp.scimToken,
1442
+ sslKey: updatedApp.sslKey,
1443
+ sslCertificate: updatedApp.sslCertificate,
1444
+ }).toStrictEqual({
1445
+ scimEnabled: true,
1446
+ scimToken: expect.any(Buffer),
1447
+ sslKey: 'key',
1448
+ sslCertificate: 'certificate',
1449
+ });
1450
+ const appVariables = await AppVariable.findAll({
1451
+ attributes: ['name', 'value'],
1452
+ where: {
1453
+ AppId: 1,
1454
+ },
1455
+ });
1456
+ expect(appVariables[0].toJSON()).toStrictEqual({
1457
+ name: 'test-variable-inline',
1458
+ value: 'inline',
1459
+ });
1460
+ expect(appVariables[1].toJSON()).toStrictEqual({
1461
+ name: 'test-variable',
1462
+ value: 'test-variable-1.test-variable-2',
1463
+ });
1464
+ const appServiceSecret = await AppServiceSecret.findOne({
1465
+ attributes: ['name', 'authenticationMethod', 'urlPatterns', 'identifier', 'secret'],
1466
+ where: {
1467
+ AppId: 1,
1468
+ },
1469
+ });
1470
+ expect(appServiceSecret.toJSON()).toStrictEqual({
1471
+ name: 'test-service-secret',
1472
+ authenticationMethod: 'custom-header',
1473
+ urlPatterns: 'http://localhost:1234',
1474
+ identifier: 'Authorization',
1475
+ secret: expect.any(Buffer),
1476
+ });
1477
+ const appSamlSecret = await AppSamlSecret.findOne({
1478
+ attributes: [
1479
+ 'name',
1480
+ 'icon',
1481
+ 'nameAttribute',
1482
+ 'emailAttribute',
1483
+ 'idpCertificate',
1484
+ 'entityId',
1485
+ 'ssoUrl',
1486
+ ],
1487
+ where: {
1488
+ AppId: 1,
1489
+ },
1490
+ });
1491
+ expect(appSamlSecret.toJSON()).toStrictEqual({
1492
+ emailAttribute: 'email',
1493
+ entityId: 'http://localhost:1234',
1494
+ icon: 'redhat',
1495
+ id: 1,
1496
+ idpCertificate: 'certificate',
1497
+ name: 'test-saml-secret',
1498
+ nameAttribute: 'name',
1499
+ ssoUrl: 'http://localhost:1234',
1500
+ spCertificate: expect.any(String),
1501
+ });
1502
+ const appOauth2Secret = await AppOAuth2Secret.findOne({
1503
+ attributes: [
1504
+ 'name',
1505
+ 'icon',
1506
+ 'scope',
1507
+ 'remapper',
1508
+ 'authorizationUrl',
1509
+ 'tokenUrl',
1510
+ 'userInfoUrl',
1511
+ 'clientId',
1512
+ 'clientSecret',
1513
+ ],
1514
+ where: {
1515
+ AppId: 1,
1516
+ },
1517
+ });
1518
+ expect({
1519
+ authorizationUrl: appOauth2Secret.authorizationUrl,
1520
+ clientId: appOauth2Secret.clientId,
1521
+ clientSecret: appOauth2Secret.clientSecret,
1522
+ icon: appOauth2Secret.icon,
1523
+ name: appOauth2Secret.name,
1524
+ remapper: appOauth2Secret.remapper,
1525
+ scope: appOauth2Secret.scope,
1526
+ tokenUrl: appOauth2Secret.tokenUrl,
1527
+ userInfoUrl: appOauth2Secret.userInfoUrl,
1528
+ }).toStrictEqual({
1529
+ authorizationUrl: 'http://localhost:1234',
1530
+ clientId: 'id',
1531
+ clientSecret: 'secret',
1532
+ icon: 'redhat',
1533
+ name: 'test-oauth2-secret',
1534
+ remapper: [{ prop: 'email' }],
1535
+ scope: 'email openid profile',
1536
+ tokenUrl: 'http://localhost:1234',
1537
+ userInfoUrl: 'http://localhost:1234',
1538
+ });
1539
+ });
1540
+ it('should update an app with `app.locked` set to `fullLock` if force is specified', async () => {
1541
+ await app.update({ locked: 'fullLock' });
1542
+ const clientCredentials = await authorizeCLI('apps:write', testApp);
1543
+ await updateApp({
1544
+ path: resolveFixture('apps/test'),
1545
+ force: true,
1546
+ remote: testApp.defaults.baseURL,
1547
+ id: app.id,
1548
+ clientCredentials,
1549
+ visibility: 'public',
1550
+ iconBackground: '#fff999',
1551
+ });
1552
+ await app.reload();
1553
+ expect(app).toMatchInlineSnapshot(`
1554
+ {
1555
+ "$created": "1970-01-01T00:00:00.000Z",
1556
+ "$updated": "1970-01-01T00:00:00.000Z",
1557
+ "OrganizationId": "testorganization",
1558
+ "OrganizationName": undefined,
1559
+ "controllerCode": null,
1560
+ "controllerImplementations": null,
1561
+ "coreStyle": ".tux {
1562
+ color: rgb(0 0 0);
1563
+ }
1564
+ ",
1565
+ "definition": {
1566
+ "defaultPage": "Test Page",
1567
+ "name": "Test App",
1568
+ "pages": [
1569
+ {
1570
+ "blocks": [
1571
+ {
1572
+ "type": "test",
1573
+ "version": "0.0.0",
1574
+ },
1575
+ ],
1576
+ "name": "Test Page",
1577
+ },
1578
+ ],
1579
+ "resources": {
1580
+ "test": {
1581
+ "schema": {
1582
+ "additionalProperties": false,
1583
+ "properties": {
1584
+ "test": {
1585
+ "format": "binary",
1586
+ "type": "string",
1587
+ },
1588
+ },
1589
+ "type": "object",
1590
+ },
1591
+ },
1592
+ },
1593
+ },
1594
+ "demoMode": false,
1595
+ "domain": null,
1596
+ "emailName": null,
1597
+ "enableSelfRegistration": true,
1598
+ "enableUnsecuredServiceSecrets": false,
1599
+ "googleAnalyticsID": null,
1600
+ "hasClonableAssets": undefined,
1601
+ "hasClonableResources": undefined,
1602
+ "hasIcon": true,
1603
+ "hasMaskableIcon": true,
1604
+ "iconBackground": "#fff999",
1605
+ "iconUrl": "/api/apps/1/icon?maskable=true&updated=1970-01-01T00%3A00%3A00.000Z",
1606
+ "id": 1,
1607
+ "locked": "fullLock",
1608
+ "messages": undefined,
1609
+ "path": "test-app",
1610
+ "rating": undefined,
1611
+ "readmeUrl": undefined,
1612
+ "screenshotUrls": undefined,
1613
+ "sentryDsn": null,
1614
+ "sentryEnvironment": null,
1615
+ "sharedStyle": ".tux {
1616
+ color: rgb(0 0 0);
1617
+ }
1618
+ ",
1619
+ "showAppDefinition": false,
1620
+ "showAppsembleLogin": false,
1621
+ "showAppsembleOAuth2Login": true,
1622
+ "template": false,
1623
+ "visibility": "public",
1624
+ "yaml": "name: Test App
1625
+ defaultPage: Test Page
1626
+ resources:
1627
+ test:
1628
+ schema:
1629
+ additionalProperties: false
1630
+ type: object
1631
+ properties:
1632
+ test:
1633
+ type: string
1634
+ format: binary
1635
+ pages:
1636
+ - name: Test Page
1637
+ blocks:
1638
+ - type: test
1639
+ version: 0.0.0
1640
+ ",
1641
+ }
1642
+ `);
1643
+ });
1644
+ it('should not update an app if the `app.locked` is set to `fullLock`', async () => {
1645
+ await app.update({ locked: 'fullLock' });
1646
+ const clientCredentials = await authorizeCLI('apps:write', testApp);
1647
+ await expect(updateApp({
1648
+ path: resolveFixture('apps/test'),
1649
+ force: false,
1650
+ remote: testApp.defaults.baseURL,
1651
+ id: app.id,
1652
+ clientCredentials,
1653
+ visibility: 'public',
1654
+ iconBackground: '#fff999',
1655
+ })).rejects.toThrow('Request failed with status code 403');
1656
+ await app.reload();
1657
+ expect(app).toMatchInlineSnapshot(`
1658
+ {
1659
+ "$created": "1970-01-01T00:00:00.000Z",
1660
+ "$updated": "1970-01-01T00:00:00.000Z",
1661
+ "OrganizationId": "testorganization",
1662
+ "OrganizationName": undefined,
1663
+ "controllerCode": null,
1664
+ "controllerImplementations": null,
1665
+ "coreStyle": undefined,
1666
+ "definition": {
1667
+ "defaultPage": "Test Page",
1668
+ "name": "Test App",
1669
+ },
1670
+ "demoMode": false,
1671
+ "domain": null,
1672
+ "emailName": null,
1673
+ "enableSelfRegistration": true,
1674
+ "enableUnsecuredServiceSecrets": false,
1675
+ "googleAnalyticsID": null,
1676
+ "hasClonableAssets": undefined,
1677
+ "hasClonableResources": undefined,
1678
+ "hasIcon": false,
1679
+ "hasMaskableIcon": false,
1680
+ "iconBackground": "#ffffff",
1681
+ "iconUrl": null,
1682
+ "id": 1,
1683
+ "locked": "fullLock",
1684
+ "messages": undefined,
1685
+ "path": "test-app",
1686
+ "rating": undefined,
1687
+ "readmeUrl": undefined,
1688
+ "screenshotUrls": undefined,
1689
+ "sentryDsn": null,
1690
+ "sentryEnvironment": null,
1691
+ "sharedStyle": undefined,
1692
+ "showAppDefinition": false,
1693
+ "showAppsembleLogin": false,
1694
+ "showAppsembleOAuth2Login": true,
1695
+ "template": false,
1696
+ "visibility": "public",
1697
+ "yaml": "name: Test App
1698
+ defaultPage: Test Page
1699
+ ",
1700
+ }
1701
+ `);
1702
+ });
1703
+ it('should validate and throw if there’s an error before updating an app', async () => {
1704
+ await BlockVersion.destroy({
1705
+ where: {
1706
+ version: '0.0.0',
1707
+ OrganizationId: 'appsemble',
1708
+ name: 'test',
1709
+ },
1710
+ });
1711
+ vi.useRealTimers();
1712
+ const clientCredentials = await authorizeCLI('apps:write resources:write', testApp);
1713
+ await expect(updateApp({
1714
+ path: resolveFixture('apps/test'),
1715
+ id: app.id,
1716
+ force: false,
1717
+ remote: testApp.defaults.baseURL,
1718
+ clientCredentials,
1719
+ // Required defaults
1720
+ visibility: 'unlisted',
1721
+ iconBackground: '#ffffff',
1722
+ })).rejects.toThrow('is not a known block type');
1723
+ vi.useFakeTimers();
1724
+ await app.reload();
1725
+ expect(app.dataValues).toMatchInlineSnapshot(`
1726
+ {
1727
+ "OrganizationId": "testorganization",
1728
+ "controllerCode": null,
1729
+ "controllerImplementations": null,
1730
+ "coreStyle": null,
1731
+ "created": 1970-01-01T00:00:00.000Z,
1732
+ "definition": {
1733
+ "defaultPage": "Test Page",
1734
+ "name": "Test App",
1735
+ },
1736
+ "deleted": null,
1737
+ "demoMode": false,
1738
+ "domain": null,
1739
+ "emailHost": null,
1740
+ "emailName": null,
1741
+ "emailPassword": null,
1742
+ "emailPort": 587,
1743
+ "emailSecure": true,
1744
+ "emailUser": null,
1745
+ "enableSelfRegistration": true,
1746
+ "enableUnsecuredServiceSecrets": false,
1747
+ "googleAnalyticsID": null,
1748
+ "icon": null,
1749
+ "iconBackground": null,
1750
+ "id": 1,
1751
+ "locked": "unlocked",
1752
+ "maskableIcon": null,
1753
+ "path": "test-app",
1754
+ "scimEnabled": false,
1755
+ "scimToken": null,
1756
+ "sentryDsn": null,
1757
+ "sentryEnvironment": null,
1758
+ "sharedStyle": null,
1759
+ "showAppDefinition": false,
1760
+ "showAppsembleLogin": false,
1761
+ "showAppsembleOAuth2Login": true,
1762
+ "sslCertificate": null,
1763
+ "sslKey": null,
1764
+ "template": false,
1765
+ "updated": 1970-01-01T00:00:00.000Z,
1766
+ "vapidPrivateKey": "b",
1767
+ "vapidPublicKey": "a",
1768
+ "visibility": "public",
1769
+ }
1770
+ `);
1771
+ });
1229
1772
  });
1230
1773
  describe('patchApp', () => {
1231
1774
  it('should apply patches to app', async () => {
@@ -1249,7 +1792,7 @@ describe('patchApp', () => {
1249
1792
  visibility: 'public',
1250
1793
  OrganizationId: organization.id,
1251
1794
  }, { raw: true });
1252
- await authorizeCLI('apps:write');
1795
+ await authorizeCLI('apps:write', testApp);
1253
1796
  await patchApp({
1254
1797
  ...patches,
1255
1798
  remote: testApp.defaults.baseURL,
@@ -1336,7 +1879,7 @@ describe('patchApp', () => {
1336
1879
  OrganizationId: organization.id,
1337
1880
  [key]: from,
1338
1881
  }, { raw: true });
1339
- await authorizeCLI('apps:write');
1882
+ await authorizeCLI('apps:write', testApp);
1340
1883
  await patchApp({
1341
1884
  [key]: to,
1342
1885
  remote: testApp.defaults.baseURL,
@@ -1346,5 +1889,207 @@ describe('patchApp', () => {
1346
1889
  await app.reload();
1347
1890
  expect(app.dataValues[key]).toStrictEqual(to);
1348
1891
  });
1892
+ it('should not apply a patch if the `app.locked` is set to `fullLock`', async () => {
1893
+ const app = await App.create({
1894
+ path: 'test-app',
1895
+ definition: { name: 'Test App', defaultPage: 'Test Page' },
1896
+ vapidPublicKey: 'a',
1897
+ vapidPrivateKey: 'b',
1898
+ visibility: 'public',
1899
+ OrganizationId: organization.id,
1900
+ locked: 'fullLock',
1901
+ showAppDefinition: false,
1902
+ }, { raw: true });
1903
+ await authorizeCLI('apps:write', testApp);
1904
+ await patchApp({
1905
+ remote: testApp.defaults.baseURL,
1906
+ id: app.id,
1907
+ showAppDefinition: true,
1908
+ });
1909
+ expect(app.dataValues.showAppDefinition).toBe(false);
1910
+ await app.reload();
1911
+ expect(app.dataValues.showAppDefinition).toBe(false);
1912
+ });
1913
+ it('should apply a patch if the `app.locked` is set to `fullLock` and `force` is specified', async () => {
1914
+ const app = await App.create({
1915
+ path: 'test-app',
1916
+ definition: { name: 'Test App', defaultPage: 'Test Page' },
1917
+ vapidPublicKey: 'a',
1918
+ vapidPrivateKey: 'b',
1919
+ visibility: 'public',
1920
+ OrganizationId: organization.id,
1921
+ locked: 'fullLock',
1922
+ showAppDefinition: false,
1923
+ }, { raw: true });
1924
+ await authorizeCLI('apps:write', testApp);
1925
+ await patchApp({
1926
+ remote: testApp.defaults.baseURL,
1927
+ id: app.id,
1928
+ showAppDefinition: true,
1929
+ force: true,
1930
+ });
1931
+ expect(app.dataValues.showAppDefinition).toBe(false);
1932
+ await app.reload();
1933
+ expect(app.dataValues.showAppDefinition).toBe(true);
1934
+ });
1935
+ });
1936
+ describe('traverseAppDirectory', () => {
1937
+ it('should read the app definition and appsembleRC from the directory', async () => {
1938
+ const formData = new FormData();
1939
+ const [appsembleContext, appsembleRC, yaml, app] = await traverseAppDirectory(resolveFixture('apps/test'), 'test', formData);
1940
+ expect(appsembleContext).toMatchInlineSnapshot({
1941
+ icon: expect.any(String),
1942
+ maskableIcon: expect.any(String),
1943
+ }, `
1944
+ {
1945
+ "appLock": "studioLock",
1946
+ "assets": true,
1947
+ "assetsClonable": true,
1948
+ "collections": [
1949
+ 1,
1950
+ ],
1951
+ "demoMode": true,
1952
+ "googleAnalyticsId": "test",
1953
+ "icon": Any<String>,
1954
+ "iconBackground": "#000000",
1955
+ "id": 1,
1956
+ "maskableIcon": Any<String>,
1957
+ "organization": "testorganization",
1958
+ "resources": true,
1959
+ "sentryDsn": "https://public@sentry.example.com/1",
1960
+ "sentryEnvironment": "test",
1961
+ "showAppDefinition": true,
1962
+ "template": true,
1963
+ "visibility": "public",
1964
+ }
1965
+ `);
1966
+ expect(appsembleRC).toMatchInlineSnapshot({
1967
+ context: {
1968
+ test: {
1969
+ icon: expect.any(String),
1970
+ maskableIcon: expect.any(String),
1971
+ },
1972
+ },
1973
+ }, `
1974
+ {
1975
+ "context": {
1976
+ "test": {
1977
+ "appLock": "studioLock",
1978
+ "assets": true,
1979
+ "assetsClonable": true,
1980
+ "collections": [
1981
+ 1,
1982
+ ],
1983
+ "demoMode": true,
1984
+ "googleAnalyticsId": "test",
1985
+ "icon": Any<String>,
1986
+ "iconBackground": "#000000",
1987
+ "id": 1,
1988
+ "maskableIcon": Any<String>,
1989
+ "organization": "testorganization",
1990
+ "resources": true,
1991
+ "sentryDsn": "https://public@sentry.example.com/1",
1992
+ "sentryEnvironment": "test",
1993
+ "showAppDefinition": true,
1994
+ "template": true,
1995
+ "visibility": "public",
1996
+ },
1997
+ },
1998
+ }
1999
+ `);
2000
+ expect(yaml).toMatchInlineSnapshot(`
2001
+ "name: Test App
2002
+ defaultPage: Test Page
2003
+
2004
+ resources:
2005
+ test:
2006
+ schema:
2007
+ additionalProperties: false
2008
+ type: object
2009
+ properties:
2010
+ test:
2011
+ type: string
2012
+ format: binary
2013
+
2014
+ pages:
2015
+ - name: Test Page
2016
+ blocks:
2017
+ - type: test
2018
+ version: 0.0.0
2019
+ "
2020
+ `);
2021
+ expect(app).toMatchInlineSnapshot({
2022
+ iconUrl: expect.any(String),
2023
+ }, `
2024
+ {
2025
+ "coreStyle": ".tux {
2026
+ color: rgb(0 0 0);
2027
+ }
2028
+ ",
2029
+ "definition": {
2030
+ "defaultPage": "Test Page",
2031
+ "name": "Test App",
2032
+ "pages": [
2033
+ {
2034
+ "blocks": [
2035
+ {
2036
+ "type": "test",
2037
+ "version": "0.0.0",
2038
+ },
2039
+ ],
2040
+ "name": "Test Page",
2041
+ },
2042
+ ],
2043
+ "resources": {
2044
+ "test": {
2045
+ "schema": {
2046
+ "additionalProperties": false,
2047
+ "properties": {
2048
+ "test": {
2049
+ "format": "binary",
2050
+ "type": "string",
2051
+ },
2052
+ },
2053
+ "type": "object",
2054
+ },
2055
+ },
2056
+ },
2057
+ },
2058
+ "iconUrl": Any<String>,
2059
+ "readmeUrl": "README.md",
2060
+ "screenshotUrls": [
2061
+ "test_en-us.png",
2062
+ ],
2063
+ "sharedStyle": ".tux {
2064
+ color: rgb(0 0 0);
2065
+ }
2066
+ ",
2067
+ }
2068
+ `);
2069
+ });
2070
+ it('should return an error if no app-definition is found in the given directory', async () => {
2071
+ const formData = new FormData();
2072
+ await expect(() => traverseAppDirectory(resolveFixture('apps/empty'), null, formData)).rejects.toThrow('No app definition found');
2073
+ });
2074
+ });
2075
+ describe('deleteApp', () => {
2076
+ it('should throw if an error occurs', async () => {
2077
+ const clientCredentials = await authorizeCLI('apps:delete', testApp);
2078
+ await expect(() => deleteApp({ id: 1, remote: testApp.defaults.baseURL, clientCredentials })).rejects.toThrow('Request failed with status code 404');
2079
+ });
2080
+ it('should delete an app', async () => {
2081
+ const app = await App.create({
2082
+ path: 'test-app',
2083
+ definition: { name: 'Test App', defaultPage: 'Test Page' },
2084
+ vapidPublicKey: 'a',
2085
+ vapidPrivateKey: 'b',
2086
+ visibility: 'public',
2087
+ OrganizationId: organization.id,
2088
+ });
2089
+ const clientCredentials = await authorizeCLI('apps:delete', testApp);
2090
+ await deleteApp({ id: app.id, remote: testApp.defaults.baseURL, clientCredentials });
2091
+ const foundApps = await App.findAll();
2092
+ expect(foundApps).toStrictEqual([]);
2093
+ });
1349
2094
  });
1350
2095
  //# sourceMappingURL=app.test.js.map