biz-a-cli 2.3.71 → 2.3.73

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/tests/app.test.js CHANGED
@@ -1,122 +1,140 @@
1
- import fs from "fs"
2
- import { jest } from '@jest/globals'
3
- import { Duplex } from 'node:stream'
4
- import { finished } from 'node:stream/promises'
5
- import { createDecipheriv } from 'node:crypto'
6
- import * as tar from "tar"
7
- import { env } from "../envs/env.js"
8
- import path from "node:path"
9
-
10
- describe('Biz-A Apps CLI', () => {
11
- let originalOptions, originalCWD, axios, child_process;
12
-
13
- const JEST_MESSAGE_TOTAL = 3;
14
- let returnedCode = 0;
15
- let jestMessage = '';
16
-
17
- const logSpy = jest.spyOn(global.console, 'log').mockImplementation()
18
- const errorSpy = jest.spyOn(global.console, 'error').mockImplementation()
19
-
20
- async function runCommand(...options) {
21
- process.argv[1] = process.cwd() + '\\bin' // mock the key root folder
22
- process.argv = [process.argv[0], process.argv[1], ...options]
23
- await import('../bin/app.js')
24
-
25
- // All below functions does not wait for ES6 import promises
26
- // await jest.runAllTimersAsync()
27
- // jest.runAllTimers()
28
- // jest.runAllTicks()
29
-
30
- await new Promise(process.nextTick) // wait for all import promise (including ES6 import promises) to be resolved
31
- }
32
-
33
- beforeEach(async () => {
34
- originalOptions = process.argv
35
- originalCWD = process.cwd()
36
-
37
- // mock default export first with unstable_mockModule
38
- jest.unstable_mockModule('axios', () => { return { default: jest.fn() } })
39
- axios = (await import('axios')).default
40
-
41
- // then we can mock classes, functions, variables, and objects inside mocked module when doing dynamic import using jest.fn()
42
- axios.get = jest.fn()
43
- axios.post = jest.fn()
44
-
45
- jest.unstable_mockModule('node:child_process', () => ({
46
- spawn: jest.fn().mockReturnValue({
47
- on: jest.fn((event, callback) => {
48
- if (event === "close") callback(returnedCode)
49
- }),
50
- stderr: {
51
- on: jest.fn((event, callback) => {
52
- if (event === "data") callback(jestMessage)
53
- })
54
- }
55
- })
56
- }))
57
- child_process = (await import('node:child_process'));
58
- })
59
-
60
- afterEach(() => {
61
- jest.resetModules()
62
- jest.resetAllMocks()
63
- process.argv = originalOptions
64
- process.chdir(originalCWD)
65
- })
66
-
67
- describe('Add App', () => {
68
- const mockDataFolder = './tests/mockData/'
69
-
70
- function mockValidTemplates(appName, scriptList) {
71
- fs.rmSync(mockDataFolder + '/' + appName, { force: true, recursive: true })
72
- fs.mkdirSync(mockDataFolder + '/' + appName, { recursive: true })
73
- for (const [key, value] of Object.entries(scriptList)) {
74
- fs.writeFileSync(mockDataFolder + '/' + appName + '/' + key, value.act)
75
- }
76
- }
77
-
78
- function mockIssuerKeyResponse() {
79
- axios.get.mockResolvedValue({
80
- data: {
81
- // this data containing 'mockAESKey', which is encrypted using CLI Private key
82
- data: 'eyJpc3N1ZXIiOnsia2V5IjoidEVmZlRwbGpsRW9seVRVcnpEQUs4b05mZEg0T2p2VE9MOHZ4c0p3MkRISC9LY09WV2t0SG5kWmxxUXJURUdoWGtoWUFtQjBobmw1RU5xdFh2TXNjcHFCMnd6eXZJUy9OdUpxeFdBQmE4Y21QbXYyUHBqRFV3eUd5LzJzbFBybG00aGJjbjJWamFPRVJiMHFpclZFNGhZVDluNlFwSnR1ZUp1NXcrVG9JN0V1eC9wdkZHeWVaOU1ham5hSTVtcVliZkNsT0pEZnV3MkpuU1RGNk1kbWg0MURualNuYk5nWkJhSjF2VWw2U1lmVDREeVJoVTkrUER6UTRMZ0xPZXlVYUlTVHQwWnpQWDIyeGI5Nlo3NlE0ZFNGUzhwRzRJRk0vMFpHbnI5eXB0NmNhRFlseW5VUWVFOHVJMklJMnpZcjFNdE12RnkzaDEyUU5Za0tLS3NaaVNRV00vQXRPTm5sa00rcjFKSFpMZW42b3lKYlV1cEgvRmZxQmViQWdRUThLcUVENnN2UlljSXBKOWh3UlJDb2RvNmlHaVVTMUVZZnh2N3pFMDhwL281Q2JmdFRKTXpISjh3T1JZbkdaTDh5cWttT2V0Y3dJL0p6U2dGVDNST2E5V3ZMRkFHWkIrcWdEOTBNeGhwT3kyVzRlbS9lUVBpeHNadDR2UGl2T1l0QVZYZTBLL0lJR0tlZ3VRZ0NzeUZENkRmajQxYkNXbm9oUEgzNWl1Y2h1cGxKVUhsSmszMTRqLytkbTBieEZ6MWFzdDBKanVaRFp3bkVSeVJVbTNFSWZNU2kxVGRRaXRWRjI2bFJ3a1NwK0VSRHh0ZllHWCs3V1U3OUhyUFF3MHBuU3NzU29FbVFzQ2VXU0dFc1AwbEtpTlBjLzBCeDNzbjdNMnJpMzJmZUZLRHM9In0sImFjcXVpcmVyIjp7ImRhdGEiOiJtT1NabzB0N0ZZb2M2a0tuZlNURk04S3VHcUtzLzU2MGtERGdldDMrRW9UTUZZaW5sbThwRFRqc2xVK2VoWnRidk1TejJ1bmNPck5rQ1phQy9LM0JrbmM5T3BZTDZ2SXVGNmY2alZrbjJDS25xbnRnUXBqUkY4T2dYK3NEKzhLbnBMVll2QmhMdkJydjI2RTBHTnk5YTNwbCt2MHNna3EycXJoeUJCeVc3VnpRL3h1Y2FCdDRrUjE0N2FpaTlSaThvUXEzRU1kZ3BPN2crYmxLMFk3eGt5ekQ2YU1JSDFSR1lSMTgwaU94akVqYVpiVE1uQkUwS1RJUnBEMnkwL25MTU5VTHZTUlBraVMyTUh5cmI1QWJCbWNQR1hzVUVvZFc0OHM0QVdmS2hZRldLbzV5anQwZlhCVCtUbEdaWmYrcDJINnRzSVNUYjZSKzlHWFk5c1EzNE5IdmFCRTZtNzlURUVQWTNkeUl3Vm5WQTIzR0FqL211bzI5M2dMTlp0R090ZWcrR1VlWXIzWXViQUZnL2VrN0NyTThGdGRQZC92R3NjdlJ4bkNEYnhmU3U3YXUzUFd1RFB5djk0ZjhXaXVMcWlKUGFQRmFZaUE4SW1lZklnY2ROKzMrTWZDRkdyR1dsWHlZM1pxVVI5NEhteGVjd1Q5Q256N2g2NHc0L3c0b0c5bU9CWmVsRk14dzlxUVlOWTZ5ZzRrakRBa2xUK29GMUszd213VUpPVTlhc3NCVEQ1SXZONlJRSEV0SU02ZmhKY1R0Zm5tK3l0Y201b3hjY3ZrN2tlVDExR2xjVi9Oa3EwNDRKb0NFL0drbzV2TWovQ29ERklJd0VjSENnQ1RvVGE0SForcWUzTWc2Wm1CazhvL2pwV3VFbTF5anJMcUdMSmFOTEJXNjlTUT0iLCJzaWduYXR1cmUiOiJzZDJXYnNDVy9oeXlMMnIzcmVhSFcyZndzVy85THVKNkp6MW0zUUNkWVA1NkZUU1ovSHM4c0Z6V2hmdVhtS1JJdEd5cnA4RGxMNTJkVkw1eTNnYmRIc0VoMDRITDhiWXIwS2lFU2RlbnVPMnoyZkdBWTlsclVjRC92alpZNlAwOHBVQTBEbXIreTk2UjQzdGVhTnZaMkplMklMWW1GZzdNOGF2cDZJZWk5WllzUmxyMFg2TmlzcUlXTWxDeHZOLzdadGNzcWQ3QndtWEhZRmlQWVkxaC9KTUtJdEREZnBneGgzU3RITlZQb01JU09FcTVkbGVRMjJNaTlPL2trZ2pkcmlBRDZsZ1hCZ214Q0FDaE5VQzVEZW5vUVIwQUlTREMvTW9udVA5ZldoMktwMkV2SlRGakR0eUJFdFZZekZZME82NEcxOVNhSFRnRmdxTFd2eXJDL2gzK1BqREIyTTdxOFBXOWVDNjlwM0ZCdUt1R0U3aTkrTmpKeWh4eFJoWWlxbW5MWjFxN3lFUzNudkIrNjJHRDZrUlR6YU5zdjFCUTRFSVpBR1NSaUF4d3o0aWo0ZldtdWJhaEd2WGlseGUzYzE1S09aa0RzcWRpQklIL1RWYzc1TUVBUVYrVVNlbXNEUnF2eUZ3ZFBobW9oM25KeVdmbFdsMU05eVNhdU1MaUFPV2c1bXhhSkdqQ0NtMGV5SHZaWUFFOFNWUjViWFJOZjIrNkpBQUJ2Mk5iSEJIVEJxM3V1cWpEVFZZdFZTRmQ3QmsvUS9NdVRiQnNjYUNqVUtybmtMcUpDTEw4akV0NnJlQnJLdlNPZll1c0txWHBWRmZMV1dHd1ZWSEViTzZVQ0xYTmIzY0c1MXRuNmpOS3NmN29wQXhBTFhUNFRXVjk0RjB6KzRpMjNYOD0ifX0=',
83
- // sign using BizA Server PrivateKey, to be verified by BizA CLI using Server PublicKey
84
- signature: 'Y9Vs9mOV5fqjcX1ZQh8N0vubV6J5+rx1Lvcrb8T9nBONMJIS74lqRdnO4a84rDCXdda0QOJs5750OdE69/ognriSqdvJ5qjb4sjYAuzXmbxGOuU4ptWMAl9CVfi7JdISdXaDMn5o2E9SuuQLwBMUvX6a9p93/L+TK/pSLuv3cwZqaCtiyhsZV1Q1mSz5xpEfGZR//0Vdaj7pZYZ7CDjQAiq6tMc1Mm7azDmxEwDmNqTMvPKJusGTTHvIFH9MbGTsHgq7DzhsGBVW0fyQGx4ycqB5u9D9j9YffjdlFZ3xncxgq/NVHJzHPbosrG2blW3yWr0Sg4P34AAOhNMtAbbrZfa5HYQrGLB5MbOY3s8EiXItufKzz5xTKDVdLER7kWk6th+a7c548TfqWkrkepPEvgBgmGoMw/PvXFsMVMKqRoF31lN7tumKnVyVzz/EwrsVVc7QrsJxfODHgU0bNNxi6gEb+prqKy0ZWmWwtf7coLWq/ybC2yFMrqUjymoJBGvNoLBURihD2m645+qudNlcJlLoyTs8B3KRCwFKtBH2X9eW5ZzDpIQAXDge6Bz/668Xg6DB8TzvpNr0sPOkuXXSKhQnb0SkR7QTHzpFDcg88Ur9Nnnh9aSZZRj1nRo5LawNto+9684SzpIfbjzAnbTHLQ6oY28SIdihKVjc0wj9BZk='
85
- }
86
- })
87
- axios.post.mockResolvedValue({ data: { success: true }, status: 200 })
88
- }
89
-
90
- beforeAll(() => {
91
- fs.rmSync(mockDataFolder, { recursive: true, force: true })
92
- })
93
-
94
- afterAll(() => {
95
- fs.rmSync(mockDataFolder, { recursive: true, force: true })
96
- })
97
-
98
- const stressTestCount = 10;
99
- it.each(Array(stressTestCount).fill().map((v, i) => i + 1))(`shall compress and encrypt app scripts (stress test %p of ${stressTestCount})`, async (testNo) => {
100
- const mockScripts = {
101
- 'a.js': {
102
- act: 'get = function () {return {modelA: {}}}',
103
- exp: '{"modelA":{}}'
104
- },
105
- 'b.js': {
106
- act: 'const get = function () {\nreturn {"modelB": {id: null}}};',
107
- exp: '{"modelB":{"id":null}}'
108
- },
109
- 'c.js': {
110
- act: 'let get = function () {return {modelC: {}, function: {func: () => { \nreturn ["lib"];\n}\n}}};',
111
- exp: '{"modelC":{},"function":{"func":["window.Function","","return[\\\"lib\\\"]"]}}'
112
- },
113
- 'd.js': {
114
- act: 'var get = function () {return {model: {}, tableName: "", fields: [], function: {}}};\nmodule.exports = get;',
115
- exp: '{"model":{},"tableName":"","fields":[],"function":{}}'
116
- },
117
- 'menu.json': {
118
- // act: JSON.stringify([
119
- act: `[
1
+ import fs from "fs";
2
+ import { jest } from "@jest/globals";
3
+ import { Duplex } from "node:stream";
4
+ import { finished } from "node:stream/promises";
5
+ import { createDecipheriv } from "node:crypto";
6
+ import * as tar from "tar";
7
+ import { env } from "../envs/env.js";
8
+ import path from "node:path";
9
+
10
+ describe("Biz-A Apps CLI", () => {
11
+ let originalOptions, originalCWD, axios, child_process;
12
+
13
+ const JEST_MESSAGE_TOTAL = 3;
14
+ let returnedCode = 0;
15
+ let jestMessage = "";
16
+
17
+ const logSpy = jest.spyOn(global.console, "log").mockImplementation();
18
+ const errorSpy = jest.spyOn(global.console, "error").mockImplementation();
19
+
20
+ async function runCommand(...options) {
21
+ process.argv[1] = process.cwd() + "\\bin"; // mock the key root folder
22
+ process.argv = [process.argv[0], process.argv[1], ...options];
23
+ await import("../bin/app.js");
24
+
25
+ // All below functions does not wait for ES6 import promises
26
+ // await jest.runAllTimersAsync()
27
+ // jest.runAllTimers()
28
+ // jest.runAllTicks()
29
+
30
+ await new Promise(process.nextTick); // wait for all import promise (including ES6 import promises) to be resolved
31
+ }
32
+
33
+ beforeEach(async () => {
34
+ originalOptions = process.argv;
35
+ originalCWD = process.cwd();
36
+
37
+ // mock default export first with unstable_mockModule
38
+ jest.unstable_mockModule("axios", () => {
39
+ return { default: jest.fn() };
40
+ });
41
+ axios = (await import("axios")).default;
42
+
43
+ // then we can mock classes, functions, variables, and objects inside mocked module when doing dynamic import using jest.fn()
44
+ axios.get = jest.fn();
45
+ axios.post = jest.fn();
46
+
47
+ jest.unstable_mockModule("node:child_process", () => ({
48
+ spawn: jest.fn().mockReturnValue({
49
+ on: jest.fn((event, callback) => {
50
+ if (event === "close") callback(returnedCode);
51
+ }),
52
+ stderr: {
53
+ on: jest.fn((event, callback) => {
54
+ if (event === "data") callback(jestMessage);
55
+ }),
56
+ },
57
+ }),
58
+ }));
59
+ child_process = await import("node:child_process");
60
+ });
61
+
62
+ afterEach(() => {
63
+ jest.resetModules();
64
+ jest.resetAllMocks();
65
+ process.argv = originalOptions;
66
+ process.chdir(originalCWD);
67
+ });
68
+
69
+ describe("Add App", () => {
70
+ const mockDataFolder = "./tests/mockData/";
71
+
72
+ function mockValidTemplates(appName, scriptList) {
73
+ fs.rmSync(mockDataFolder + "/" + appName, {
74
+ force: true,
75
+ recursive: true,
76
+ });
77
+ fs.mkdirSync(mockDataFolder + "/" + appName, { recursive: true });
78
+ for (const [key, value] of Object.entries(scriptList)) {
79
+ fs.writeFileSync(
80
+ mockDataFolder + "/" + appName + "/" + key,
81
+ value.act,
82
+ );
83
+ }
84
+ }
85
+
86
+ function mockIssuerKeyResponse() {
87
+ axios.get.mockResolvedValue({
88
+ data: {
89
+ // this data containing 'mockAESKey', which is encrypted using CLI Private key
90
+ data: "eyJpc3N1ZXIiOnsia2V5IjoidEVmZlRwbGpsRW9seVRVcnpEQUs4b05mZEg0T2p2VE9MOHZ4c0p3MkRISC9LY09WV2t0SG5kWmxxUXJURUdoWGtoWUFtQjBobmw1RU5xdFh2TXNjcHFCMnd6eXZJUy9OdUpxeFdBQmE4Y21QbXYyUHBqRFV3eUd5LzJzbFBybG00aGJjbjJWamFPRVJiMHFpclZFNGhZVDluNlFwSnR1ZUp1NXcrVG9JN0V1eC9wdkZHeWVaOU1ham5hSTVtcVliZkNsT0pEZnV3MkpuU1RGNk1kbWg0MURualNuYk5nWkJhSjF2VWw2U1lmVDREeVJoVTkrUER6UTRMZ0xPZXlVYUlTVHQwWnpQWDIyeGI5Nlo3NlE0ZFNGUzhwRzRJRk0vMFpHbnI5eXB0NmNhRFlseW5VUWVFOHVJMklJMnpZcjFNdE12RnkzaDEyUU5Za0tLS3NaaVNRV00vQXRPTm5sa00rcjFKSFpMZW42b3lKYlV1cEgvRmZxQmViQWdRUThLcUVENnN2UlljSXBKOWh3UlJDb2RvNmlHaVVTMUVZZnh2N3pFMDhwL281Q2JmdFRKTXpISjh3T1JZbkdaTDh5cWttT2V0Y3dJL0p6U2dGVDNST2E5V3ZMRkFHWkIrcWdEOTBNeGhwT3kyVzRlbS9lUVBpeHNadDR2UGl2T1l0QVZYZTBLL0lJR0tlZ3VRZ0NzeUZENkRmajQxYkNXbm9oUEgzNWl1Y2h1cGxKVUhsSmszMTRqLytkbTBieEZ6MWFzdDBKanVaRFp3bkVSeVJVbTNFSWZNU2kxVGRRaXRWRjI2bFJ3a1NwK0VSRHh0ZllHWCs3V1U3OUhyUFF3MHBuU3NzU29FbVFzQ2VXU0dFc1AwbEtpTlBjLzBCeDNzbjdNMnJpMzJmZUZLRHM9In0sImFjcXVpcmVyIjp7ImRhdGEiOiJtT1NabzB0N0ZZb2M2a0tuZlNURk04S3VHcUtzLzU2MGtERGdldDMrRW9UTUZZaW5sbThwRFRqc2xVK2VoWnRidk1TejJ1bmNPck5rQ1phQy9LM0JrbmM5T3BZTDZ2SXVGNmY2alZrbjJDS25xbnRnUXBqUkY4T2dYK3NEKzhLbnBMVll2QmhMdkJydjI2RTBHTnk5YTNwbCt2MHNna3EycXJoeUJCeVc3VnpRL3h1Y2FCdDRrUjE0N2FpaTlSaThvUXEzRU1kZ3BPN2crYmxLMFk3eGt5ekQ2YU1JSDFSR1lSMTgwaU94akVqYVpiVE1uQkUwS1RJUnBEMnkwL25MTU5VTHZTUlBraVMyTUh5cmI1QWJCbWNQR1hzVUVvZFc0OHM0QVdmS2hZRldLbzV5anQwZlhCVCtUbEdaWmYrcDJINnRzSVNUYjZSKzlHWFk5c1EzNE5IdmFCRTZtNzlURUVQWTNkeUl3Vm5WQTIzR0FqL211bzI5M2dMTlp0R090ZWcrR1VlWXIzWXViQUZnL2VrN0NyTThGdGRQZC92R3NjdlJ4bkNEYnhmU3U3YXUzUFd1RFB5djk0ZjhXaXVMcWlKUGFQRmFZaUE4SW1lZklnY2ROKzMrTWZDRkdyR1dsWHlZM1pxVVI5NEhteGVjd1Q5Q256N2g2NHc0L3c0b0c5bU9CWmVsRk14dzlxUVlOWTZ5ZzRrakRBa2xUK29GMUszd213VUpPVTlhc3NCVEQ1SXZONlJRSEV0SU02ZmhKY1R0Zm5tK3l0Y201b3hjY3ZrN2tlVDExR2xjVi9Oa3EwNDRKb0NFL0drbzV2TWovQ29ERklJd0VjSENnQ1RvVGE0SForcWUzTWc2Wm1CazhvL2pwV3VFbTF5anJMcUdMSmFOTEJXNjlTUT0iLCJzaWduYXR1cmUiOiJzZDJXYnNDVy9oeXlMMnIzcmVhSFcyZndzVy85THVKNkp6MW0zUUNkWVA1NkZUU1ovSHM4c0Z6V2hmdVhtS1JJdEd5cnA4RGxMNTJkVkw1eTNnYmRIc0VoMDRITDhiWXIwS2lFU2RlbnVPMnoyZkdBWTlsclVjRC92alpZNlAwOHBVQTBEbXIreTk2UjQzdGVhTnZaMkplMklMWW1GZzdNOGF2cDZJZWk5WllzUmxyMFg2TmlzcUlXTWxDeHZOLzdadGNzcWQ3QndtWEhZRmlQWVkxaC9KTUtJdEREZnBneGgzU3RITlZQb01JU09FcTVkbGVRMjJNaTlPL2trZ2pkcmlBRDZsZ1hCZ214Q0FDaE5VQzVEZW5vUVIwQUlTREMvTW9udVA5ZldoMktwMkV2SlRGakR0eUJFdFZZekZZME82NEcxOVNhSFRnRmdxTFd2eXJDL2gzK1BqREIyTTdxOFBXOWVDNjlwM0ZCdUt1R0U3aTkrTmpKeWh4eFJoWWlxbW5MWjFxN3lFUzNudkIrNjJHRDZrUlR6YU5zdjFCUTRFSVpBR1NSaUF4d3o0aWo0ZldtdWJhaEd2WGlseGUzYzE1S09aa0RzcWRpQklIL1RWYzc1TUVBUVYrVVNlbXNEUnF2eUZ3ZFBobW9oM25KeVdmbFdsMU05eVNhdU1MaUFPV2c1bXhhSkdqQ0NtMGV5SHZaWUFFOFNWUjViWFJOZjIrNkpBQUJ2Mk5iSEJIVEJxM3V1cWpEVFZZdFZTRmQ3QmsvUS9NdVRiQnNjYUNqVUtybmtMcUpDTEw4akV0NnJlQnJLdlNPZll1c0txWHBWRmZMV1dHd1ZWSEViTzZVQ0xYTmIzY0c1MXRuNmpOS3NmN29wQXhBTFhUNFRXVjk0RjB6KzRpMjNYOD0ifX0=",
91
+ // sign using BizA Server PrivateKey, to be verified by BizA CLI using Server PublicKey
92
+ signature:
93
+ "Y9Vs9mOV5fqjcX1ZQh8N0vubV6J5+rx1Lvcrb8T9nBONMJIS74lqRdnO4a84rDCXdda0QOJs5750OdE69/ognriSqdvJ5qjb4sjYAuzXmbxGOuU4ptWMAl9CVfi7JdISdXaDMn5o2E9SuuQLwBMUvX6a9p93/L+TK/pSLuv3cwZqaCtiyhsZV1Q1mSz5xpEfGZR//0Vdaj7pZYZ7CDjQAiq6tMc1Mm7azDmxEwDmNqTMvPKJusGTTHvIFH9MbGTsHgq7DzhsGBVW0fyQGx4ycqB5u9D9j9YffjdlFZ3xncxgq/NVHJzHPbosrG2blW3yWr0Sg4P34AAOhNMtAbbrZfa5HYQrGLB5MbOY3s8EiXItufKzz5xTKDVdLER7kWk6th+a7c548TfqWkrkepPEvgBgmGoMw/PvXFsMVMKqRoF31lN7tumKnVyVzz/EwrsVVc7QrsJxfODHgU0bNNxi6gEb+prqKy0ZWmWwtf7coLWq/ybC2yFMrqUjymoJBGvNoLBURihD2m645+qudNlcJlLoyTs8B3KRCwFKtBH2X9eW5ZzDpIQAXDge6Bz/668Xg6DB8TzvpNr0sPOkuXXSKhQnb0SkR7QTHzpFDcg88Ur9Nnnh9aSZZRj1nRo5LawNto+9684SzpIfbjzAnbTHLQ6oY28SIdihKVjc0wj9BZk=",
94
+ },
95
+ });
96
+ axios.post.mockResolvedValue({
97
+ data: { success: true },
98
+ status: 200,
99
+ });
100
+ }
101
+
102
+ beforeAll(() => {
103
+ fs.rmSync(mockDataFolder, { recursive: true, force: true });
104
+ });
105
+
106
+ afterAll(() => {
107
+ fs.rmSync(mockDataFolder, { recursive: true, force: true });
108
+ });
109
+
110
+ const stressTestCount = 10;
111
+ it.each(
112
+ Array(stressTestCount)
113
+ .fill()
114
+ .map((v, i) => i + 1),
115
+ )(
116
+ `shall compress and encrypt app scripts (stress test %p of ${stressTestCount})`,
117
+ async (testNo) => {
118
+ const mockScripts = {
119
+ "a.js": {
120
+ act: "get = function () {return {modelA: {}}}",
121
+ exp: '{"modelA":{}}',
122
+ },
123
+ "b.js": {
124
+ act: 'const get = function () {\nreturn {"modelB": {id: null}}};',
125
+ exp: '{"modelB":{"id":null}}',
126
+ },
127
+ "c.js": {
128
+ act: 'let get = function () {return {modelC: {}, function: {func: () => { \nreturn ["lib"];\n}\n}}};',
129
+ exp: '{"modelC":{},"function":{"func":["window.Function","","return[\\\"lib\\\"]"]}}',
130
+ },
131
+ "d.js": {
132
+ act: 'var get = function () {return {model: {}, tableName: "", fields: [], function: {}}};\nmodule.exports = get;',
133
+ exp: '{"model":{},"tableName":"","fields":[],"function":{}}',
134
+ },
135
+ "menu.json": {
136
+ // act: JSON.stringify([
137
+ act: `[
120
138
  {"menuName": "", "caption": "Home", "link": ["./main"],"subMenu": []},
121
139
  {"menuName": "", "caption": "Personalia","link": [], "subMenu": [
122
140
  {"menuName": "","caption": "Data Master","link": [], "subMenu": []},
@@ -165,217 +183,365 @@ describe('Biz-A Apps CLI', () => {
165
183
  }
166
184
  ]}
167
185
  ]`,
168
- // ]),
169
- // not saved as templates file, but as metadata of curret App
170
- exp: "[{\"menuName\":\"\",\"caption\":\"Home\",\"link\":[\"./main\"],\"subMenu\":[]},{\"menuName\":\"\",\"caption\":\"Personalia\",\"link\":[],\"subMenu\":[{\"menuName\":\"\",\"caption\":\"Data Master\",\"link\":[],\"subMenu\":[]},{\"menuName\":\"\",\"caption\":\"Gaji\",\"link\":[],\"subMenu\":[{\"menuName\":\"\",\"caption\":\"Parameter\",\"link\":[\"./form\",\"gajiparam\",null],\"subMenu\":[]},{\"menuName\":\"\",\"caption\":\"Perubahan\",\"link\":[],\"subMenu\":[{\"menuName\":\"\",\"caption\":\"Form Perubahan Gaji\",\"link\":[\"./form\",\"gajiubah\",null],\"subMenu\":[]},{\"menuName\":\"\",\"caption\":\"Daftar Perubahan Gaji\",\"link\":[\"./list\",\"gajiubahdaftar\"],\"subMenu\":[]}]}]}]}]"
171
- }
172
- }
173
-
174
- const appName = 'compressStressTest' + testNo
175
- mockValidTemplates(appName, mockScripts)
176
- mockIssuerKeyResponse()
177
- const mockAESKey = Buffer.from('hAnHadaJXJaq/9fCFMNmjkrB61CBPXJid6vbtXgG8Ug=', 'base64')
178
-
179
- await runCommand('add', '-s', 'https://a.b.c', '-p', '1205', '-i', '2', '-d', mockDataFolder + '/' + appName)
180
-
181
- expect(axios.get.mock.calls).toHaveLength(1)
182
- const getArgs = axios.get.mock.calls[0]
183
- expect(getArgs).toHaveLength(2)
184
- expect(getArgs[0]).toBe(`${env.BIZA_SERVER_LINK}/api/issuerKey`)
185
- expect(getArgs[1]).toHaveProperty('params')
186
- const params = getArgs[1].params
187
- expect(params).toHaveProperty('data')
188
- expect(params.data).toBe('eyJpc3N1ZXIiOiJDTEkiLCJhY3F1aXJlciI6IkNsaWVudCJ9') // BizA CLI Private key
189
- expect(params).toHaveProperty('signature')
190
- expect(params.signature).not.toBeNull() // PSS signature is not deterministic
191
-
192
- expect(axios.post.mock.calls).toHaveLength(1)
193
- const postArgs = axios.post.mock.calls[0]
194
- expect(postArgs).toHaveLength(3)
195
- expect(postArgs[0]).toBe('https://a.b.c:1205/fina/rest/TOrmMethod/%22setApp%22')
196
-
197
- const apiParams = postArgs[1]._parameters
198
- expect(apiParams[0]).toBe(2)
199
- expect(apiParams[1]).toBe(appName.toLowerCase())
200
-
201
- // encrypted template scripts is not deterministic, because IV (initialize vector) using 16 random bytes
202
- const templateBuffer = Buffer.from(apiParams[2], 'base64')
203
- const decryptScripts = {}
204
- const stream = new Duplex()
205
- stream.push(templateBuffer)
206
- stream.push(null)
207
- stream.pipe(
208
- // tar.x -> will extract and write to file
209
- // tar.t -> only extract
210
- tar.t({
211
- strict: true
212
- , sync: true
213
- , onReadEntry: entry => {
214
- const chunks = [];
215
- entry.on('data', chunk => chunks.push(chunk));
216
- entry.on('end', () => {
217
- expect(entry.path.trim().toLowerCase()).not.toBe('menu.json') // shall save "menu.json" as App.metadata
218
- const cipherText = Buffer.from(Buffer.concat(chunks).toString(), 'base64')
219
- const decryptInitializeVector = cipherText.subarray(0, 16)
220
- const decryptData = cipherText.subarray(16)
221
- const decipher = createDecipheriv('aes-256-cbc', mockAESKey, decryptInitializeVector)
222
- const decrypted = Buffer.concat([decipher.update(decryptData), decipher.final()])
223
- decryptScripts[entry.path] = decrypted.toString()
224
- })
225
- }
226
- })
227
- .on('finish', () => stream.end())
228
- )
229
- await finished(stream) // wait for untar process to finish
230
- for (const [fileName, script] of Object.entries(decryptScripts)) {
231
- expect(script).toBe(mockScripts[fileName].exp)
232
- }
233
-
234
- // app.metadata : {
235
- // "acquirer": { // generate by BizA Server
236
- // "data": "", //containing AES Key for template script encryption or deccryption, which is encrypted using BizA Client publicKey
237
- // "signature": "", // sign using BizA CLI Private key, to be verified by BizA Client using CLI Public Key
238
- // },
239
- // "menu" : "" // contain encrypted "menu.json"
240
- // }
241
- expect(apiParams[3]).toHaveProperty('acquirer')
242
- expect(apiParams[3].acquirer).toHaveProperty('data')
243
- expect(apiParams[3].acquirer.data).toBe('eyJkYXRhIjoibU9TWm8wdDdGWW9jNmtLbmZTVEZNOEt1R3FLcy81NjBrRERnZXQzK0VvVE1GWWlubG04cERUanNsVStlaFp0YnZNU3oydW5jT3JOa0NaYUMvSzNCa25jOU9wWUw2dkl1RjZmNmpWa24yQ0tucW50Z1FwalJGOE9nWCtzRCs4S25wTFZZdkJoTHZCcnYyNkUwR055OWEzcGwrdjBzZ2txMnFyaHlCQnlXN1Z6US94dWNhQnQ0a1IxNDdhaWk5Umk4b1FxM0VNZGdwTzdnK2JsSzBZN3hreXpENmFNSUgxUkdZUjE4MGlPeGpFamFaYlRNbkJFMEtUSVJwRDJ5MC9uTE1OVUx2U1JQa2lTMk1IeXJiNUFiQm1jUEdYc1VFb2RXNDhzNEFXZktoWUZXS281eWp0MGZYQlQrVGxHWlpmK3AySDZ0c0lTVGI2Uis5R1hZOXNRMzROSHZhQkU2bTc5VEVFUFkzZHlJd1ZuVkEyM0dBai9tdW8yOTNnTE5adEdPdGVnK0dVZVlyM1l1YkFGZy9lazdDck04RnRkUGQvdkdzY3ZSeG5DRGJ4ZlN1N2F1M1BXdURQeXY5NGY4V2l1THFpSlBhUEZhWWlBOEltZWZJZ2NkTiszK01mQ0ZHckdXbFh5WTNacVVSOTRIbXhlY3dUOUNuejdoNjR3NC93NG9HOW1PQlplbEZNeHc5cVFZTlk2eWc0a2pEQWtsVCtvRjFLM3dtd1VKT1U5YXNzQlRENUl2TjZSUUhFdElNNmZoSmNUdGZubSt5dGNtNW94Y2N2azdrZVQxMUdsY1YvTmtxMDQ0Sm9DRS9Ha281dk1qL0NvREZJSXdFY0hDZ0NUb1RhNEhaK3FlM01nNlptQms4by9qcFd1RW0xeWpyTHFHTEphTkxCVzY5U1E9Iiwic2lnbmF0dXJlIjoic2QyV2JzQ1cvaHl5TDJyM3JlYUhXMmZ3c1cvOUx1SjZKejFtM1FDZFlQNTZGVFNaL0hzOHNGeldoZnVYbUtSSXRHeXJwOERsTDUyZFZMNXkzZ2JkSHNFaDA0SEw4YllyMEtpRVNkZW51TzJ6MmZHQVk5bHJVY0QvdmpaWTZQMDhwVUEwRG1yK3k5NlI0M3RlYU52WjJKZTJJTFltRmc3TThhdnA2SWVpOVpZc1JscjBYNk5pc3FJV01sQ3h2Ti83WnRjc3FkN0J3bVhIWUZpUFlZMWgvSk1LSXRERGZwZ3hoM1N0SE5WUG9NSVNPRXE1ZGxlUTIyTWk5Ty9ra2dqZHJpQUQ2bGdYQmdteENBQ2hOVUM1RGVub1FSMEFJU0RDL01vbnVQOWZXaDJLcDJFdkpURmpEdHlCRXRWWXpGWTBPNjRHMTlTYUhUZ0ZncUxXdnlyQy9oMytQakRCMk03cThQVzllQzY5cDNGQnVLdUdFN2k5K05qSnloeHhSaFlpcW1uTFoxcTd5RVMzbnZCKzYyR0Q2a1JUemFOc3YxQlE0RUlaQUdTUmlBeHd6NGlqNGZXbXViYWhHdlhpbHhlM2MxNUtPWmtEc3FkaUJJSC9UVmM3NU1FQVFWK1VTZW1zRFJxdnlGd2RQaG1vaDNuSnlXZmxXbDFNOXlTYXVNTGlBT1dnNW14YUpHakNDbTBleUh2WllBRThTVlI1YlhSTmYyKzZKQUFCdjJOYkhCSFRCcTN1dXFqRFRWWXRWU0ZkN0JrL1EvTXVUYkJzY2FDalVLcm5rTHFKQ0xMOGpFdDZyZUJyS3ZTT2ZZdXNLcVhwVkZmTFdXR3dWVkhFYk82VUNMWE5iM2NHNTF0bjZqTktzZjdvcEF4QUxYVDRUV1Y5NEYweis0aTIzWDg9In0=')
244
- expect(apiParams[3].acquirer).toHaveProperty('signature')
245
- expect(apiParams[3].acquirer.signature).not.toBeNull() // PSS signature is not deterministic
246
-
247
- expect(apiParams[3]).toHaveProperty('menu') // shall save "menu.json" as App.metadata
248
- expect(apiParams[3].menu).not.toBeNull() // IV (initialize vector) using 16 random bytes
249
-
250
- expect(postArgs[2]).toStrictEqual({ "headers": { "Content-Type": "text/plain" } })
251
-
252
- expect(logSpy.mock.calls[JEST_MESSAGE_TOTAL + 0][0]).toContain(`Finished packing ${Object.keys(mockScripts).length} files into "./upload/${appName.toLowerCase()}.tgz"`)
253
- expect(logSpy.mock.calls[JEST_MESSAGE_TOTAL + 1][0]).toContain(`Finished uploading "./upload/${appName.toLocaleLowerCase()}.tgz"`)
254
-
255
- // fs.rmSync(mockDataFolder + '/' + appName, {recursive: true, force: true})
256
- })
257
-
258
- it('shall support addApp body scripts when called from other function', async () => {
259
- const appName = 'bodyScriptSourceApp'
260
- const workingDir = mockDataFolder + '/' + appName
261
- const mockAESKey = Buffer.from('hAnHadaJXJaq/9fCFMNmjkrB61CBPXJid6vbtXgG8Ug=', 'base64')
262
- const expectedScript = '{"fromBody":{"id":1}}'
263
-
264
- mockValidTemplates(appName, {})
265
- mockIssuerKeyResponse()
266
- process.env.BIZA_APP_SKIP_PARSE = '1'
267
-
268
- try {
269
- process.argv[1] = process.cwd() + '\\bin'
270
- const { addApp } = await import('../bin/app.js')
271
-
272
- const result = await addApp({
273
- workingDir,
274
- verbose: false,
275
- server: 'https://a.b.c',
276
- apiPort: 1205,
277
- dbIndex: 2,
278
- files: [{ name: 'a.js' }],
279
- body: {
280
- scripts: {
281
- 'a.js': 'get = function () { return {fromBody: {id: 1}} }'
282
- }
283
- }
284
- })
285
-
286
- expect(result.success).toBe(true)
287
- expect(axios.post).toHaveBeenCalledTimes(1)
288
-
289
- const apiParams = axios.post.mock.calls[0][1]._parameters
290
- const templateBuffer = Buffer.from(apiParams[2], 'base64')
291
- const decryptScripts = {}
292
- const stream = new Duplex()
293
- stream.push(templateBuffer)
294
- stream.push(null)
295
- stream.pipe(
296
- tar.t({
297
- strict: true,
298
- sync: true,
299
- onReadEntry: entry => {
300
- const chunks = []
301
- entry.on('data', chunk => chunks.push(chunk))
302
- entry.on('end', () => {
303
- const cipherText = Buffer.from(Buffer.concat(chunks).toString(), 'base64')
304
- const decryptInitializeVector = cipherText.subarray(0, 16)
305
- const decryptData = cipherText.subarray(16)
306
- const decipher = createDecipheriv('aes-256-cbc', mockAESKey, decryptInitializeVector)
307
- const decrypted = Buffer.concat([decipher.update(decryptData), decipher.final()])
308
- decryptScripts[entry.path] = decrypted.toString()
309
- })
310
- }
311
- })
312
- .on('finish', () => stream.end())
313
- )
314
- await finished(stream)
315
-
316
- expect(decryptScripts['a.js']).toBe(expectedScript)
317
- } finally {
318
- delete process.env.BIZA_APP_SKIP_PARSE
319
- }
320
- })
321
-
322
- it('shall resolve app name from SYS$CONFIG APPLICATION_CONFIG and use it for upload', async () => {
323
- const appName = 'metadataNameSourceApp'
324
- const workingDir = mockDataFolder + '/' + appName
325
-
326
- mockValidTemplates(appName, {})
327
- mockIssuerKeyResponse()
328
- axios.request = jest.fn().mockResolvedValue({
329
- data: {
330
- data: JSON.stringify([
331
- {
332
- 'SYS$CONFIG.NAME': 'APPLICATION_CONFIG',
333
- 'SYS$CONFIG.DATA': JSON.stringify({
334
- metadata: {
335
- name: 'BizA Sales App'
336
- }
337
- })
338
- }
339
- ])
340
- }
341
- })
342
- process.env.BIZA_APP_SKIP_PARSE = '1'
343
-
344
- try {
345
- process.argv[1] = process.cwd() + '\\bin'
346
- const { addApp } = await import('../bin/app.js')
347
-
348
- const result = await addApp({
349
- workingDir,
350
- verbose: false,
351
- server: 'https://a.b.c',
352
- apiPort: 1205,
353
- dbIndex: 2,
354
- files: [{ name: 'applicationConfig.js' }],
355
- sub: 'metadata-sub',
356
- body: {
357
- scripts: {
358
- 'applicationConfig.js': 'get = function () { return { metadata: { name: "Name From Body Must Be Ignored" } }; }'
359
- }
360
- }
361
- })
362
-
363
- expect(result.success).toBe(true)
364
- expect(axios.post).toHaveBeenCalledTimes(1)
365
- expect(axios.request).toHaveBeenCalledTimes(1)
366
-
367
- const apiParams = axios.post.mock.calls[0][1]._parameters
368
- expect(apiParams[1]).toBe('bizasalesapp')
369
- expect(apiParams[3]).toHaveProperty('name', 'BizA Sales App')
370
- } finally {
371
- delete process.env.BIZA_APP_SKIP_PARSE
372
- }
373
- })
374
-
375
- /* it('Invalid script syntax', async ()=>{
376
- mockValidTemplates({
377
- 'a.js' : {act: 'get = function () {return {modelA: {}}'} // missing close closure at the end
378
- })
186
+ // ]),
187
+ // not saved as templates file, but as metadata of curret App
188
+ exp: '[{"menuName":"","caption":"Home","link":["./main"],"subMenu":[]},{"menuName":"","caption":"Personalia","link":[],"subMenu":[{"menuName":"","caption":"Data Master","link":[],"subMenu":[]},{"menuName":"","caption":"Gaji","link":[],"subMenu":[{"menuName":"","caption":"Parameter","link":["./form","gajiparam",null],"subMenu":[]},{"menuName":"","caption":"Perubahan","link":[],"subMenu":[{"menuName":"","caption":"Form Perubahan Gaji","link":["./form","gajiubah",null],"subMenu":[]},{"menuName":"","caption":"Daftar Perubahan Gaji","link":["./list","gajiubahdaftar"],"subMenu":[]}]}]}]}]',
189
+ },
190
+ };
191
+
192
+ const appName = "compressStressTest" + testNo;
193
+ mockValidTemplates(appName, mockScripts);
194
+ mockIssuerKeyResponse();
195
+ const mockAESKey = Buffer.from(
196
+ "hAnHadaJXJaq/9fCFMNmjkrB61CBPXJid6vbtXgG8Ug=",
197
+ "base64",
198
+ );
199
+
200
+ await runCommand(
201
+ "add",
202
+ "-s",
203
+ "https://a.b.c",
204
+ "-p",
205
+ "1205",
206
+ "-i",
207
+ "2",
208
+ "-d",
209
+ mockDataFolder + "/" + appName,
210
+ );
211
+
212
+ expect(axios.get.mock.calls).toHaveLength(1);
213
+ const getArgs = axios.get.mock.calls[0];
214
+ expect(getArgs).toHaveLength(2);
215
+ expect(getArgs[0]).toBe(
216
+ `${env.BIZA_SERVER_LINK}/api/issuerKey`,
217
+ );
218
+ expect(getArgs[1]).toHaveProperty("params");
219
+ const params = getArgs[1].params;
220
+ expect(params).toHaveProperty("data");
221
+ expect(params.data).toBe(
222
+ "eyJpc3N1ZXIiOiJDTEkiLCJhY3F1aXJlciI6IkNsaWVudCJ9",
223
+ ); // BizA CLI Private key
224
+ expect(params).toHaveProperty("signature");
225
+ expect(params.signature).not.toBeNull(); // PSS signature is not deterministic
226
+
227
+ expect(axios.post.mock.calls).toHaveLength(1);
228
+ const postArgs = axios.post.mock.calls[0];
229
+ expect(postArgs).toHaveLength(3);
230
+ expect(postArgs[0]).toBe(
231
+ "https://a.b.c:1205/fina/rest/TOrmMethod/%22setApp%22",
232
+ );
233
+
234
+ const apiParams = postArgs[1]._parameters;
235
+ expect(apiParams[0]).toBe(2);
236
+ expect(apiParams[1]).toBe(appName.toLowerCase());
237
+
238
+ // encrypted template scripts is not deterministic, because IV (initialize vector) using 16 random bytes
239
+ const templateBuffer = Buffer.from(apiParams[2], "base64");
240
+ const decryptScripts = {};
241
+ const stream = new Duplex();
242
+ stream.push(templateBuffer);
243
+ stream.push(null);
244
+ stream.pipe(
245
+ // tar.x -> will extract and write to file
246
+ // tar.t -> only extract
247
+ tar
248
+ .t({
249
+ strict: true,
250
+ sync: true,
251
+ onReadEntry: (entry) => {
252
+ const chunks = [];
253
+ entry.on("data", (chunk) => chunks.push(chunk));
254
+ entry.on("end", () => {
255
+ expect(
256
+ entry.path.trim().toLowerCase(),
257
+ ).not.toBe("menu.json"); // shall save "menu.json" as App.metadata
258
+ const cipherText = Buffer.from(
259
+ Buffer.concat(chunks).toString(),
260
+ "base64",
261
+ );
262
+ const decryptInitializeVector =
263
+ cipherText.subarray(0, 16);
264
+ const decryptData = cipherText.subarray(16);
265
+ const decipher = createDecipheriv(
266
+ "aes-256-cbc",
267
+ mockAESKey,
268
+ decryptInitializeVector,
269
+ );
270
+ const decrypted = Buffer.concat([
271
+ decipher.update(decryptData),
272
+ decipher.final(),
273
+ ]);
274
+ decryptScripts[entry.path] =
275
+ decrypted.toString();
276
+ });
277
+ },
278
+ })
279
+ .on("finish", () => stream.end()),
280
+ );
281
+ await finished(stream); // wait for untar process to finish
282
+ for (const [fileName, script] of Object.entries(
283
+ decryptScripts,
284
+ )) {
285
+ expect(script).toBe(mockScripts[fileName].exp);
286
+ }
287
+
288
+ // app.metadata : {
289
+ // "acquirer": { // generate by BizA Server
290
+ // "data": "", //containing AES Key for template script encryption or deccryption, which is encrypted using BizA Client publicKey
291
+ // "signature": "", // sign using BizA CLI Private key, to be verified by BizA Client using CLI Public Key
292
+ // },
293
+ // "menu" : "" // contain encrypted "menu.json"
294
+ // }
295
+ expect(apiParams[3]).toHaveProperty("acquirer");
296
+ expect(apiParams[3].acquirer).toHaveProperty("data");
297
+ expect(apiParams[3].acquirer.data).toBe(
298
+ "eyJkYXRhIjoibU9TWm8wdDdGWW9jNmtLbmZTVEZNOEt1R3FLcy81NjBrRERnZXQzK0VvVE1GWWlubG04cERUanNsVStlaFp0YnZNU3oydW5jT3JOa0NaYUMvSzNCa25jOU9wWUw2dkl1RjZmNmpWa24yQ0tucW50Z1FwalJGOE9nWCtzRCs4S25wTFZZdkJoTHZCcnYyNkUwR055OWEzcGwrdjBzZ2txMnFyaHlCQnlXN1Z6US94dWNhQnQ0a1IxNDdhaWk5Umk4b1FxM0VNZGdwTzdnK2JsSzBZN3hreXpENmFNSUgxUkdZUjE4MGlPeGpFamFaYlRNbkJFMEtUSVJwRDJ5MC9uTE1OVUx2U1JQa2lTMk1IeXJiNUFiQm1jUEdYc1VFb2RXNDhzNEFXZktoWUZXS281eWp0MGZYQlQrVGxHWlpmK3AySDZ0c0lTVGI2Uis5R1hZOXNRMzROSHZhQkU2bTc5VEVFUFkzZHlJd1ZuVkEyM0dBai9tdW8yOTNnTE5adEdPdGVnK0dVZVlyM1l1YkFGZy9lazdDck04RnRkUGQvdkdzY3ZSeG5DRGJ4ZlN1N2F1M1BXdURQeXY5NGY4V2l1THFpSlBhUEZhWWlBOEltZWZJZ2NkTiszK01mQ0ZHckdXbFh5WTNacVVSOTRIbXhlY3dUOUNuejdoNjR3NC93NG9HOW1PQlplbEZNeHc5cVFZTlk2eWc0a2pEQWtsVCtvRjFLM3dtd1VKT1U5YXNzQlRENUl2TjZSUUhFdElNNmZoSmNUdGZubSt5dGNtNW94Y2N2azdrZVQxMUdsY1YvTmtxMDQ0Sm9DRS9Ha281dk1qL0NvREZJSXdFY0hDZ0NUb1RhNEhaK3FlM01nNlptQms4by9qcFd1RW0xeWpyTHFHTEphTkxCVzY5U1E9Iiwic2lnbmF0dXJlIjoic2QyV2JzQ1cvaHl5TDJyM3JlYUhXMmZ3c1cvOUx1SjZKejFtM1FDZFlQNTZGVFNaL0hzOHNGeldoZnVYbUtSSXRHeXJwOERsTDUyZFZMNXkzZ2JkSHNFaDA0SEw4YllyMEtpRVNkZW51TzJ6MmZHQVk5bHJVY0QvdmpaWTZQMDhwVUEwRG1yK3k5NlI0M3RlYU52WjJKZTJJTFltRmc3TThhdnA2SWVpOVpZc1JscjBYNk5pc3FJV01sQ3h2Ti83WnRjc3FkN0J3bVhIWUZpUFlZMWgvSk1LSXRERGZwZ3hoM1N0SE5WUG9NSVNPRXE1ZGxlUTIyTWk5Ty9ra2dqZHJpQUQ2bGdYQmdteENBQ2hOVUM1RGVub1FSMEFJU0RDL01vbnVQOWZXaDJLcDJFdkpURmpEdHlCRXRWWXpGWTBPNjRHMTlTYUhUZ0ZncUxXdnlyQy9oMytQakRCMk03cThQVzllQzY5cDNGQnVLdUdFN2k5K05qSnloeHhSaFlpcW1uTFoxcTd5RVMzbnZCKzYyR0Q2a1JUemFOc3YxQlE0RUlaQUdTUmlBeHd6NGlqNGZXbXViYWhHdlhpbHhlM2MxNUtPWmtEc3FkaUJJSC9UVmM3NU1FQVFWK1VTZW1zRFJxdnlGd2RQaG1vaDNuSnlXZmxXbDFNOXlTYXVNTGlBT1dnNW14YUpHakNDbTBleUh2WllBRThTVlI1YlhSTmYyKzZKQUFCdjJOYkhCSFRCcTN1dXFqRFRWWXRWU0ZkN0JrL1EvTXVUYkJzY2FDalVLcm5rTHFKQ0xMOGpFdDZyZUJyS3ZTT2ZZdXNLcVhwVkZmTFdXR3dWVkhFYk82VUNMWE5iM2NHNTF0bjZqTktzZjdvcEF4QUxYVDRUV1Y5NEYweis0aTIzWDg9In0=",
299
+ );
300
+ expect(apiParams[3].acquirer).toHaveProperty("signature");
301
+ expect(apiParams[3].acquirer.signature).not.toBeNull(); // PSS signature is not deterministic
302
+
303
+ expect(apiParams[3]).toHaveProperty("menu"); // shall save "menu.json" as App.metadata
304
+ expect(apiParams[3].menu).not.toBeNull(); // IV (initialize vector) using 16 random bytes
305
+
306
+ expect(postArgs[2]).toStrictEqual({
307
+ headers: { "Content-Type": "text/plain" },
308
+ });
309
+
310
+ expect(logSpy.mock.calls[JEST_MESSAGE_TOTAL + 0][0]).toContain(
311
+ `Finished packing ${Object.keys(mockScripts).length} files into "./upload/${appName.toLowerCase()}.tgz"`,
312
+ );
313
+ expect(logSpy.mock.calls[JEST_MESSAGE_TOTAL + 1][0]).toContain(
314
+ `Finished uploading "./upload/${appName.toLocaleLowerCase()}.tgz"`,
315
+ );
316
+
317
+ // fs.rmSync(mockDataFolder + '/' + appName, {recursive: true, force: true})
318
+ },
319
+ );
320
+
321
+ it("shall support addApp body scripts when called from other function", async () => {
322
+ const appName = "bodyScriptSourceApp";
323
+ const workingDir = mockDataFolder + "/" + appName;
324
+ const mockAESKey = Buffer.from(
325
+ "hAnHadaJXJaq/9fCFMNmjkrB61CBPXJid6vbtXgG8Ug=",
326
+ "base64",
327
+ );
328
+ const expectedScript = '{"fromBody":{"id":1}}';
329
+
330
+ mockValidTemplates(appName, {});
331
+ mockIssuerKeyResponse();
332
+ process.env.BIZA_APP_SKIP_PARSE = "1";
333
+
334
+ try {
335
+ process.argv[1] = process.cwd() + "\\bin";
336
+ const { addApp } = await import("../bin/app.js");
337
+
338
+ const result = await addApp({
339
+ workingDir,
340
+ verbose: false,
341
+ server: "https://a.b.c",
342
+ apiPort: 1205,
343
+ dbIndex: 2,
344
+ files: [{ name: "a.js" }],
345
+ body: {
346
+ scripts: {
347
+ "a.js": "get = function () { return {fromBody: {id: 1}} }",
348
+ },
349
+ },
350
+ });
351
+
352
+ expect(result.success).toBe(true);
353
+ expect(axios.post).toHaveBeenCalledTimes(1);
354
+
355
+ const apiParams = axios.post.mock.calls[0][1]._parameters;
356
+ const templateBuffer = Buffer.from(apiParams[2], "base64");
357
+ const decryptScripts = {};
358
+ const stream = new Duplex();
359
+ stream.push(templateBuffer);
360
+ stream.push(null);
361
+ stream.pipe(
362
+ tar
363
+ .t({
364
+ strict: true,
365
+ sync: true,
366
+ onReadEntry: (entry) => {
367
+ const chunks = [];
368
+ entry.on("data", (chunk) => chunks.push(chunk));
369
+ entry.on("end", () => {
370
+ const cipherText = Buffer.from(
371
+ Buffer.concat(chunks).toString(),
372
+ "base64",
373
+ );
374
+ const decryptInitializeVector =
375
+ cipherText.subarray(0, 16);
376
+ const decryptData = cipherText.subarray(16);
377
+ const decipher = createDecipheriv(
378
+ "aes-256-cbc",
379
+ mockAESKey,
380
+ decryptInitializeVector,
381
+ );
382
+ const decrypted = Buffer.concat([
383
+ decipher.update(decryptData),
384
+ decipher.final(),
385
+ ]);
386
+ decryptScripts[entry.path] =
387
+ decrypted.toString();
388
+ });
389
+ },
390
+ })
391
+ .on("finish", () => stream.end()),
392
+ );
393
+ await finished(stream);
394
+
395
+ expect(decryptScripts["a.js"]).toBe(expectedScript);
396
+ } finally {
397
+ delete process.env.BIZA_APP_SKIP_PARSE;
398
+ }
399
+ });
400
+
401
+ it("shall resolve app name from SYS$CONFIG APPLICATION_CONFIG and use it for upload", async () => {
402
+ const appName = "metadataNameSourceApp";
403
+ const workingDir = mockDataFolder + "/" + appName;
404
+
405
+ mockValidTemplates(appName, {});
406
+ mockIssuerKeyResponse();
407
+ axios.request = jest.fn().mockResolvedValue({
408
+ data: {
409
+ data: JSON.stringify([
410
+ {
411
+ "SYS$CONFIG.NAME": "APPLICATION_CONFIG",
412
+ "SYS$CONFIG.DATA": JSON.stringify({
413
+ metadata: {
414
+ name: "BizA Sales App",
415
+ },
416
+ }),
417
+ },
418
+ ]),
419
+ },
420
+ });
421
+ process.env.BIZA_APP_SKIP_PARSE = "1";
422
+
423
+ try {
424
+ process.argv[1] = process.cwd() + "\\bin";
425
+ const { addApp } = await import("../bin/app.js");
426
+
427
+ const result = await addApp({
428
+ workingDir,
429
+ verbose: false,
430
+ server: "https://a.b.c",
431
+ apiPort: 1205,
432
+ dbIndex: 2,
433
+ files: [{ name: "applicationConfig.js" }],
434
+ sub: "metadata-sub",
435
+ body: {
436
+ scripts: {
437
+ "applicationConfig.js":
438
+ 'get = function () { return { metadata: { name: "Name From Body Must Be Ignored" } }; }',
439
+ },
440
+ },
441
+ });
442
+
443
+ expect(result.success).toBe(true);
444
+ expect(axios.post).toHaveBeenCalledTimes(1);
445
+ expect(axios.request).toHaveBeenCalledTimes(1);
446
+
447
+ const apiParams = axios.post.mock.calls[0][1]._parameters;
448
+ expect(apiParams[1]).toBe("bizasalesapp");
449
+ expect(apiParams[3]).toHaveProperty("name", "BizA Sales App");
450
+ } finally {
451
+ delete process.env.BIZA_APP_SKIP_PARSE;
452
+ }
453
+ });
454
+
455
+ it("shall use bundle name for upload when body is null even if application config has metadata name", async () => {
456
+ const appName = "metadataNameSourceApp";
457
+ const workingDir = mockDataFolder + "/" + appName;
458
+
459
+ mockValidTemplates(appName, {
460
+ "applicationConfig.js": {
461
+ act: 'get = function () { return { metadata: { name: "Name From Body Must Be Ignored" } }; }',
462
+ },
463
+ });
464
+ mockIssuerKeyResponse();
465
+ axios.request = jest.fn().mockResolvedValue({
466
+ data: {
467
+ data: JSON.stringify([
468
+ {
469
+ "SYS$CONFIG.NAME": "APPLICATION_CONFIG",
470
+ "SYS$CONFIG.DATA": JSON.stringify({
471
+ metadata: {
472
+ name: "BizA Sales App",
473
+ },
474
+ }),
475
+ },
476
+ ]),
477
+ },
478
+ });
479
+ process.env.BIZA_APP_SKIP_PARSE = "1";
480
+
481
+ try {
482
+ process.argv[1] = process.cwd() + "\\bin";
483
+ const { addApp } = await import("../bin/app.js");
484
+
485
+ const result = await addApp({
486
+ workingDir,
487
+ verbose: false,
488
+ server: "https://a.b.c",
489
+ apiPort: 1205,
490
+ dbIndex: 2,
491
+ files: [{ name: "applicationConfig.js" }],
492
+ sub: "metadata-sub",
493
+ });
494
+
495
+ expect(result.success).toBe(true);
496
+ expect(axios.post).toHaveBeenCalledTimes(1);
497
+ expect(axios.request).toHaveBeenCalledTimes(1);
498
+
499
+ const apiParams = axios.post.mock.calls[0][1]._parameters;
500
+ expect(apiParams[1]).toBe("metadatanamesourceapp");
501
+ } finally {
502
+ delete process.env.BIZA_APP_SKIP_PARSE;
503
+ }
504
+ });
505
+
506
+ it("shall remove hyphens from upload app name derived from bundle folder name", async () => {
507
+ const appName = "biz-a-insight";
508
+ const workingDir = mockDataFolder + "/" + appName;
509
+
510
+ mockValidTemplates(appName, {
511
+ "applicationConfig.js": {
512
+ act: 'get = function () { return { metadata: { name: "Ignored Name" } }; }',
513
+ },
514
+ });
515
+ mockIssuerKeyResponse();
516
+ process.env.BIZA_APP_SKIP_PARSE = "1";
517
+
518
+ try {
519
+ process.argv[1] = process.cwd() + "\\bin";
520
+ const { addApp } = await import("../bin/app.js");
521
+
522
+ const result = await addApp({
523
+ workingDir,
524
+ verbose: false,
525
+ server: "https://a.b.c",
526
+ apiPort: 1205,
527
+ dbIndex: 2,
528
+ files: [{ name: "applicationConfig.js" }],
529
+ sub: "metadata-sub",
530
+ });
531
+
532
+ expect(result.success).toBe(true);
533
+ expect(axios.post).toHaveBeenCalledTimes(1);
534
+
535
+ const apiParams = axios.post.mock.calls[0][1]._parameters;
536
+ expect(apiParams[1]).toBe("bizainsight");
537
+ } finally {
538
+ delete process.env.BIZA_APP_SKIP_PARSE;
539
+ }
540
+ });
541
+ /* it('Invalid script syntax', async ()=>{
542
+ mockValidTemplates({
543
+ 'a.js' : {act: 'get = function () {return {modelA: {}}'} // missing close closure at the end
544
+ })
379
545
  mockIssuerKeyResponse()
380
546
  await runCommand('add', '-s', 'https://a.b.c', '-p', '1205', '-i', '2', '-d', appFolderName, "-v")
381
547
 
@@ -383,7 +549,7 @@ describe('Biz-A Apps CLI', () => {
383
549
  expect(logSpy.mock.calls[0][0]).toBe('===================\nA.JS\n===================')
384
550
 
385
551
  expect(errorSpy.mock.calls.length).toBe(1)
386
- expect(errorSpy.mock.calls[0][0]).toBe('a.js : SyntaxError: Unexpected token: eof, expected: punc «}»')
552
+ expect(errorSpy.mock.calls[0][0]).toBe('a.js : SyntaxError: Unexpected token: eof, expected: punc «}»')
387
553
  })
388
554
 
389
555
  it('Failed to compile script with node sandbox and ES6 Import', async ()=>{
@@ -421,400 +587,622 @@ describe('Biz-A Apps CLI', () => {
421
587
  expect(errorSpy.mock.calls[0][0]).toBe('a.js : Failed to compile template script.\nPlease make sure the script is correct and not returning empty result') // ES6 import error
422
588
  }) */
423
589
 
424
- const errorHandlingTestCases = [
425
- // [test case name, scripts, expectedFn()]
426
- [
427
- 'Invalid script syntax',
428
- { act: 'get = function () {return {modelA: {}}' }, // missing close closure at the end',
429
- () => {
430
- expect(logSpy.mock.calls.length).toBe(JEST_MESSAGE_TOTAL + 5)
431
- expect(logSpy.mock.calls[JEST_MESSAGE_TOTAL + 0][0]).toBe('===================\nA.JS\n===================')
432
- expect(logSpy.mock.calls[JEST_MESSAGE_TOTAL + 1][0]).toBe('Running script with VM') // node sandbox (VN) error as console.log
433
- expect(logSpy.mock.calls[JEST_MESSAGE_TOTAL + 2][0]).toStrictEqual('a.js:1:38: SyntaxError: Unexpected token: eof, expected: punc «}»')
434
- expect(logSpy.mock.calls[JEST_MESSAGE_TOTAL + 3][0]).toStrictEqual('Running script with Import function')
435
- expect(logSpy.mock.calls[JEST_MESSAGE_TOTAL + 4][0]).toStrictEqual('===================\nEOF A.JS\n===================')
436
-
437
- expect(errorSpy.mock.calls.length).toBe(1)
438
- expect(errorSpy.mock.calls[0][0]).toBe('a.js : SyntaxError: Unexpected end of input')
439
- }
440
- ],
441
- [
442
- 'Failed to compile script with node sandbox and ES6 Import',
443
- { act: 'get = function () {return {modelA: {}}}\n modul.expor = get;\n' }, // wrong "module.export"
444
- () => {
445
- expect(logSpy.mock.calls.length).toBe(JEST_MESSAGE_TOTAL + 6)
446
- expect(logSpy.mock.calls[JEST_MESSAGE_TOTAL + 0][0]).toBe('===================\nA.JS\n===================')
447
- expect(logSpy.mock.calls[JEST_MESSAGE_TOTAL + 1][0]).toBe('Running script with VM') // node sandbox (VN) error as console.log
448
- expect(logSpy.mock.calls[JEST_MESSAGE_TOTAL + 2][0]).toBe('Minify : \nget=function(){return{modelA:{}}};modul.expor=get;')
449
- expect(logSpy.mock.calls[JEST_MESSAGE_TOTAL + 3][0]).toBe("a.js : ReferenceError: modul is not defined")
450
- expect(logSpy.mock.calls[JEST_MESSAGE_TOTAL + 4][0]).toBe('Running script with Import function')
451
- expect(logSpy.mock.calls[JEST_MESSAGE_TOTAL + 5][0]).toStrictEqual('===================\nEOF A.JS\n===================')
452
-
453
- expect(errorSpy.mock.calls.length).toBe(1)
454
- expect(errorSpy.mock.calls[0][0]).toStrictEqual('a.js : Error: Invalid data URI')// ES6 import error
455
- }
456
- ],
457
- [
458
- 'Shall not allow empty script',
459
- { act: 'get = function () {}' }, // empty script
460
- () => {
461
- expect(logSpy.mock.calls.length).toBe(JEST_MESSAGE_TOTAL + 5)
462
- expect(logSpy.mock.calls[JEST_MESSAGE_TOTAL + 0][0]).toBe('===================\nA.JS\n===================')
463
- expect(logSpy.mock.calls[JEST_MESSAGE_TOTAL + 1][0]).toBe('Running script with VM') // node sandbox (VN) error as console.log
464
- expect(logSpy.mock.calls[JEST_MESSAGE_TOTAL + 2][0]).toBe('Minify : \nget=function(){};')
465
- expect(logSpy.mock.calls[JEST_MESSAGE_TOTAL + 3][0]).toBe('Running script with Import function')
466
- expect(logSpy.mock.calls[JEST_MESSAGE_TOTAL + 4][0]).toStrictEqual('===================\nEOF A.JS\n===================')
467
-
468
- expect(errorSpy.mock.calls.length).toBe(1)
469
- expect(errorSpy.mock.calls[0][0]).toStrictEqual('a.js : Failed to compile template script.\nPlease make sure the script is correct and not returning empty result')
470
- }
471
- ],
472
- ]
473
- it.each(errorHandlingTestCases)('%p', async (name, scripts, expectedFn) => {
474
- const appName = 'errorHandlingTest' + errorHandlingTestCases.findIndex(testCase => testCase[0] === name)
475
- mockValidTemplates(appName, { 'a.js': scripts })
476
- mockIssuerKeyResponse()
477
- await runCommand('add', '-s', 'https://a.b.c', '-p', '1205', '-i', '2', '-d', mockDataFolder + '/' + appName, "-v")
478
- expectedFn()
479
- })
480
-
481
- it('Shall not allow app without script template', async () => {
482
- mockValidTemplates(expect.getState().currentTestName, {})
483
- mockIssuerKeyResponse()
484
- await runCommand('add', '-s', 'https://a.b.c', '-p', '1205', '-i', '2', '-d', mockDataFolder + '/' + expect.getState().currentTestName, "-v")
485
-
486
- expect(logSpy.mock.calls.length).toBe(JEST_MESSAGE_TOTAL + 0)
487
-
488
- expect(errorSpy.mock.calls.length).toBe(1)
489
- expect(errorSpy.mock.calls[0][0]).toBe('Nothing to upload. Please recheck your app folder.')
490
- })
491
-
492
- it('Shall recommend command', async () => {
493
- const exitSpy = jest.spyOn(process, 'exit').mockImplementation(() => { })
494
- await runCommand('Add', '-s', 'https://a.b.c', '-p', '1205', '-i', '2', '-d', mockDataFolder + '/NotExistFolder', "-v")
495
- expect(logSpy.mock.calls.length).toBe(0)
496
- expect(exitSpy).toHaveBeenCalledWith(1) // exit code = 1
497
- expect(errorSpy.mock.calls[errorSpy.mock.calls.length - 1][0]).toBe('Did you mean add?')
498
- })
499
-
500
- it('Shall have minimun one command', async () => {
501
- const exitSpy = jest.spyOn(process, 'exit').mockImplementation()
502
- await runCommand('-s', 'https://a.b.c', '-i', 2)
503
- expect(logSpy.mock.calls.length).toBe(0)
504
- expect(exitSpy).toHaveBeenCalledWith(1)
505
- expect(errorSpy.mock.calls[errorSpy.mock.calls.length - 1][0]).toBe('You need at least one command before moving on')
506
- })
507
-
508
- it('Shall use strict command and options', async () => {
509
- const exitSpy = jest.spyOn(process, 'exit').mockImplementation()
510
- await runCommand('Unknown Command', '-s', 'https://a.b.c', '-i', 2)
511
- expect(logSpy.mock.calls.length).toBe(0)
512
- expect(exitSpy).toHaveBeenCalledWith(1)
513
- expect(errorSpy.mock.calls[errorSpy.mock.calls.length - 1][0]).toBe('Unknown argument: Unknown Command')
514
- })
515
-
516
- describe('run Jest', () => { //SCY BZ 4331
517
- const mockScripts = {
518
- 'a.js': {
519
- act: 'get = function () {return {modelA: {}}}',
520
- exp: '{"modelA":{}}'
521
- }
522
- }
523
- const appName = 'compressStressTest1';
524
- const fullTestPath = mockDataFolder + appName;
525
- const absoluteFullTestPath = path.resolve(fullTestPath);
526
-
527
- let jestArgs = ['--no-install', 'jest', '--json'];
528
- let jestArgsWithPassNoTest = jestArgs.slice();
529
- jestArgsWithPassNoTest.push('--passWithNoTests');
530
-
531
- async function runJest() {
532
- mockIssuerKeyResponse();
533
- await runCommand('add', '-s', 'https://a.b.c', '-p', '1205', '-i', '2', '-d', fullTestPath)
534
- }
535
-
536
- function continueExpect(args) {
537
- expect(process.cwd()).toBe(absoluteFullTestPath);
538
- if (process.platform === "win32") {
539
- expect(child_process.spawn).toBeCalledWith("cmd.exe", ["/d", "/s", "/c", "npx", ...args]);
540
- } else {
541
- expect(child_process.spawn).toBeCalledWith("npx", args);
542
- }
543
- expect(logSpy.mock.calls[logSpy.mock.calls.length - JEST_MESSAGE_TOTAL - 2][0]).toBe('====================');
544
- expect(logSpy.mock.calls[logSpy.mock.calls.length - JEST_MESSAGE_TOTAL - 1][0]).toBe(jestMessage);
545
- expect(logSpy.mock.calls[logSpy.mock.calls.length - JEST_MESSAGE_TOTAL][0]).toBe('====================');
546
- expect(errorSpy.mock.calls.length).toBe(0);
547
- }
548
-
549
- function abortExpect(args) {
550
- expect(process.cwd()).toBe(absoluteFullTestPath);
551
- if (process.platform === "win32") {
552
- expect(child_process.spawn).toBeCalledWith("cmd.exe", ["/d", "/s", "/c", "npx", ...args]);
553
- } else {
554
- expect(child_process.spawn).toBeCalledWith("npx", args);
555
- }
556
- expect(logSpy.mock.calls[logSpy.mock.calls.length - JEST_MESSAGE_TOTAL][0]).toBe('====================');
557
- expect(logSpy.mock.calls[logSpy.mock.calls.length - JEST_MESSAGE_TOTAL + 1][0]).toBe(jestMessage);
558
- expect(logSpy.mock.calls[logSpy.mock.calls.length - JEST_MESSAGE_TOTAL + 2][0]).toBe('====================');
559
- expect(errorSpy.mock.calls[errorSpy.mock.calls.length - 1][0]).toBe('Biz-A Add aborted');
560
- }
561
-
562
- it('Shall continue add biz a template, jest found, folder test found, unit test found, unit test passed', async () => {
563
- returnedCode = 0;
564
- jestMessage = 'Passed';
565
-
566
- mockValidTemplates(appName, mockScripts);
567
- fs.mkdirSync(fullTestPath + '/test', { recursive: true })
568
- fs.writeFileSync(fullTestPath + '/test/a.test.js', `it('dummy test', () => {
590
+ const errorHandlingTestCases = [
591
+ // [test case name, scripts, expectedFn()]
592
+ [
593
+ "Invalid script syntax",
594
+ { act: "get = function () {return {modelA: {}}" }, // missing close closure at the end',
595
+ () => {
596
+ expect(logSpy.mock.calls.length).toBe(
597
+ JEST_MESSAGE_TOTAL + 5,
598
+ );
599
+ expect(logSpy.mock.calls[JEST_MESSAGE_TOTAL + 0][0]).toBe(
600
+ "===================\nA.JS\n===================",
601
+ );
602
+ expect(logSpy.mock.calls[JEST_MESSAGE_TOTAL + 1][0]).toBe(
603
+ "Running script with VM",
604
+ ); // node sandbox (VN) error as console.log
605
+ const vmSyntaxError =
606
+ logSpy.mock.calls[JEST_MESSAGE_TOTAL + 2][0];
607
+ expect(vmSyntaxError).toContain(
608
+ "a.js:1:38: SyntaxError: Unexpected token: eof, expected: punc",
609
+ );
610
+ expect(vmSyntaxError).toContain("}");
611
+ expect(
612
+ logSpy.mock.calls[JEST_MESSAGE_TOTAL + 3][0],
613
+ ).toStrictEqual("Running script with Import function");
614
+ expect(
615
+ logSpy.mock.calls[JEST_MESSAGE_TOTAL + 4][0],
616
+ ).toStrictEqual(
617
+ "===================\nEOF A.JS\n===================",
618
+ );
619
+
620
+ expect(errorSpy.mock.calls.length).toBe(1);
621
+ expect(errorSpy.mock.calls[0][0]).toBe(
622
+ "a.js : SyntaxError: Unexpected end of input",
623
+ );
624
+ },
625
+ ],
626
+ [
627
+ "Failed to compile script with node sandbox and ES6 Import",
628
+ {
629
+ act: "get = function () {return {modelA: {}}}\n modul.expor = get;\n",
630
+ }, // wrong "module.export"
631
+ () => {
632
+ expect(logSpy.mock.calls.length).toBe(
633
+ JEST_MESSAGE_TOTAL + 6,
634
+ );
635
+ expect(logSpy.mock.calls[JEST_MESSAGE_TOTAL + 0][0]).toBe(
636
+ "===================\nA.JS\n===================",
637
+ );
638
+ expect(logSpy.mock.calls[JEST_MESSAGE_TOTAL + 1][0]).toBe(
639
+ "Running script with VM",
640
+ ); // node sandbox (VN) error as console.log
641
+ expect(logSpy.mock.calls[JEST_MESSAGE_TOTAL + 2][0]).toBe(
642
+ "Minify : \nget=function(){return{modelA:{}}};modul.expor=get;",
643
+ );
644
+ expect(logSpy.mock.calls[JEST_MESSAGE_TOTAL + 3][0]).toBe(
645
+ "a.js : ReferenceError: modul is not defined",
646
+ );
647
+ expect(logSpy.mock.calls[JEST_MESSAGE_TOTAL + 4][0]).toBe(
648
+ "Running script with Import function",
649
+ );
650
+ expect(
651
+ logSpy.mock.calls[JEST_MESSAGE_TOTAL + 5][0],
652
+ ).toStrictEqual(
653
+ "===================\nEOF A.JS\n===================",
654
+ );
655
+
656
+ expect(errorSpy.mock.calls.length).toBe(1);
657
+ expect(errorSpy.mock.calls[0][0]).toStrictEqual(
658
+ "a.js : Error: Invalid data URI",
659
+ ); // ES6 import error
660
+ },
661
+ ],
662
+ [
663
+ "Shall not allow empty script",
664
+ { act: "get = function () {}" }, // empty script
665
+ () => {
666
+ expect(logSpy.mock.calls.length).toBe(
667
+ JEST_MESSAGE_TOTAL + 5,
668
+ );
669
+ expect(logSpy.mock.calls[JEST_MESSAGE_TOTAL + 0][0]).toBe(
670
+ "===================\nA.JS\n===================",
671
+ );
672
+ expect(logSpy.mock.calls[JEST_MESSAGE_TOTAL + 1][0]).toBe(
673
+ "Running script with VM",
674
+ ); // node sandbox (VN) error as console.log
675
+ expect(logSpy.mock.calls[JEST_MESSAGE_TOTAL + 2][0]).toBe(
676
+ "Minify : \nget=function(){};",
677
+ );
678
+ expect(logSpy.mock.calls[JEST_MESSAGE_TOTAL + 3][0]).toBe(
679
+ "Running script with Import function",
680
+ );
681
+ expect(
682
+ logSpy.mock.calls[JEST_MESSAGE_TOTAL + 4][0],
683
+ ).toStrictEqual(
684
+ "===================\nEOF A.JS\n===================",
685
+ );
686
+
687
+ expect(errorSpy.mock.calls.length).toBe(1);
688
+ expect(errorSpy.mock.calls[0][0]).toStrictEqual(
689
+ "a.js : Failed to compile template script.\nPlease make sure the script is correct and not returning empty result",
690
+ );
691
+ },
692
+ ],
693
+ ];
694
+ it.each(errorHandlingTestCases)(
695
+ "%p",
696
+ async (name, scripts, expectedFn) => {
697
+ const appName =
698
+ "errorHandlingTest" +
699
+ errorHandlingTestCases.findIndex(
700
+ (testCase) => testCase[0] === name,
701
+ );
702
+ mockValidTemplates(appName, { "a.js": scripts });
703
+ mockIssuerKeyResponse();
704
+ await runCommand(
705
+ "add",
706
+ "-s",
707
+ "https://a.b.c",
708
+ "-p",
709
+ "1205",
710
+ "-i",
711
+ "2",
712
+ "-d",
713
+ mockDataFolder + "/" + appName,
714
+ "-v",
715
+ );
716
+ expectedFn();
717
+ },
718
+ );
719
+
720
+ it("Shall not allow app without script template", async () => {
721
+ mockValidTemplates(expect.getState().currentTestName, {});
722
+ mockIssuerKeyResponse();
723
+ await runCommand(
724
+ "add",
725
+ "-s",
726
+ "https://a.b.c",
727
+ "-p",
728
+ "1205",
729
+ "-i",
730
+ "2",
731
+ "-d",
732
+ mockDataFolder + "/" + expect.getState().currentTestName,
733
+ "-v",
734
+ );
735
+
736
+ expect(logSpy.mock.calls.length).toBe(JEST_MESSAGE_TOTAL + 0);
737
+
738
+ expect(errorSpy.mock.calls.length).toBe(1);
739
+ expect(errorSpy.mock.calls[0][0]).toBe(
740
+ "Nothing to upload. Please recheck your app folder.",
741
+ );
742
+ });
743
+
744
+ it("Shall recommend command", async () => {
745
+ const exitSpy = jest
746
+ .spyOn(process, "exit")
747
+ .mockImplementation(() => {});
748
+ await runCommand(
749
+ "Add",
750
+ "-s",
751
+ "https://a.b.c",
752
+ "-p",
753
+ "1205",
754
+ "-i",
755
+ "2",
756
+ "-d",
757
+ mockDataFolder + "/NotExistFolder",
758
+ "-v",
759
+ );
760
+ expect(logSpy.mock.calls.length).toBe(0);
761
+ expect(exitSpy).toHaveBeenCalledWith(1); // exit code = 1
762
+ expect(errorSpy.mock.calls[errorSpy.mock.calls.length - 1][0]).toBe(
763
+ "Did you mean add?",
764
+ );
765
+ });
766
+
767
+ it("Shall have minimun one command", async () => {
768
+ const exitSpy = jest.spyOn(process, "exit").mockImplementation();
769
+ await runCommand("-s", "https://a.b.c", "-i", 2);
770
+ expect(logSpy.mock.calls.length).toBe(0);
771
+ expect(exitSpy).toHaveBeenCalledWith(1);
772
+ expect(errorSpy.mock.calls[errorSpy.mock.calls.length - 1][0]).toBe(
773
+ "You need at least one command before moving on",
774
+ );
775
+ });
776
+
777
+ it("Shall use strict command and options", async () => {
778
+ const exitSpy = jest.spyOn(process, "exit").mockImplementation();
779
+ await runCommand("Unknown Command", "-s", "https://a.b.c", "-i", 2);
780
+ expect(logSpy.mock.calls.length).toBe(0);
781
+ expect(exitSpy).toHaveBeenCalledWith(1);
782
+ expect(errorSpy.mock.calls[errorSpy.mock.calls.length - 1][0]).toBe(
783
+ "Unknown argument: Unknown Command",
784
+ );
785
+ });
786
+
787
+ describe("run Jest", () => {
788
+ //SCY BZ 4331
789
+ const mockScripts = {
790
+ "a.js": {
791
+ act: "get = function () {return {modelA: {}}}",
792
+ exp: '{"modelA":{}}',
793
+ },
794
+ };
795
+ const appName = "compressStressTest1";
796
+ const fullTestPath = mockDataFolder + appName;
797
+ const absoluteFullTestPath = path.resolve(fullTestPath);
798
+
799
+ let jestArgs = ["--no-install", "jest", "--json"];
800
+ let jestArgsWithPassNoTest = jestArgs.slice();
801
+ jestArgsWithPassNoTest.push("--passWithNoTests");
802
+
803
+ async function runJest() {
804
+ mockIssuerKeyResponse();
805
+ await runCommand(
806
+ "add",
807
+ "-s",
808
+ "https://a.b.c",
809
+ "-p",
810
+ "1205",
811
+ "-i",
812
+ "2",
813
+ "-d",
814
+ fullTestPath,
815
+ );
816
+ }
817
+
818
+ function continueExpect(args) {
819
+ expect(process.cwd()).toBe(absoluteFullTestPath);
820
+ if (process.platform === "win32") {
821
+ expect(child_process.spawn).toBeCalledWith("cmd.exe", [
822
+ "/d",
823
+ "/s",
824
+ "/c",
825
+ "npx",
826
+ ...args,
827
+ ]);
828
+ } else {
829
+ expect(child_process.spawn).toBeCalledWith("npx", args);
830
+ }
831
+ expect(
832
+ logSpy.mock.calls[
833
+ logSpy.mock.calls.length - JEST_MESSAGE_TOTAL - 2
834
+ ][0],
835
+ ).toBe("====================");
836
+ expect(
837
+ logSpy.mock.calls[
838
+ logSpy.mock.calls.length - JEST_MESSAGE_TOTAL - 1
839
+ ][0],
840
+ ).toBe(jestMessage);
841
+ expect(
842
+ logSpy.mock.calls[
843
+ logSpy.mock.calls.length - JEST_MESSAGE_TOTAL
844
+ ][0],
845
+ ).toBe("====================");
846
+ expect(errorSpy.mock.calls.length).toBe(0);
847
+ }
848
+
849
+ function abortExpect(args) {
850
+ expect(process.cwd()).toBe(absoluteFullTestPath);
851
+ if (process.platform === "win32") {
852
+ expect(child_process.spawn).toBeCalledWith("cmd.exe", [
853
+ "/d",
854
+ "/s",
855
+ "/c",
856
+ "npx",
857
+ ...args,
858
+ ]);
859
+ } else {
860
+ expect(child_process.spawn).toBeCalledWith("npx", args);
861
+ }
862
+ expect(
863
+ logSpy.mock.calls[
864
+ logSpy.mock.calls.length - JEST_MESSAGE_TOTAL
865
+ ][0],
866
+ ).toBe("====================");
867
+ expect(
868
+ logSpy.mock.calls[
869
+ logSpy.mock.calls.length - JEST_MESSAGE_TOTAL + 1
870
+ ][0],
871
+ ).toBe(jestMessage);
872
+ expect(
873
+ logSpy.mock.calls[
874
+ logSpy.mock.calls.length - JEST_MESSAGE_TOTAL + 2
875
+ ][0],
876
+ ).toBe("====================");
877
+ expect(
878
+ errorSpy.mock.calls[errorSpy.mock.calls.length - 1][0],
879
+ ).toBe("Biz-A Add aborted");
880
+ }
881
+
882
+ it("Shall continue add biz a template, jest found, folder test found, unit test found, unit test passed", async () => {
883
+ returnedCode = 0;
884
+ jestMessage = "Passed";
885
+
886
+ mockValidTemplates(appName, mockScripts);
887
+ fs.mkdirSync(fullTestPath + "/test", { recursive: true });
888
+ fs.writeFileSync(
889
+ fullTestPath + "/test/a.test.js",
890
+ `it('dummy test', () => {
569
891
  expect(true).toBe(true);
570
- })`)
571
-
572
- await runJest();
573
- continueExpect(jestArgs);
574
- })
575
-
576
- it('Shall abort add biz a template, jest found, folder test found, unit test found, unit test failed', async () => {
577
- returnedCode = 1;
578
- jestMessage = 'FAIL';
579
-
580
- mockValidTemplates(appName, mockScripts);
581
- fs.mkdirSync(fullTestPath + '/test', { recursive: true })
582
- fs.writeFileSync(fullTestPath + '/test/a.test.js', `it('dummy test', () => {
892
+ })`,
893
+ );
894
+
895
+ await runJest();
896
+ continueExpect(jestArgs);
897
+ });
898
+
899
+ it("Shall abort add biz a template, jest found, folder test found, unit test found, unit test failed", async () => {
900
+ returnedCode = 1;
901
+ jestMessage = "FAIL";
902
+
903
+ mockValidTemplates(appName, mockScripts);
904
+ fs.mkdirSync(fullTestPath + "/test", { recursive: true });
905
+ fs.writeFileSync(
906
+ fullTestPath + "/test/a.test.js",
907
+ `it('dummy test', () => {
583
908
  expect(true).toBe(false);
584
- })`)
909
+ })`,
910
+ );
585
911
 
586
- await runJest();
912
+ await runJest();
587
913
 
588
- abortExpect(jestArgs);
589
- })
914
+ abortExpect(jestArgs);
915
+ });
590
916
 
591
- it('Shall continue add biz a template, jest found, folder test found, no unit test', async () => {
592
- returnedCode = 1;
593
- jestMessage = 'No tests found';
917
+ it("Shall continue add biz a template, jest found, folder test found, no unit test", async () => {
918
+ returnedCode = 1;
919
+ jestMessage = "No tests found";
594
920
 
595
- mockValidTemplates(appName, mockScripts);
596
- fs.mkdirSync(fullTestPath + '/test', { recursive: true })
921
+ mockValidTemplates(appName, mockScripts);
922
+ fs.mkdirSync(fullTestPath + "/test", { recursive: true });
597
923
 
598
- await runJest();
924
+ await runJest();
599
925
 
600
- continueExpect(jestArgs);
601
- })
926
+ continueExpect(jestArgs);
927
+ });
602
928
 
603
- it('Shall continue add biz a template, jest found, no folder test', async () => {
604
- returnedCode = 0;
605
- jestMessage = 'No tests found';
929
+ it("Shall continue add biz a template, jest found, no folder test", async () => {
930
+ returnedCode = 0;
931
+ jestMessage = "No tests found";
606
932
 
607
- mockValidTemplates(appName, mockScripts);
608
- await runJest();
933
+ mockValidTemplates(appName, mockScripts);
934
+ await runJest();
609
935
 
610
- continueExpect(jestArgsWithPassNoTest);
611
- })
936
+ continueExpect(jestArgsWithPassNoTest);
937
+ });
612
938
 
613
- it('Shall abort add biz a template, no jest found, folder test found, unit test found, unit test passed', async () => {
614
- returnedCode = 1;
615
- jestMessage = 'missing packages jest';
939
+ it("Shall abort add biz a template, no jest found, folder test found, unit test found, unit test passed", async () => {
940
+ returnedCode = 1;
941
+ jestMessage = "missing packages jest";
616
942
 
617
- mockValidTemplates(appName, mockScripts);
618
- fs.mkdirSync(fullTestPath + '/test', { recursive: true })
619
- fs.writeFileSync(fullTestPath + '/test/a.test.js', `it('dummy test', () => {
943
+ mockValidTemplates(appName, mockScripts);
944
+ fs.mkdirSync(fullTestPath + "/test", { recursive: true });
945
+ fs.writeFileSync(
946
+ fullTestPath + "/test/a.test.js",
947
+ `it('dummy test', () => {
620
948
  expect(true).toBe(true);
621
- })`)
949
+ })`,
950
+ );
622
951
 
623
- await runJest();
952
+ await runJest();
624
953
 
625
- abortExpect(jestArgs);
626
- })
954
+ abortExpect(jestArgs);
955
+ });
627
956
 
628
- it('Shall abort add biz a template, no jest found, folder test found, unit test found, unit test failed', async () => {
629
- returnedCode = 1;
630
- jestMessage = 'missing packages jest';
957
+ it("Shall abort add biz a template, no jest found, folder test found, unit test found, unit test failed", async () => {
958
+ returnedCode = 1;
959
+ jestMessage = "missing packages jest";
631
960
 
632
- mockValidTemplates(appName, mockScripts);
633
- fs.mkdirSync(fullTestPath + '/test', { recursive: true })
634
- fs.writeFileSync(fullTestPath + '/test/a.test.js', `it('dummy test', () => {
961
+ mockValidTemplates(appName, mockScripts);
962
+ fs.mkdirSync(fullTestPath + "/test", { recursive: true });
963
+ fs.writeFileSync(
964
+ fullTestPath + "/test/a.test.js",
965
+ `it('dummy test', () => {
635
966
  expect(true).toBe(false);
636
- })`)
637
-
638
- await runJest();
639
-
640
- abortExpect(jestArgs);
641
- })
642
-
643
- it('Shall abort add biz a template, no jest found, folder test found, no unit test', async () => {
644
- returnedCode = 1;
645
- jestMessage = 'missing packages jest';
646
-
647
- mockValidTemplates(appName, mockScripts);
648
- fs.mkdirSync(fullTestPath + '/test', { recursive: true })
649
-
650
- await runJest();
651
-
652
- abortExpect(jestArgs);
653
- })
654
-
655
- it('Shall continue add biz a template, no jest found, no folder test', async () => {
656
- returnedCode = 1;
657
- jestMessage = 'missing packages jest';
658
-
659
- mockValidTemplates(appName, mockScripts);
660
- await runJest();
661
-
662
- continueExpect(jestArgsWithPassNoTest);
663
- })
664
-
665
- it('Shall continue add biz a template, stdout exists and must be consumed for many unit test outputs', async () => {
666
- returnedCode = 0;
667
- const stdoutChunks = Array(300).fill('Passed');
668
- let stdoutConsumed = false;
669
-
670
- child_process.spawn.mockImplementationOnce(() => ({
671
- on: jest.fn((event, callback) => {
672
- if ((event === 'close') && stdoutConsumed) callback(returnedCode)
673
- }),
674
- stderr: {
675
- on: jest.fn()
676
- },
677
- stdout: {
678
- on: jest.fn((event, callback) => {
679
- if (event === 'data') {
680
- stdoutConsumed = true;
681
- for (const chunk of stdoutChunks) {
682
- callback(chunk)
683
- }
684
- }
685
- })
686
- }
687
- }))
688
-
689
- mockValidTemplates(appName, mockScripts);
690
- fs.mkdirSync(fullTestPath + '/test', { recursive: true })
691
- fs.writeFileSync(fullTestPath + '/test/a.test.js', `it('dummy test', () => {
967
+ })`,
968
+ );
969
+
970
+ await runJest();
971
+
972
+ abortExpect(jestArgs);
973
+ });
974
+
975
+ it("Shall abort add biz a template, no jest found, folder test found, no unit test", async () => {
976
+ returnedCode = 1;
977
+ jestMessage = "missing packages jest";
978
+
979
+ mockValidTemplates(appName, mockScripts);
980
+ fs.mkdirSync(fullTestPath + "/test", { recursive: true });
981
+
982
+ await runJest();
983
+
984
+ abortExpect(jestArgs);
985
+ });
986
+
987
+ it("Shall continue add biz a template, no jest found, no folder test", async () => {
988
+ returnedCode = 1;
989
+ jestMessage = "missing packages jest";
990
+
991
+ mockValidTemplates(appName, mockScripts);
992
+ await runJest();
993
+
994
+ continueExpect(jestArgsWithPassNoTest);
995
+ });
996
+
997
+ it("Shall continue add biz a template, stdout exists and must be consumed for many unit test outputs", async () => {
998
+ returnedCode = 0;
999
+ const stdoutChunks = Array(300).fill("Passed");
1000
+ let stdoutConsumed = false;
1001
+
1002
+ child_process.spawn.mockImplementationOnce(() => ({
1003
+ on: jest.fn((event, callback) => {
1004
+ if (event === "close" && stdoutConsumed)
1005
+ callback(returnedCode);
1006
+ }),
1007
+ stderr: {
1008
+ on: jest.fn(),
1009
+ },
1010
+ stdout: {
1011
+ on: jest.fn((event, callback) => {
1012
+ if (event === "data") {
1013
+ stdoutConsumed = true;
1014
+ for (const chunk of stdoutChunks) {
1015
+ callback(chunk);
1016
+ }
1017
+ }
1018
+ }),
1019
+ },
1020
+ }));
1021
+
1022
+ mockValidTemplates(appName, mockScripts);
1023
+ fs.mkdirSync(fullTestPath + "/test", { recursive: true });
1024
+ fs.writeFileSync(
1025
+ fullTestPath + "/test/a.test.js",
1026
+ `it('dummy test', () => {
692
1027
  expect(true).toBe(true);
693
- })`)
694
-
695
- await runJest();
696
-
697
- expect(stdoutConsumed).toBe(true);
698
- expect(process.cwd()).toBe(absoluteFullTestPath);
699
- if (process.platform === "win32") {
700
- expect(child_process.spawn).toBeCalledWith("cmd.exe", ["/d", "/s", "/c", "npx", ...jestArgs]);
701
- } else {
702
- expect(child_process.spawn).toBeCalledWith("npx", jestArgs);
703
- }
704
- expect(logSpy.mock.calls[logSpy.mock.calls.length - JEST_MESSAGE_TOTAL - 2][0]).toBe('====================');
705
- expect(logSpy.mock.calls[logSpy.mock.calls.length - JEST_MESSAGE_TOTAL - 1][0]).toBe('');
706
- expect(logSpy.mock.calls[logSpy.mock.calls.length - JEST_MESSAGE_TOTAL][0]).toBe('====================');
707
- expect(errorSpy.mock.calls.length).toBe(0);
708
- })
709
- })
710
- })
711
-
712
- describe('Remove App', () => {
713
- it('shall remove specific apps', async () => {
714
- axios.post.mockResolvedValue({ data: { success: true }, status: 200 })
715
-
716
- await runCommand('remove', '-s', 'https://finaapi.imamatek.com', '-p', '1205', '-i', '1', '-n', 'myApp')
717
-
718
- expect(axios.post.mock.calls).toHaveLength(1)
719
- expect(axios.post).toHaveBeenCalledWith(
720
- 'https://finaapi.imamatek.com:1205/fina/rest/TOrmMethod/%22deleteApp%22',
721
- { _parameters: [1, 'myapp'] },
722
- { headers: { 'Content-Type': 'text/plain' } }
723
- )
724
- expect(logSpy).toHaveBeenCalledWith('myapp removed')
725
- })
726
-
727
- it('shall remove multiple apps', async () => {
728
- axios.post.mockResolvedValue({ data: { success: true, _f: 'notExistApp' }, status: 200 })
729
-
730
- await runCommand('remove', '-i', '2', '-s', 'https://www.AA.io', '-p', '2708', '-n', 'firstApp, notExistApp, secondApp')
731
-
732
- expect(axios.post.mock.calls).toHaveLength(1)
733
- expect(axios.post).toHaveBeenCalledWith(
734
- 'https://www.AA.io:2708/fina/rest/TOrmMethod/%22deleteApp%22',
735
- { _parameters: [2, 'firstapp,notexistapp,secondapp'] },
736
- { headers: { 'Content-Type': 'text/plain' } }
737
- )
738
- expect(logSpy).toHaveBeenCalledWith('firstapp removed')
739
- expect(logSpy).toHaveBeenCalledWith('secondapp removed')
740
- expect(logSpy).toHaveBeenCalledWith('notexistapp not found')
741
- })
742
-
743
- it('shall remove all apps', async () => {
744
- axios.post.mockResolvedValue({ data: { success: true }, _f: '', status: 200 })
745
-
746
- await runCommand('remove', '-s', 'https://finaapi.imamatek.com', '-p', '1205', '-i', '2')
747
-
748
- expect(axios.post.mock.calls).toHaveLength(1)
749
- expect(axios.post).toHaveBeenCalledWith(
750
- 'https://finaapi.imamatek.com:1205/fina/rest/TOrmMethod/%22deleteApp%22',
751
- { _parameters: [2, ''] },
752
- { headers: { 'Content-Type': 'text/plain' } }
753
- )
754
- expect(logSpy).toHaveBeenCalledWith('All apps removed')
755
- })
756
- })
757
-
758
- it('Shall have options', async () => {
759
- jest.spyOn(process, 'exit').mockImplementation()
760
- const appModule = await import('../bin/app.js')
761
- expect(appModule.options).toStrictEqual({
762
- "s": {
763
- alias: "server",
764
- describe: `API or Server URL (ex: ${env.BIZA_SERVER_LINK} or http://192.168.1.1 or https://finaapi.imamatek.com)`,
765
- type: "string",
766
- demandOption: true,
767
- default: 'http://localhost'
768
- },
769
- "i": {
770
- alias: "dbIndex",
771
- default: 2,
772
- describe: "database index",
773
- type: "number",
774
- demandOption: false
775
- },
776
- "sub": {
777
- alias: "subdomain",
778
- describe: "Subdomain",
779
- type: "string",
780
- demandOption: false
781
- },
782
- "p": {
783
- alias: "apiPort",
784
- default: 212,
785
- describe: "FINA API Port",
786
- type: "number",
787
- demandOption: false,
788
- }
789
- })
790
- expect(appModule.addCommandOptions).toStrictEqual({
791
- 'd': {
792
- alias: "workingDir",
793
- describe: "Path to templates directory",
794
- type: "string",
795
- demandOption: false,
796
- default: process.cwd(),
797
- },
798
- 'v': {
799
- alias: "verbose",
800
- describe: "Print info to console",
801
- type: "boolean",
802
- demandOption: false,
803
- default: false,
804
- }
805
- })
806
- expect(appModule.removeCommandOptions).toStrictEqual({
807
- 'n': {
808
- alias: "appName",
809
- describe: "Application name",
810
- type: "string",
811
- demandOption: true,
812
- default: ""
813
- }
814
- })
815
- })
816
- })
817
-
818
-
819
-
820
-
1028
+ })`,
1029
+ );
1030
+
1031
+ await runJest();
1032
+
1033
+ expect(stdoutConsumed).toBe(true);
1034
+ expect(process.cwd()).toBe(absoluteFullTestPath);
1035
+ if (process.platform === "win32") {
1036
+ expect(child_process.spawn).toBeCalledWith("cmd.exe", [
1037
+ "/d",
1038
+ "/s",
1039
+ "/c",
1040
+ "npx",
1041
+ ...jestArgs,
1042
+ ]);
1043
+ } else {
1044
+ expect(child_process.spawn).toBeCalledWith("npx", jestArgs);
1045
+ }
1046
+ expect(
1047
+ logSpy.mock.calls[
1048
+ logSpy.mock.calls.length - JEST_MESSAGE_TOTAL - 2
1049
+ ][0],
1050
+ ).toBe("====================");
1051
+ expect(
1052
+ logSpy.mock.calls[
1053
+ logSpy.mock.calls.length - JEST_MESSAGE_TOTAL - 1
1054
+ ][0],
1055
+ ).toBe("");
1056
+ expect(
1057
+ logSpy.mock.calls[
1058
+ logSpy.mock.calls.length - JEST_MESSAGE_TOTAL
1059
+ ][0],
1060
+ ).toBe("====================");
1061
+ expect(errorSpy.mock.calls.length).toBe(0);
1062
+ });
1063
+ });
1064
+ });
1065
+
1066
+ describe("Remove App", () => {
1067
+ it("shall remove specific apps", async () => {
1068
+ axios.post.mockResolvedValue({
1069
+ data: { success: true },
1070
+ status: 200,
1071
+ });
1072
+
1073
+ await runCommand(
1074
+ "remove",
1075
+ "-s",
1076
+ "https://finaapi.imamatek.com",
1077
+ "-p",
1078
+ "1205",
1079
+ "-i",
1080
+ "1",
1081
+ "-n",
1082
+ "myApp",
1083
+ );
1084
+
1085
+ expect(axios.post.mock.calls).toHaveLength(1);
1086
+ expect(axios.post).toHaveBeenCalledWith(
1087
+ "https://finaapi.imamatek.com:1205/fina/rest/TOrmMethod/%22deleteApp%22",
1088
+ { _parameters: [1, "myapp"] },
1089
+ { headers: { "Content-Type": "text/plain" } },
1090
+ );
1091
+ expect(logSpy).toHaveBeenCalledWith("myapp removed");
1092
+ });
1093
+
1094
+ it("shall remove multiple apps", async () => {
1095
+ axios.post.mockResolvedValue({
1096
+ data: { success: true, _f: "notExistApp" },
1097
+ status: 200,
1098
+ });
1099
+
1100
+ await runCommand(
1101
+ "remove",
1102
+ "-i",
1103
+ "2",
1104
+ "-s",
1105
+ "https://www.AA.io",
1106
+ "-p",
1107
+ "2708",
1108
+ "-n",
1109
+ "firstApp, notExistApp, secondApp",
1110
+ );
1111
+
1112
+ expect(axios.post.mock.calls).toHaveLength(1);
1113
+ expect(axios.post).toHaveBeenCalledWith(
1114
+ "https://www.AA.io:2708/fina/rest/TOrmMethod/%22deleteApp%22",
1115
+ { _parameters: [2, "firstapp,notexistapp,secondapp"] },
1116
+ { headers: { "Content-Type": "text/plain" } },
1117
+ );
1118
+ expect(logSpy).toHaveBeenCalledWith("firstapp removed");
1119
+ expect(logSpy).toHaveBeenCalledWith("secondapp removed");
1120
+ expect(logSpy).toHaveBeenCalledWith("notexistapp not found");
1121
+ });
1122
+
1123
+ it("shall remove all apps", async () => {
1124
+ axios.post.mockResolvedValue({
1125
+ data: { success: true },
1126
+ _f: "",
1127
+ status: 200,
1128
+ });
1129
+
1130
+ await runCommand(
1131
+ "remove",
1132
+ "-s",
1133
+ "https://finaapi.imamatek.com",
1134
+ "-p",
1135
+ "1205",
1136
+ "-i",
1137
+ "2",
1138
+ );
1139
+
1140
+ expect(axios.post.mock.calls).toHaveLength(1);
1141
+ expect(axios.post).toHaveBeenCalledWith(
1142
+ "https://finaapi.imamatek.com:1205/fina/rest/TOrmMethod/%22deleteApp%22",
1143
+ { _parameters: [2, ""] },
1144
+ { headers: { "Content-Type": "text/plain" } },
1145
+ );
1146
+ expect(logSpy).toHaveBeenCalledWith("All apps removed");
1147
+ });
1148
+ });
1149
+
1150
+ it("Shall have options", async () => {
1151
+ jest.spyOn(process, "exit").mockImplementation();
1152
+ const appModule = await import("../bin/app.js");
1153
+ expect(appModule.options).toStrictEqual({
1154
+ s: {
1155
+ alias: "server",
1156
+ describe: `API or Server URL (ex: ${env.BIZA_SERVER_LINK} or http://192.168.1.1 or https://finaapi.imamatek.com)`,
1157
+ type: "string",
1158
+ demandOption: true,
1159
+ default: "http://localhost",
1160
+ },
1161
+ i: {
1162
+ alias: "dbIndex",
1163
+ default: 2,
1164
+ describe: "database index",
1165
+ type: "number",
1166
+ demandOption: false,
1167
+ },
1168
+ sub: {
1169
+ alias: "subdomain",
1170
+ describe: "Subdomain",
1171
+ type: "string",
1172
+ demandOption: false,
1173
+ },
1174
+ p: {
1175
+ alias: "apiPort",
1176
+ default: 212,
1177
+ describe: "FINA API Port",
1178
+ type: "number",
1179
+ demandOption: false,
1180
+ },
1181
+ });
1182
+ expect(appModule.addCommandOptions).toStrictEqual({
1183
+ d: {
1184
+ alias: "workingDir",
1185
+ describe: "Path to templates directory",
1186
+ type: "string",
1187
+ demandOption: false,
1188
+ default: process.cwd(),
1189
+ },
1190
+ v: {
1191
+ alias: "verbose",
1192
+ describe: "Print info to console",
1193
+ type: "boolean",
1194
+ demandOption: false,
1195
+ default: false,
1196
+ },
1197
+ });
1198
+ expect(appModule.removeCommandOptions).toStrictEqual({
1199
+ n: {
1200
+ alias: "appName",
1201
+ describe: "Application name",
1202
+ type: "string",
1203
+ demandOption: true,
1204
+ default: "",
1205
+ },
1206
+ });
1207
+ });
1208
+ });