biz-a-cli 2.3.53 → 2.3.55
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/bin/app.js +6 -5
- package/bin/directHubEvent.js +38 -46
- package/bin/hub.js +31 -23
- package/bin/hubEvent.js +67 -22
- package/package.json +4 -4
- package/readme.md +1 -1
- package/scheduler/timer.js +29 -17
- package/tests/app.test.js +8 -5
- package/tests/data.test.js +14 -0
- package/tests/hub.test.js +327 -16
- package/tests/mailCtl.test.js +5 -0
- package/tests/timer.test.js +101 -6
- package/tests/watcher.test.js +2 -0
package/bin/app.js
CHANGED
|
@@ -22,9 +22,10 @@ yargs(process.argv.slice(2))
|
|
|
22
22
|
})
|
|
23
23
|
.option("i", {
|
|
24
24
|
alias: "dbIndex",
|
|
25
|
+
default: 2,
|
|
25
26
|
describe: "database index",
|
|
26
27
|
type: "number",
|
|
27
|
-
demandOption:
|
|
28
|
+
demandOption: false
|
|
28
29
|
})
|
|
29
30
|
.option("sub", {
|
|
30
31
|
alias: "subdomain",
|
|
@@ -34,10 +35,10 @@ yargs(process.argv.slice(2))
|
|
|
34
35
|
})
|
|
35
36
|
.option("p", {
|
|
36
37
|
alias: "apiPort",
|
|
38
|
+
default : 212,
|
|
37
39
|
describe: "FINA API Port",
|
|
38
|
-
type: "
|
|
40
|
+
type: "number",
|
|
39
41
|
demandOption: false,
|
|
40
|
-
default : "212"
|
|
41
42
|
})
|
|
42
43
|
.command('add', 'Add Biz-A Application',
|
|
43
44
|
{
|
|
@@ -262,8 +263,8 @@ yargs(process.argv.slice(2))
|
|
|
262
263
|
console.error('Nothing to upload. Please recheck your app folder.')
|
|
263
264
|
}
|
|
264
265
|
} catch (e) {
|
|
265
|
-
|
|
266
|
-
console.error({e})
|
|
266
|
+
console.error(e.response?.data ? e.response.data : e)
|
|
267
|
+
// console.error({e})
|
|
267
268
|
}
|
|
268
269
|
}
|
|
269
270
|
|
package/bin/directHubEvent.js
CHANGED
|
@@ -1,57 +1,49 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { IDLE_SOCKET_TIMEOUT_MILLISECONDS } from './hubEvent.js'
|
|
1
|
+
import { apiRequestListener } from './hubEvent.js'
|
|
3
2
|
import { Tunnel as QuickTunnel } from 'cloudflared'
|
|
4
3
|
|
|
5
|
-
export async function localhostTunnel(port){
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
let url = await new Promise((resolve)=>{
|
|
12
|
-
let tunnelURL = ''
|
|
13
|
-
qt.once('url', (qtUrl)=>{tunnelURL=qtUrl})
|
|
14
|
-
qt.once('connected', (conn)=>{resolve(tunnelURL)})
|
|
15
|
-
qt.once('exit', (code)=>{resolve('')})
|
|
16
|
-
qt.once('error', (err)=>{resolve('')})
|
|
4
|
+
export async function localhostTunnel(port, notifier){
|
|
5
|
+
let tunnelUrl = ''
|
|
6
|
+
let qt = QuickTunnel.quick('127.0.0.1:'+port)
|
|
7
|
+
qt.on('url', (qtUrl)=>{
|
|
8
|
+
// console.log('qt url', qtUrl)
|
|
9
|
+
tunnelUrl = qtUrl
|
|
17
10
|
})
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
11
|
+
qt.on('connected', (conn)=>{
|
|
12
|
+
// console.log('qt connect', conn)
|
|
13
|
+
notifier(tunnelUrl)
|
|
14
|
+
console.log(`${new Date()}: Connected to Direct Hub at public URL`)
|
|
15
|
+
})
|
|
16
|
+
qt.on('disconnected', (conn)=>{
|
|
17
|
+
// console.log('qt disconnect', conn)
|
|
18
|
+
notifier('')
|
|
19
|
+
qt.stop
|
|
20
|
+
})
|
|
21
|
+
qt.on('error', (err)=>{
|
|
22
|
+
// console.log('qt err', err)
|
|
23
|
+
notifier('')
|
|
24
|
+
qt.stop
|
|
25
|
+
})
|
|
26
|
+
qt.on('exit', (code)=>{
|
|
27
|
+
// console.log('qt exit', code)
|
|
28
|
+
notifier('')
|
|
29
|
+
console.log(`${new Date()}: Direct Hub is not available, will try to reinitiate it in 5 minutes. Error code : `, code)
|
|
30
|
+
setTimeout(()=>{qt = localhostTunnel(port, notifier)}, 5*60000)
|
|
31
|
+
})
|
|
32
|
+
// qt.on('stdout', (data)=>{
|
|
33
|
+
// console.log('qt stdout', data)
|
|
34
|
+
// })
|
|
35
|
+
// qt.on('stderr', (data)=>{
|
|
36
|
+
// console.log('qt stderr', data)
|
|
37
|
+
// })
|
|
38
|
+
return qt
|
|
22
39
|
}
|
|
23
40
|
|
|
24
41
|
export function directHubEvent(serverSocket, argv){
|
|
25
42
|
serverSocket.on('connection', (clientSocket) => {
|
|
26
43
|
if (process.env.NODE_ENV !== 'production') {
|
|
27
|
-
console.log(
|
|
28
|
-
clientSocket.on('disconnect', (reason)=>{console.log(
|
|
44
|
+
console.log(clientSocket.id, 'connected')
|
|
45
|
+
clientSocket.on('disconnect', (reason)=>{console.log(`${clientSocket.id||'Direct Hub'} disconnected. Reason : ${reason}`)})
|
|
29
46
|
}
|
|
30
|
-
clientSocket
|
|
31
|
-
const socketResponse = (resp)=>{return {status: resp.status, statusText: resp.statusText, headers: resp.headers, body: resp.data, url: `${argv['hostname']}:${argv['port']+resp.config.url}`}}
|
|
32
|
-
if (argv['subdomain'].localeCompare(reqData.subDomain)==0) {
|
|
33
|
-
const apiAddress = `${argv['secure']==true ? 'https://' : 'http://'}${argv['hostname']}:${argv['port']}`
|
|
34
|
-
axios.request({
|
|
35
|
-
timeout: IDLE_SOCKET_TIMEOUT_MILLISECONDS,
|
|
36
|
-
baseURL: apiAddress,
|
|
37
|
-
url: reqData.path,
|
|
38
|
-
method: reqData.method,
|
|
39
|
-
headers: reqData.headers,
|
|
40
|
-
data: reqData.body,
|
|
41
|
-
// decompress: false, // if we need to interfered default Agent compression
|
|
42
|
-
responseType: reqData.responseType,
|
|
43
|
-
maxContentLength: Infinity,
|
|
44
|
-
})
|
|
45
|
-
.then(response=>{
|
|
46
|
-
resCB(null, socketResponse(response))
|
|
47
|
-
})
|
|
48
|
-
.catch(error=>{
|
|
49
|
-
resCB(error, null)
|
|
50
|
-
})
|
|
51
|
-
}
|
|
52
|
-
else {
|
|
53
|
-
resCB({status: 401, statusText: 'bad subdomain', url: `${argv['hostname']}:${argv['port']+reqData.path}`}, null)
|
|
54
|
-
}
|
|
55
|
-
})
|
|
47
|
+
apiRequestListener(clientSocket, argv)
|
|
56
48
|
})
|
|
57
49
|
}
|
package/bin/hub.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import yargs from 'yargs';
|
|
4
4
|
import { io as ioClient } from "socket.io-client";
|
|
5
|
-
import hubEvent from './hubEvent.js'
|
|
5
|
+
import { streamEvent, hubEvent } from './hubEvent.js'
|
|
6
6
|
import { createLogger, transports, format } from "winston";
|
|
7
7
|
import { Server as ioServer } from 'socket.io'
|
|
8
8
|
import os from 'node:os'
|
|
@@ -38,7 +38,7 @@ const argv = yargs(process.argv.slice(2))
|
|
|
38
38
|
default: 'https://biz-a.herokuapp.com',
|
|
39
39
|
describe: '(Required) Tunnel server endpoint',
|
|
40
40
|
type: 'string',
|
|
41
|
-
demandOption:
|
|
41
|
+
demandOption: false
|
|
42
42
|
})
|
|
43
43
|
.options('sub', {
|
|
44
44
|
alias: 'subdomain',
|
|
@@ -51,7 +51,7 @@ const argv = yargs(process.argv.slice(2))
|
|
|
51
51
|
default: '127.0.0.1',
|
|
52
52
|
describe: 'Address of API server for forwarding over socket-tunnel. Please emit "HTTP" or "HTTPS" from address',
|
|
53
53
|
type: 'string',
|
|
54
|
-
demandOption:
|
|
54
|
+
demandOption: false
|
|
55
55
|
})
|
|
56
56
|
.options('p', {
|
|
57
57
|
alias: 'port',
|
|
@@ -86,6 +86,12 @@ const argv = yargs(process.argv.slice(2))
|
|
|
86
86
|
type: 'number',
|
|
87
87
|
demandOption: false
|
|
88
88
|
})
|
|
89
|
+
.options('hub', {
|
|
90
|
+
default: 'https://biz-a-hub-ed77b066db87.herokuapp.com/',
|
|
91
|
+
describe: 'BizA hub',
|
|
92
|
+
type: 'string',
|
|
93
|
+
demandOption: false
|
|
94
|
+
})
|
|
89
95
|
.argv;
|
|
90
96
|
|
|
91
97
|
if (argv.help) {
|
|
@@ -122,7 +128,6 @@ app.set('args', argv);
|
|
|
122
128
|
|
|
123
129
|
app.use('/cb', runCliScript);
|
|
124
130
|
|
|
125
|
-
|
|
126
131
|
// create HTTP(s) Server
|
|
127
132
|
const keyFile = path.join(import.meta.dirname, "../cert/key.pem")
|
|
128
133
|
const certFile = path.join(import.meta.dirname, "../cert/cert.pem")
|
|
@@ -130,32 +135,35 @@ const rootFile = path.join(import.meta.dirname, "../cert/root.pem")
|
|
|
130
135
|
const isHttps = (fs.existsSync(keyFile) && fs.existsSync(certFile) && fs.existsSync(rootFile))
|
|
131
136
|
const getProtocol = ()=>(isHttps ? 'Https://' : 'Http://')
|
|
132
137
|
let server = isHttps ? https.createServer({key: fs.readFileSync(keyFile), cert: fs.readFileSync(certFile), ca: fs.readFileSync(rootFile),}, app) : http.createServer(app)
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
// publish CLI with tunnel
|
|
136
|
-
argv.publicUrl = (argv.publish==true) ? await localhostTunnel(argv.serverport) : ''
|
|
137
|
-
|
|
138
|
-
// prepare CLI Address
|
|
139
138
|
argv.cliAddress = ()=>{
|
|
140
139
|
const ip = Object.values(os.networkInterfaces()).flat().reduce((ip, {family, address, internal})=> ip || !internal && family==='IPv4' && address, undefined)
|
|
141
|
-
return {ip, port: argv.serverport, address: `${ip}:${argv.serverport}`, publicUrl: argv.
|
|
140
|
+
return {ip, port: argv.serverport, address: `${ip}:${argv.serverport}`, publicUrl: argv.connectedPublicUrl, hubUrl: argv.connectedHubUrl}
|
|
142
141
|
}
|
|
143
|
-
const cliAddress = argv.cliAddress()
|
|
144
142
|
server.listen(argv.serverport, () => {
|
|
145
|
-
|
|
143
|
+
const info = argv.cliAddress()
|
|
144
|
+
console.log(`${new Date()}: CLI is listening at ${getProtocol() + (process.env.HOST || info.ip || 'localhost')}:${info.port} `);
|
|
146
145
|
});
|
|
147
146
|
|
|
147
|
+
await streamEvent(ioClient(argv['server'], {query: {isSockStream: true }}), argv); // as socket client to BizA SERVER
|
|
148
148
|
|
|
149
|
-
//
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
directHubEvent(new ioServer(server, {cors: {origin: serverCORSOrigin}}), argv)
|
|
158
|
-
console.log(`${new Date()}: CLI is listening at "${cliAddress.publicUrl ? cliAddress.publicUrl : cliAddress.address}"`)
|
|
149
|
+
// as socket client to BizA HUB
|
|
150
|
+
if (argv.hub) {
|
|
151
|
+
hubEvent(ioClient(argv['hub'], { reconnectionDelay: 5*60*1000, reconnectionDelayMax: 5*60*1000, query: {isCLI: true, room: argv['subdomain']} }), argv, (url)=>{
|
|
152
|
+
if (url!==argv.connectedHubUrl) {
|
|
153
|
+
argv.connectedHubUrl = url
|
|
154
|
+
}
|
|
155
|
+
})
|
|
156
|
+
}
|
|
159
157
|
|
|
158
|
+
// as socket server from BizA Client
|
|
159
|
+
const serverCORSOrigin = ['https://biz-a.id', 'https://test.biz-a.id', /\.biz-a\.id$/].concat((process.env.NODE_ENV === 'production') ? [] : [`http://${argv.cliAddress().ip}:4200`, 'http://localhost:4200'])
|
|
160
|
+
directHubEvent(new ioServer(server, {cors: {origin: serverCORSOrigin}}), argv);
|
|
161
|
+
if (argv.publish==true) {
|
|
162
|
+
localhostTunnel(argv.serverport, (url)=>{ // publish CLI
|
|
163
|
+
if (url!==argv.connectedPublicUrl) {
|
|
164
|
+
argv.connectedPublicUrl = url
|
|
165
|
+
}
|
|
166
|
+
})
|
|
167
|
+
}
|
|
160
168
|
|
|
161
169
|
export { app }
|
package/bin/hubEvent.js
CHANGED
|
@@ -7,33 +7,19 @@ const ss = require('socket.io-stream'); //SCY: Temporary, next will be replaced
|
|
|
7
7
|
import { Transform } from 'node:stream'
|
|
8
8
|
// import { pipeline } from 'node:stream'
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
const IDLE_SOCKET_TIMEOUT_MILLISECONDS = 1000 * 30;
|
|
11
11
|
|
|
12
12
|
export const socketAgent = isUsingHttps=>(isUsingHttps==true) ? tls : net
|
|
13
13
|
|
|
14
|
-
export
|
|
14
|
+
export const streamEvent = async (socket, argv) => new Promise((resolve, reject) => {
|
|
15
15
|
|
|
16
16
|
const connectCb = () => {
|
|
17
|
-
// console.log(new Date() + ': connected to BizA Server');
|
|
18
|
-
// console.log(new Date() + ': requesting subdomain ' + argv['subdomain'] + ' via ' + argv['server']);
|
|
19
|
-
|
|
20
17
|
socket.emit('createTunnel', argv['subdomain'], (err) => {
|
|
21
18
|
if (err) {
|
|
22
19
|
console.log(new Date() + ': [error] ' + err);
|
|
23
20
|
reject(err);
|
|
24
21
|
} else {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
// // clean and concat requested url
|
|
28
|
-
// let url;
|
|
29
|
-
// // let subdomain = argv['subdomain'].toString();
|
|
30
|
-
// let server = argv['server'].toString();
|
|
31
|
-
|
|
32
|
-
// url = server;
|
|
33
|
-
|
|
34
|
-
// // resolve promise with requested URL
|
|
35
|
-
// resolve(url);
|
|
36
|
-
|
|
22
|
+
console.log(`${new Date()}: Connected to BizA Server at ${argv.server} using sub domain "${argv['subdomain']}"`)
|
|
37
23
|
resolve(argv['server'].toString());
|
|
38
24
|
}
|
|
39
25
|
});
|
|
@@ -42,11 +28,11 @@ export default async (socket, argv) => new Promise((resolve, reject) => {
|
|
|
42
28
|
const incomingHubCb = (clientId) => {
|
|
43
29
|
// console.log(clientId, 'incoming clientId')
|
|
44
30
|
|
|
45
|
-
|
|
31
|
+
const addCLIAddressAsResponseHeader = new Transform({
|
|
46
32
|
transform(chunk, encoding, next){
|
|
47
33
|
const apiResponse = chunk.toString().toLowerCase()
|
|
48
34
|
const cliAddress = argv.cliAddress()
|
|
49
|
-
if ( (apiResponse.indexOf('200 ok') > -1) && (apiResponse.indexOf('server: datasnaphttpservice') > -1)
|
|
35
|
+
if ( (apiResponse.indexOf('200 ok') > -1) && (apiResponse.indexOf('server: datasnaphttpservice') > -1)) {
|
|
50
36
|
// don't use string to insert additional headers, chunk can have mixed content of string and binary data
|
|
51
37
|
const response = Buffer.from(chunk)
|
|
52
38
|
const delimiter = '\r\n\r\n'
|
|
@@ -55,8 +41,10 @@ export default async (socket, argv) => new Promise((resolve, reject) => {
|
|
|
55
41
|
Buffer.copyBytesFrom(response, 0, delimiterPos),
|
|
56
42
|
Buffer.from(
|
|
57
43
|
'\r\n' +
|
|
58
|
-
'Access-Control-Expose-Headers: biza-cli-address\r\n' +
|
|
59
|
-
`biza-cli-address: ${cliAddress.
|
|
44
|
+
'Access-Control-Expose-Headers: biza-cli-address, biza-hub-address\r\n' +
|
|
45
|
+
// `biza-cli-address: ${cliAddress.address}\r\n` + // if we need directHub with local LAN
|
|
46
|
+
(cliAddress.publicUrl ? `biza-cli-address: ${cliAddress.publicUrl}\r\n` : '') +
|
|
47
|
+
(cliAddress.hubUrl ? `biza-hub-address: ${cliAddress.hubUrl}\r\n` : '') +
|
|
60
48
|
'\r\n'
|
|
61
49
|
)
|
|
62
50
|
])
|
|
@@ -121,4 +109,61 @@ export default async (socket, argv) => new Promise((resolve, reject) => {
|
|
|
121
109
|
socket.on('connect', connectCb);
|
|
122
110
|
socket.on('incomingClient', incomingHubCb)
|
|
123
111
|
socket.on('cli-req', cliReqCb);
|
|
124
|
-
})
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
export const hubEvent = (socket, argv, notifier)=>{
|
|
115
|
+
let id
|
|
116
|
+
socket.on('connect', ()=>{
|
|
117
|
+
id = socket.id
|
|
118
|
+
notifier(argv['hub'])
|
|
119
|
+
console.log(`${new Date()}: Connected to BizA Hub at ${argv['hub']} using sub domain "${argv['subdomain']}" with id "${id}"`)
|
|
120
|
+
})
|
|
121
|
+
socket.on('disconnect', (reason)=>{
|
|
122
|
+
notifier('')
|
|
123
|
+
console.log(`${new Date()}: ${id||''} disconnected from BizA Hub. Reason: ${reason}`)
|
|
124
|
+
})
|
|
125
|
+
const logError = msg=>{console.log(`${new Date()}: ${id||''} connection error to BizA Hub. Error : ${msg}`)}
|
|
126
|
+
socket.on('connect_error', (error)=>{
|
|
127
|
+
notifier('')
|
|
128
|
+
logError(error.message)
|
|
129
|
+
})
|
|
130
|
+
socket.on('error', (error)=>{
|
|
131
|
+
notifier('')
|
|
132
|
+
logError(error)
|
|
133
|
+
})
|
|
134
|
+
apiRequestListener(socket, argv)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export const apiRequestListener = (socket, argv)=>{
|
|
138
|
+
socket.on('apiRequest', (reqData, resCB)=>{
|
|
139
|
+
const socketResponse = (resp)=>{return {
|
|
140
|
+
status: resp.status,
|
|
141
|
+
statusText: resp.statusText,
|
|
142
|
+
headers: resp.headers,
|
|
143
|
+
body: resp.data, url: `${argv['hostname']}:${argv['port']+resp.config.url}`
|
|
144
|
+
}}
|
|
145
|
+
if (argv['subdomain'].localeCompare(reqData.subDomain)==0) {
|
|
146
|
+
const apiAddress = `${argv['secure']==true ? 'https://' : 'http://'}${argv['hostname']}:${argv['port']}`
|
|
147
|
+
axios.request({
|
|
148
|
+
timeout: (reqData.timeout || IDLE_SOCKET_TIMEOUT_MILLISECONDS),
|
|
149
|
+
baseURL: apiAddress,
|
|
150
|
+
url: reqData.path,
|
|
151
|
+
method: reqData.method,
|
|
152
|
+
headers: reqData.headers,
|
|
153
|
+
data: reqData.body,
|
|
154
|
+
// decompress: false, // if we need to interfered default Agent compression
|
|
155
|
+
responseType: reqData.responseType,
|
|
156
|
+
maxContentLength: Infinity,
|
|
157
|
+
})
|
|
158
|
+
.then(response=>{
|
|
159
|
+
resCB(null, socketResponse(response))
|
|
160
|
+
})
|
|
161
|
+
.catch(error=>{
|
|
162
|
+
resCB(error, null)
|
|
163
|
+
})
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
resCB({status: 401, statusText: 'bad subdomain', url: `${argv['hostname']}:${argv['port']+reqData.path}`}, null)
|
|
167
|
+
}
|
|
168
|
+
})
|
|
169
|
+
}
|
package/package.json
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "biz-a-cli",
|
|
3
3
|
"nameDev": "biz-a-cli-dev",
|
|
4
|
-
"version": "2.3.
|
|
5
|
-
"versionDev": "0.0.
|
|
4
|
+
"version": "2.3.55",
|
|
5
|
+
"versionDev": "0.0.34",
|
|
6
6
|
"description": "",
|
|
7
7
|
"main": "bin/index.js",
|
|
8
8
|
"type": "module",
|
|
9
9
|
"engines": {
|
|
10
|
-
"node": "
|
|
11
|
-
"npm": "
|
|
10
|
+
"node": ">=20.16.0",
|
|
11
|
+
"npm": ">=10.8.1"
|
|
12
12
|
},
|
|
13
13
|
"scripts": {
|
|
14
14
|
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js --watch a",
|
package/readme.md
CHANGED
|
@@ -82,4 +82,4 @@
|
|
|
82
82
|
hub --server [BizA Hub Server] --sub [subdomain] --hostname [ip_API] --port [port_API] --publish [true]
|
|
83
83
|
|
|
84
84
|
Example :
|
|
85
|
-
hub --server https://biz-a.herokuapp.com --sub imamatek --hostname localhost --port 212 --publish
|
|
85
|
+
hub --server https://biz-a.herokuapp.com --sub imamatek --hostname localhost --port 212 --publish
|
package/scheduler/timer.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { checkSchedule } from "./datalib.js"
|
|
2
2
|
import { getWatchers, getHistories } from "./watcherController.js";
|
|
3
3
|
import { isItTime, loopTimer } from "./watcherlib.js"
|
|
4
|
-
import { setInterval } from 'timers/promises';
|
|
5
4
|
import { getCompanyObjectId, getSelectedConfig } from "../scheduler/configController.js";
|
|
6
5
|
import { historyRecordToJson, watcherRecordToJson, setUtcDate } from "./converter.js";
|
|
7
6
|
|
|
@@ -46,31 +45,44 @@ export async function runHistory(histories, config, watchers, now) {
|
|
|
46
45
|
}
|
|
47
46
|
}
|
|
48
47
|
|
|
48
|
+
export async function runRecentSchedule(intervalTime, config) {
|
|
49
|
+
const result = await getWatchers(config);
|
|
50
|
+
const watchers = watcherRecordToJson(result);
|
|
51
|
+
|
|
52
|
+
for (const watcher of watchers) {
|
|
53
|
+
loopTimer(watcher.timer, intervalTime, config, true);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// reload == true ?
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export async function* setConstantInterval(interval, start) { //SCY BZ 4169
|
|
60
|
+
let nextTime = start;
|
|
61
|
+
while (true) {
|
|
62
|
+
await new Promise(resolve => setTimeout(resolve, nextTime - Date.now()));
|
|
63
|
+
yield nextTime;
|
|
64
|
+
nextTime += interval;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
49
67
|
|
|
50
68
|
export const runScheduler = async (companyName) => {
|
|
51
69
|
const company = await getCompanyObjectId(companyName);
|
|
52
70
|
const config = await getSelectedConfig(company._id);
|
|
53
71
|
|
|
54
|
-
Promise.all([getHistories(config), getWatchers(config)])
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
72
|
+
Promise.all([getHistories(config), getWatchers(config)]).then(([historyResult, watcherResult]) => {
|
|
73
|
+
const histories = historyRecordToJson(historyResult);
|
|
74
|
+
const historyWatchers = watcherRecordToJson(watcherResult);
|
|
75
|
+
runHistory(histories, config, historyWatchers, new Date());
|
|
76
|
+
});
|
|
77
|
+
|
|
60
78
|
|
|
61
79
|
let intervalStartTime = '';
|
|
62
|
-
for await (const
|
|
80
|
+
for await (const intervalTime of setConstantInterval(SCHEDULE_INTERVAL, Date.now())) {
|
|
63
81
|
if (!intervalStartTime) {
|
|
64
|
-
console.log('Interval Time starts at :', new Date(
|
|
65
|
-
intervalStartTime = new Date(
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const result = await getWatchers(config);
|
|
69
|
-
const watchers = watcherRecordToJson(result);
|
|
70
|
-
for (const watcher of watchers) {
|
|
71
|
-
loopTimer(watcher.timer, Date.now(), config, true);
|
|
82
|
+
console.log('Interval Time starts at :', new Date(intervalTime));
|
|
83
|
+
intervalStartTime = new Date(intervalTime);
|
|
72
84
|
}
|
|
73
85
|
|
|
74
|
-
|
|
86
|
+
await runRecentSchedule(intervalTime, config);
|
|
75
87
|
}
|
|
76
88
|
}
|
package/tests/app.test.js
CHANGED
|
@@ -9,8 +9,8 @@ import { env } from "../envs/env.js"
|
|
|
9
9
|
describe('Biz-A Apps CLI', ()=>{
|
|
10
10
|
|
|
11
11
|
let originalOptions, originalCWD, axios
|
|
12
|
-
const logSpy = jest.spyOn(global.console, 'log')
|
|
13
|
-
const errorSpy = jest.spyOn(global.console, 'error')
|
|
12
|
+
const logSpy = jest.spyOn(global.console, 'log').mockImplementation()
|
|
13
|
+
const errorSpy = jest.spyOn(global.console, 'error').mockImplementation()
|
|
14
14
|
|
|
15
15
|
async function runCommand(...options){
|
|
16
16
|
process.argv[1] = process.cwd()+'\\bin' // mock the key root folder
|
|
@@ -294,7 +294,8 @@ describe('Biz-A Apps CLI', ()=>{
|
|
|
294
294
|
expect(logSpy.mock.calls[0][0]).toBe('===================\nA.JS\n===================')
|
|
295
295
|
|
|
296
296
|
expect(errorSpy.mock.calls.length).toBe(1)
|
|
297
|
-
expect(errorSpy.mock.calls[0][0]).toStrictEqual({e: 'a.js:1:38: SyntaxError: Unexpected token: eof, expected: punc «}»'})
|
|
297
|
+
// expect(errorSpy.mock.calls[0][0]).toStrictEqual({e: 'a.js:1:38: SyntaxError: Unexpected token: eof, expected: punc «}»'})
|
|
298
|
+
expect(errorSpy.mock.calls[0][0]).toStrictEqual('a.js:1:38: SyntaxError: Unexpected token: eof, expected: punc «}»')
|
|
298
299
|
}
|
|
299
300
|
],
|
|
300
301
|
[
|
|
@@ -309,7 +310,8 @@ describe('Biz-A Apps CLI', ()=>{
|
|
|
309
310
|
expect(logSpy.mock.calls[4][0]).toBe('Running script with Import function')
|
|
310
311
|
|
|
311
312
|
expect(errorSpy.mock.calls.length).toBe(1)
|
|
312
|
-
expect(errorSpy.mock.calls[0][0]).toStrictEqual({e: 'a.js : ReferenceError: modul is not defined'}) // ES6 import error
|
|
313
|
+
// expect(errorSpy.mock.calls[0][0]).toStrictEqual({e: 'a.js : ReferenceError: modul is not defined'}) // ES6 import error
|
|
314
|
+
expect(errorSpy.mock.calls[0][0]).toStrictEqual('a.js : ReferenceError: modul is not defined')// ES6 import error
|
|
313
315
|
}
|
|
314
316
|
],
|
|
315
317
|
[
|
|
@@ -323,7 +325,8 @@ describe('Biz-A Apps CLI', ()=>{
|
|
|
323
325
|
expect(logSpy.mock.calls[3][0]).toBe('Running script with Import function')
|
|
324
326
|
|
|
325
327
|
expect(errorSpy.mock.calls.length).toBe(1)
|
|
326
|
-
expect(errorSpy.mock.calls[0][0]).toStrictEqual({e: 'a.js : Failed to compile template script.\nPlease make sure the script is correct and not returning empty result'})
|
|
328
|
+
// expect(errorSpy.mock.calls[0][0]).toStrictEqual({e: 'a.js : Failed to compile template script.\nPlease make sure the script is correct and not returning empty result'})
|
|
329
|
+
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')
|
|
327
330
|
}
|
|
328
331
|
],
|
|
329
332
|
]
|
package/tests/data.test.js
CHANGED
|
@@ -46,6 +46,8 @@ describe('data test', () => {
|
|
|
46
46
|
"}";
|
|
47
47
|
|
|
48
48
|
test('check schedule subscription no history', async () => {
|
|
49
|
+
const logSpy = jest.spyOn(console, 'log').mockImplementation();
|
|
50
|
+
|
|
49
51
|
const config = {
|
|
50
52
|
_id: 'ffffffff2ae49fab9ea654e1',
|
|
51
53
|
url: 'http://localhost:212',
|
|
@@ -62,9 +64,13 @@ describe('data test', () => {
|
|
|
62
64
|
|
|
63
65
|
await scheduleSubscription(config, data, trigger, false, true);
|
|
64
66
|
expect(mockInsertHistory).toBeCalledTimes(0);
|
|
67
|
+
expect(logSpy).toHaveBeenCalledWith('Run Schedule : New Watcher 1');
|
|
68
|
+
|
|
69
|
+
logSpy.mockRestore();
|
|
65
70
|
});
|
|
66
71
|
|
|
67
72
|
test('check schedule subscription with history', async () => {
|
|
73
|
+
const logSpy = jest.spyOn(console, 'log').mockImplementation();
|
|
68
74
|
const config = {
|
|
69
75
|
_id: 'ffffffff2ae49fab9ea654e1',
|
|
70
76
|
url: 'http://localhost:212',
|
|
@@ -82,6 +88,9 @@ describe('data test', () => {
|
|
|
82
88
|
|
|
83
89
|
await scheduleSubscription(config, data, trigger, true, true);
|
|
84
90
|
expect(mockInsertHistory).toBeCalledTimes(1);
|
|
91
|
+
expect(logSpy).toHaveBeenCalledWith('Run Recent Schedule : New Watcher 1');
|
|
92
|
+
|
|
93
|
+
logSpy.mockRestore();
|
|
85
94
|
});
|
|
86
95
|
|
|
87
96
|
test('get input data', () => {
|
|
@@ -339,6 +348,8 @@ describe('data test', () => {
|
|
|
339
348
|
});
|
|
340
349
|
|
|
341
350
|
test('should return undefined when data has error', async () => {
|
|
351
|
+
const logSpy = jest.spyOn(console, 'log').mockImplementation();
|
|
352
|
+
|
|
342
353
|
const config = {};
|
|
343
354
|
const data = { error: 'error' };
|
|
344
355
|
|
|
@@ -346,6 +357,9 @@ describe('data test', () => {
|
|
|
346
357
|
const expected = undefined;
|
|
347
358
|
|
|
348
359
|
expect(actual).toStrictEqual(expected);
|
|
360
|
+
expect(logSpy).toHaveBeenCalledWith('error', 'error');
|
|
361
|
+
|
|
362
|
+
logSpy.mockRestore();
|
|
349
363
|
});
|
|
350
364
|
|
|
351
365
|
test('should return undefined when no data', async () => {
|
package/tests/hub.test.js
CHANGED
|
@@ -1,13 +1,19 @@
|
|
|
1
|
-
import { Server } from 'socket.io';
|
|
1
|
+
import { Server as ioServer } from 'socket.io';
|
|
2
2
|
import { createServer } from 'node:http'
|
|
3
|
-
import hubEvent from '../bin/hubEvent.js'
|
|
4
|
-
import { io as
|
|
3
|
+
import { streamEvent, hubEvent, socketAgent } from '../bin/hubEvent.js'
|
|
4
|
+
import { io as ioClient } from "socket.io-client";
|
|
5
5
|
import { jest } from '@jest/globals';
|
|
6
6
|
import axios from "axios";
|
|
7
|
+
import net from 'node:net'
|
|
8
|
+
import tls from 'node:tls'
|
|
9
|
+
import ss from 'socket.io-stream'
|
|
10
|
+
import { Writable, pipeline } from 'node:stream'
|
|
11
|
+
import { text } from 'node:stream/consumers'
|
|
12
|
+
import { directHubEvent } from '../bin/directHubEvent.js'
|
|
7
13
|
|
|
8
14
|
let socketsBySubdomain = {};
|
|
9
15
|
|
|
10
|
-
const
|
|
16
|
+
const createSockTunnel2CLI = (socket) => {
|
|
11
17
|
return (subdomain, responseCb) => {
|
|
12
18
|
const responseCallback = (error) => { if (responseCb) responseCb(error) }
|
|
13
19
|
let subdomainStr = subdomain.toString();
|
|
@@ -31,39 +37,52 @@ const toPromise = (cb) => new Promise((resolve, reject) => {
|
|
|
31
37
|
setTimeout(() => reject(new Error('timeout')), 1000)
|
|
32
38
|
})
|
|
33
39
|
|
|
34
|
-
describe('
|
|
35
|
-
let
|
|
40
|
+
describe('Hub event tests', () => {
|
|
41
|
+
let bizAServer_ServerSocket, port;
|
|
42
|
+
let cliHttpServer
|
|
43
|
+
|
|
36
44
|
const startSocket = function startSocket(server) {
|
|
37
|
-
let io = new
|
|
45
|
+
let io = new ioServer(server);
|
|
38
46
|
io.on('connection', (socket) => {
|
|
39
|
-
socket.on('createTunnel',
|
|
47
|
+
socket.on('createTunnel', createSockTunnel2CLI(socket));
|
|
40
48
|
socket.on('disconnect', deleteSubdomain(socket));
|
|
41
49
|
});
|
|
42
|
-
|
|
43
50
|
return io;
|
|
44
51
|
}
|
|
45
52
|
|
|
53
|
+
function freeSocketClient(sock){
|
|
54
|
+
if (sock) {
|
|
55
|
+
if (sock.connected) {
|
|
56
|
+
sock.off()
|
|
57
|
+
sock.disconnect()
|
|
58
|
+
}
|
|
59
|
+
sock.destroy()
|
|
60
|
+
sock = undefined
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
46
64
|
beforeAll((done) => {
|
|
47
|
-
|
|
48
|
-
|
|
65
|
+
cliHttpServer = createServer()
|
|
66
|
+
bizAServer_ServerSocket = startSocket(cliHttpServer)
|
|
49
67
|
|
|
50
|
-
|
|
51
|
-
port =
|
|
68
|
+
cliHttpServer.listen(async () => {
|
|
69
|
+
port = cliHttpServer.address().port;
|
|
52
70
|
done()
|
|
53
71
|
})
|
|
54
72
|
})
|
|
55
73
|
|
|
56
74
|
afterAll(() => {
|
|
57
|
-
|
|
75
|
+
bizAServer_ServerSocket.close();
|
|
58
76
|
})
|
|
59
77
|
|
|
60
78
|
test('request to cli', async () => {
|
|
79
|
+
const logSpy = jest.spyOn(console, 'log').mockImplementation();
|
|
61
80
|
let mockedRequest = jest.spyOn(axios, 'request').mockReturnValue({ data: 'OK' });
|
|
62
81
|
|
|
63
82
|
let socket;
|
|
64
83
|
try {
|
|
65
|
-
socket =
|
|
66
|
-
await
|
|
84
|
+
socket = ioClient(`http://localhost:${port}`);
|
|
85
|
+
await streamEvent(socket, {
|
|
67
86
|
server: `http://localhost:${port}`,
|
|
68
87
|
subdomain: 'scy',
|
|
69
88
|
hostname: 'localhost',
|
|
@@ -103,8 +122,300 @@ describe('cli req test', () => {
|
|
|
103
122
|
});
|
|
104
123
|
|
|
105
124
|
socket.disconnect();
|
|
125
|
+
|
|
126
|
+
expect(logSpy).toHaveBeenCalledWith('error', 'error');
|
|
127
|
+
|
|
128
|
+
logSpy.mockRestore();
|
|
106
129
|
} catch (error) {
|
|
107
130
|
console.log(error);
|
|
108
131
|
}
|
|
109
132
|
})
|
|
133
|
+
|
|
134
|
+
test('should used correct socket agent', ()=>{
|
|
135
|
+
expect(socketAgent(false)).toBe(net)
|
|
136
|
+
expect(socketAgent(true)).toBe(tls)
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
test('should access API with scheme of BizA Client <--https--> BizA Server <--socket stream--> BizA CLI <--socket--> API', async ()=>{
|
|
140
|
+
const uniqueId = '123456' // this is used for make sure BizA Server will get correct response from BizA CLI
|
|
141
|
+
const subdomain = 'hub'
|
|
142
|
+
|
|
143
|
+
let cliToBizAServerSocket // mocking of socket client from BizA CLI to BizA Server
|
|
144
|
+
let mockAPIServer // mocking of FINA API Datasnap server. We need to used TCP Server to test data flow using socket, so don't used HTTP Server
|
|
145
|
+
let cliToAPISocket // mocking of socket client from BizA CLI to FINA API Datasnap Server
|
|
146
|
+
|
|
147
|
+
try {
|
|
148
|
+
|
|
149
|
+
cliToBizAServerSocket = ioClient(`http://localhost:${port}`)
|
|
150
|
+
await streamEvent(cliToBizAServerSocket, {
|
|
151
|
+
server: `http://localhost:${port}`,
|
|
152
|
+
subdomain,
|
|
153
|
+
hostname: '127.0.0.1',
|
|
154
|
+
port: 212,
|
|
155
|
+
serverport: 3002,
|
|
156
|
+
cliAddress: ()=>{return {ip: '59.60.1.22', port: '3002', address: `59.60.1.22:3002`, publicUrl: 'https://some.public.url', hubUrl: 'https://some.hub.url'}},
|
|
157
|
+
hub: 'https://some.hub.url'
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
mockAPIServer = net.createServer((clientSocket)=>{
|
|
161
|
+
cliToAPISocket = clientSocket
|
|
162
|
+
clientSocket.on('data', (data)=>{
|
|
163
|
+
if (data.toString().toUpperCase()==='POST /SUCCESS HTTP/1.1') {
|
|
164
|
+
clientSocket.write('HTTP/1.1 200 Ok\r\nServer: datasnapHTTPService\r\n\r\n')
|
|
165
|
+
}
|
|
166
|
+
else if (data.toString().toUpperCase()==='POST /FAIL HTTP/1.1') {
|
|
167
|
+
clientSocket.write('HTTP/1.1 403 Forbidden\r\nServer: datasnapHTTPService\r\n\r\n')
|
|
168
|
+
}
|
|
169
|
+
})
|
|
170
|
+
})
|
|
171
|
+
await toPromise(resolve=>{mockAPIServer.listen({port: 212, host: '127.0.0.1'}, ()=>{resolve()})})
|
|
172
|
+
|
|
173
|
+
const getResponse = async (httpRawRequest)=>{
|
|
174
|
+
let tunnelStream // mocking of socket stream between BizA CLI and BizA Server, also act as mocking of HTTP Request from BizA Client
|
|
175
|
+
let outputStream // mocking of HTTP Response received by BizA Client
|
|
176
|
+
try {
|
|
177
|
+
let outputContent = []
|
|
178
|
+
outputStream = new Writable({
|
|
179
|
+
write(chunk, encoding, next){
|
|
180
|
+
outputContent.push(chunk)
|
|
181
|
+
cliToAPISocket.end() // Imitate API Server to finish sending response, it will call end "tunnelStream" and lastly it will close "outputStream"
|
|
182
|
+
next()
|
|
183
|
+
}
|
|
184
|
+
})
|
|
185
|
+
tunnelStream = await toPromise(resolve => {
|
|
186
|
+
ss(socketsBySubdomain[subdomain]).once(uniqueId, (stream)=>{
|
|
187
|
+
pipeline(stream, outputStream, (err)=>{if (err) console.error(err)})
|
|
188
|
+
resolve(stream)
|
|
189
|
+
})
|
|
190
|
+
socketsBySubdomain[subdomain].emit('incomingClient', uniqueId)
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
tunnelStream.write(httpRawRequest)
|
|
194
|
+
await text(tunnelStream) // this will try to read data from "cliToAPISocket" -> "transformStream" -> "tunnelStream" -> "outputStream"
|
|
195
|
+
return outputContent.toString()
|
|
196
|
+
}
|
|
197
|
+
finally {
|
|
198
|
+
if (tunnelStream) tunnelStream.destroy()
|
|
199
|
+
if (outputStream) outputStream.destroy()
|
|
200
|
+
freeSocketClient(cliToAPISocket)
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
expect(await getResponse('POST /success HTTP/1.1')).toBe(
|
|
205
|
+
'HTTP/1.1 200 Ok\r\n' +
|
|
206
|
+
'Server: datasnapHTTPService\r\n'+
|
|
207
|
+
'Access-Control-Expose-Headers: biza-cli-address, biza-hub-address\r\n'+
|
|
208
|
+
'biza-cli-address: https://some.public.url\r\n' +
|
|
209
|
+
'biza-hub-address: https://some.hub.url\r\n\r\n'
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
expect(await getResponse('POST /fail HTTP/1.1')).toBe(
|
|
213
|
+
'HTTP/1.1 403 Forbidden\r\n' +
|
|
214
|
+
'Server: datasnapHTTPService\r\n\r\n'
|
|
215
|
+
)
|
|
216
|
+
}
|
|
217
|
+
finally {
|
|
218
|
+
freeSocketClient(cliToBizAServerSocket)
|
|
219
|
+
if (mockAPIServer) mockAPIServer.close()
|
|
220
|
+
}
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
describe('should access API with scheme of BizA Client <--socket tunnel--> BizA CLI <--http--> API', ()=>{
|
|
224
|
+
const apiAddress = '127.0.0.1'
|
|
225
|
+
const subdomain = 'directHub'
|
|
226
|
+
let cliSocketServer // mocking of CLI socket server
|
|
227
|
+
let clientToCliSocket // mocking of socket client from BizA Client to BizA CLI
|
|
228
|
+
let mockedRequest
|
|
229
|
+
|
|
230
|
+
beforeEach(()=>{
|
|
231
|
+
mockedRequest = jest.spyOn(axios, 'request')
|
|
232
|
+
mockedRequest.mockClear()
|
|
233
|
+
})
|
|
234
|
+
|
|
235
|
+
beforeAll(()=>{
|
|
236
|
+
cliSocketServer = new ioServer(9999)// mocking of CLI socket server
|
|
237
|
+
directHubEvent(cliSocketServer, {subdomain, hostname: apiAddress, port, secure: false})
|
|
238
|
+
|
|
239
|
+
clientToCliSocket = ioClient('http://localhost:9999')
|
|
240
|
+
})
|
|
241
|
+
|
|
242
|
+
afterAll(()=>{
|
|
243
|
+
freeSocketClient(clientToCliSocket)
|
|
244
|
+
if (cliSocketServer) {
|
|
245
|
+
cliSocketServer.close()
|
|
246
|
+
}
|
|
247
|
+
})
|
|
248
|
+
|
|
249
|
+
test('should return success response', async ()=>{
|
|
250
|
+
mockedRequest.mockResolvedValueOnce({
|
|
251
|
+
status: 200,
|
|
252
|
+
statusText: 'Success',
|
|
253
|
+
headers: {'res-header': 'aa'},
|
|
254
|
+
data: 'body of response',
|
|
255
|
+
config: {baseURL: 'localhost', url: '/test'}
|
|
256
|
+
})
|
|
257
|
+
|
|
258
|
+
const [error, response] = await toPromise(resolve => {
|
|
259
|
+
clientToCliSocket.emit(
|
|
260
|
+
'apiRequest'
|
|
261
|
+
, {subDomain: subdomain, method: 'POST', path: '/test', headers: {'req-header': 'aa'}, body: 'body of request', responseType: 'json'}
|
|
262
|
+
, (res, err)=>{resolve([res, err])}
|
|
263
|
+
)
|
|
264
|
+
})
|
|
265
|
+
|
|
266
|
+
expect(response).toStrictEqual({
|
|
267
|
+
status: 200,
|
|
268
|
+
statusText: 'Success',
|
|
269
|
+
headers: {'res-header': 'aa'},
|
|
270
|
+
body: 'body of response',
|
|
271
|
+
url: `${apiAddress}:${port}/test`
|
|
272
|
+
})
|
|
273
|
+
|
|
274
|
+
expect(error).toBe(null)
|
|
275
|
+
|
|
276
|
+
expect(mockedRequest).toHaveBeenCalledWith({
|
|
277
|
+
timeout: 30*1000,
|
|
278
|
+
baseURL: `http://127.0.0.1:${port}`,
|
|
279
|
+
url: '/test',
|
|
280
|
+
method: "POST",
|
|
281
|
+
headers: {'req-header': 'aa'},
|
|
282
|
+
data: 'body of request',
|
|
283
|
+
// decompress: false,
|
|
284
|
+
responseType: 'json',
|
|
285
|
+
maxContentLength: Infinity,
|
|
286
|
+
})
|
|
287
|
+
})
|
|
288
|
+
|
|
289
|
+
test('should return fail response', async ()=>{
|
|
290
|
+
const mockErrorResponse = {response: {
|
|
291
|
+
status: 400,
|
|
292
|
+
statusText: 'Failed',
|
|
293
|
+
headers: {'res-header': 'aa'},
|
|
294
|
+
data: 'body of response',
|
|
295
|
+
config: {baseURL: 'localhost', url: '/test'}
|
|
296
|
+
}}
|
|
297
|
+
mockedRequest.mockRejectedValueOnce(mockErrorResponse)
|
|
298
|
+
|
|
299
|
+
const [error, response] = await toPromise(resolve => {
|
|
300
|
+
clientToCliSocket.emit(
|
|
301
|
+
'apiRequest'
|
|
302
|
+
, {subDomain: subdomain, method: 'POST', path: '/test', headers: {'req-header': 'aa'}, body: 'body of request', responseType: 'arraybuffer'}
|
|
303
|
+
, (res, err)=>{resolve([res, err])}
|
|
304
|
+
)
|
|
305
|
+
})
|
|
306
|
+
|
|
307
|
+
expect(response).toBe(null)
|
|
308
|
+
|
|
309
|
+
expect(error).toStrictEqual(mockErrorResponse)
|
|
310
|
+
|
|
311
|
+
expect(mockedRequest).toHaveBeenCalledWith({
|
|
312
|
+
timeout: 30*1000,
|
|
313
|
+
baseURL: `http://127.0.0.1:${port}`,
|
|
314
|
+
url: '/test',
|
|
315
|
+
method: "POST",
|
|
316
|
+
headers: {'req-header': 'aa'},
|
|
317
|
+
data: 'body of request',
|
|
318
|
+
// decompress: false,
|
|
319
|
+
responseType: 'arraybuffer',
|
|
320
|
+
maxContentLength: Infinity,
|
|
321
|
+
})
|
|
322
|
+
})
|
|
323
|
+
|
|
324
|
+
test('should return bad subdomain response', async ()=>{
|
|
325
|
+
const [error, response] = await toPromise(resolve => {
|
|
326
|
+
clientToCliSocket.emit(
|
|
327
|
+
'apiRequest'
|
|
328
|
+
, {subDomain: 'wrong subdomain', method: 'POST', path: '/test', headers: {'req-header': 'aa'}, body: 'body of request'}
|
|
329
|
+
, (res, err)=>{resolve([res, err])}
|
|
330
|
+
)
|
|
331
|
+
})
|
|
332
|
+
|
|
333
|
+
expect(response).toBe(null)
|
|
334
|
+
|
|
335
|
+
expect(error).toStrictEqual({
|
|
336
|
+
status: 401,
|
|
337
|
+
statusText: 'bad subdomain',
|
|
338
|
+
url: `${apiAddress}:${port}/test`
|
|
339
|
+
})
|
|
340
|
+
|
|
341
|
+
expect(mockedRequest).not.toHaveBeenCalledWith()
|
|
342
|
+
})
|
|
343
|
+
})
|
|
344
|
+
|
|
345
|
+
test('should access API with scheme of BizA Client <--socket--> BizA Hub <--socket--> BizA CLI <--http--> API', async ()=>{
|
|
346
|
+
const subdomain = 'roomA'
|
|
347
|
+
let cliToHubSocket
|
|
348
|
+
let hubToCLISocket
|
|
349
|
+
let hubServer
|
|
350
|
+
|
|
351
|
+
try {
|
|
352
|
+
mockedRequest = jest.spyOn(axios, 'request')
|
|
353
|
+
mockedRequest.mockClear()
|
|
354
|
+
mockedRequest.mockResolvedValueOnce({
|
|
355
|
+
status: 200,
|
|
356
|
+
statusText: 'Success',
|
|
357
|
+
headers: {'res-header': 'aa'},
|
|
358
|
+
data: 'body of response',
|
|
359
|
+
config: {baseURL: 'localhost', url: '/test'}
|
|
360
|
+
})
|
|
361
|
+
|
|
362
|
+
hubServer = new ioServer(9998)
|
|
363
|
+
hubServer.on('connection', (sock)=>hubToCLISocket = sock)
|
|
364
|
+
|
|
365
|
+
cliToHubSocket = ioClient('http://localhost:9998')
|
|
366
|
+
hubEvent(
|
|
367
|
+
cliToHubSocket,
|
|
368
|
+
{
|
|
369
|
+
server: `http://localhost:${port}`,
|
|
370
|
+
subdomain,
|
|
371
|
+
hostname: '127.0.0.1',
|
|
372
|
+
port,
|
|
373
|
+
serverport: 3002,
|
|
374
|
+
cliAddress: ()=>{return {ip: '59.60.1.22', port: '3002', address: `59.60.1.22:3002`, publicUrl: 'https://some.tunnel.url'}},
|
|
375
|
+
hub: 'https://some.hub.url'
|
|
376
|
+
},
|
|
377
|
+
(hubUrl)=>{
|
|
378
|
+
expect(hubUrl).toBe('https://some.hub.url')
|
|
379
|
+
}
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
await toPromise(resolve => cliToHubSocket.once('connect', ()=>resolve()))
|
|
383
|
+
|
|
384
|
+
const [error, response] = await toPromise(resolve => {
|
|
385
|
+
hubToCLISocket.emit(
|
|
386
|
+
'apiRequest'
|
|
387
|
+
, {subDomain: subdomain, method: 'POST', path: '/test', headers: {'req-header': 'aa'}, body: 'body of request', responseType: 'json'}
|
|
388
|
+
, (res, err)=>{resolve([res, err])}
|
|
389
|
+
)
|
|
390
|
+
})
|
|
391
|
+
|
|
392
|
+
expect(response).toStrictEqual({
|
|
393
|
+
status: 200,
|
|
394
|
+
statusText: 'Success',
|
|
395
|
+
headers: {'res-header': 'aa'},
|
|
396
|
+
body: 'body of response',
|
|
397
|
+
url: `127.0.0.1:${port}/test`
|
|
398
|
+
})
|
|
399
|
+
|
|
400
|
+
expect(error).toBe(null)
|
|
401
|
+
|
|
402
|
+
expect(mockedRequest).toHaveBeenCalledWith({
|
|
403
|
+
timeout: 30*1000,
|
|
404
|
+
baseURL: `http://127.0.0.1:${port}`,
|
|
405
|
+
url: '/test',
|
|
406
|
+
method: "POST",
|
|
407
|
+
headers: {'req-header': 'aa'},
|
|
408
|
+
data: 'body of request',
|
|
409
|
+
responseType: 'json',
|
|
410
|
+
maxContentLength: Infinity,
|
|
411
|
+
})
|
|
412
|
+
}
|
|
413
|
+
finally {
|
|
414
|
+
freeSocketClient(cliToHubSocket)
|
|
415
|
+
if (hubServer) {
|
|
416
|
+
hubServer.close()
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
})
|
|
420
|
+
|
|
110
421
|
})
|
package/tests/mailCtl.test.js
CHANGED
|
@@ -17,6 +17,8 @@ describe('Mail Controller', () => {
|
|
|
17
17
|
let req;
|
|
18
18
|
|
|
19
19
|
test('transporter.sendMailCliScript is called', async () => {
|
|
20
|
+
const logSpy = jest.spyOn(console, 'log').mockImplementation();
|
|
21
|
+
|
|
20
22
|
req = {
|
|
21
23
|
body: {
|
|
22
24
|
companyname: 'abc',
|
|
@@ -35,5 +37,8 @@ describe('Mail Controller', () => {
|
|
|
35
37
|
expect(await sendMailCliScript(req)).toEqual('error');
|
|
36
38
|
|
|
37
39
|
expect(mockSendMail).toBeCalledTimes(2);
|
|
40
|
+
expect(logSpy).toHaveBeenCalledWith('Error: error');
|
|
41
|
+
|
|
42
|
+
logSpy.mockRestore();
|
|
38
43
|
})
|
|
39
44
|
})
|
package/tests/timer.test.js
CHANGED
|
@@ -1,4 +1,31 @@
|
|
|
1
1
|
import { jest } from '@jest/globals'
|
|
2
|
+
const { ObjectId } = await import('mongodb');
|
|
3
|
+
|
|
4
|
+
const CONFIGS = [{
|
|
5
|
+
companyObjectId: new ObjectId('62948492f7d559fba6f32196')
|
|
6
|
+
}]
|
|
7
|
+
|
|
8
|
+
const RAW_WATCHERS = [{
|
|
9
|
+
watcher_id: 1,
|
|
10
|
+
company_id: '62948492f7d559fba6f32196',
|
|
11
|
+
timer_id: 1,
|
|
12
|
+
timer_watcher_id: 1,
|
|
13
|
+
name: 'New Watcher 1',
|
|
14
|
+
active: true,
|
|
15
|
+
timezone: 'Asia/Jakarta',
|
|
16
|
+
templateName: 'cekUser.js',
|
|
17
|
+
seq: 0,
|
|
18
|
+
daily_days: '0,1,2,3,4,5,6',
|
|
19
|
+
weekly_ordinal: '',
|
|
20
|
+
weekly_days: '',
|
|
21
|
+
monthly_days: '',
|
|
22
|
+
minutely_everymin: 1,
|
|
23
|
+
minutely_time_from: '08:00',
|
|
24
|
+
minutely_time_to: '22:00',
|
|
25
|
+
hourly_hours: '',
|
|
26
|
+
scriptid: 1,
|
|
27
|
+
cli_script: 'abc'
|
|
28
|
+
}];
|
|
2
29
|
|
|
3
30
|
const mockCheckSchedule = jest.fn();
|
|
4
31
|
jest.unstable_mockModule("../scheduler/datalib.js", () => ({
|
|
@@ -9,13 +36,29 @@ jest.unstable_mockModule("../scheduler/datalib.js", () => ({
|
|
|
9
36
|
delay: jest.fn().mockResolvedValue('OK')
|
|
10
37
|
}))
|
|
11
38
|
|
|
39
|
+
jest.unstable_mockModule("../scheduler/watcherController.js", () => ({
|
|
40
|
+
getWatchers: jest.fn(async () => {
|
|
41
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
42
|
+
return RAW_WATCHERS;
|
|
43
|
+
}),
|
|
44
|
+
getHistories: jest.fn().mockResolvedValue('OK')
|
|
45
|
+
}))
|
|
46
|
+
|
|
47
|
+
const mockLoopTimer = jest.fn();
|
|
48
|
+
jest.unstable_mockModule("../scheduler/watcherLib.js", () => ({
|
|
49
|
+
loopTimer: mockLoopTimer.mockResolvedValue('OK'),
|
|
50
|
+
isItTime: jest.fn().mockResolvedValue('OK'),
|
|
51
|
+
}))
|
|
52
|
+
|
|
12
53
|
const {
|
|
13
|
-
SCHEDULE_INTERVAL,
|
|
54
|
+
// SCHEDULE_INTERVAL,
|
|
14
55
|
getSelectedData,
|
|
15
56
|
increaseInterval,
|
|
16
|
-
runHistory
|
|
57
|
+
runHistory,
|
|
58
|
+
runRecentSchedule,
|
|
59
|
+
setConstantInterval
|
|
17
60
|
} = await import('../scheduler/timer.js');
|
|
18
|
-
|
|
61
|
+
|
|
19
62
|
|
|
20
63
|
describe('timer and mitigation test', () => {
|
|
21
64
|
test('increase interval', async () => {
|
|
@@ -60,9 +103,7 @@ describe('timer and mitigation test', () => {
|
|
|
60
103
|
// latestRun: new Date(decreaseInterval(NOW)).toISOString()
|
|
61
104
|
}
|
|
62
105
|
]
|
|
63
|
-
|
|
64
|
-
companyObjectId: new ObjectId('62948492f7d559fba6f32196')
|
|
65
|
-
}]
|
|
106
|
+
|
|
66
107
|
const WATCHERS = [
|
|
67
108
|
{
|
|
68
109
|
_id: new ObjectId('65c34bd674ae2595b5efb5a7'),
|
|
@@ -89,4 +130,58 @@ describe('timer and mitigation test', () => {
|
|
|
89
130
|
await runHistory(HISTORIES, CONFIGS, WATCHERS, new Date('2024-02-29T07:00:00.000Z'));
|
|
90
131
|
expect(mockCheckSchedule).toBeCalledTimes(3);
|
|
91
132
|
});
|
|
133
|
+
|
|
134
|
+
test('run recent schedule with delay functions', async () => {
|
|
135
|
+
const startTime = Date.now();
|
|
136
|
+
|
|
137
|
+
const WATCHER_CONVERTED = [{
|
|
138
|
+
"_id": 1,
|
|
139
|
+
"watcherObjectId": 1,
|
|
140
|
+
"name": "New Watcher 1",
|
|
141
|
+
"active": true,
|
|
142
|
+
"timezone": "Asia/Jakarta",
|
|
143
|
+
"templateName": "cekUser.js",
|
|
144
|
+
"seq": 0,
|
|
145
|
+
"scriptid": 1,
|
|
146
|
+
"script": "\"abc\"",
|
|
147
|
+
"daily": [0, 1, 2, 3, 4, 5, 6],
|
|
148
|
+
"minutely": {
|
|
149
|
+
"everyMin": 1,
|
|
150
|
+
"from": "08:00",
|
|
151
|
+
"to": "22:00"
|
|
152
|
+
}
|
|
153
|
+
}];
|
|
154
|
+
|
|
155
|
+
await runRecentSchedule(startTime, CONFIGS[0]);
|
|
156
|
+
expect(mockLoopTimer).toHaveBeenCalledWith(WATCHER_CONVERTED, startTime, CONFIGS[0], true);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
test('constant interval', async () => {
|
|
160
|
+
jest.useFakeTimers();
|
|
161
|
+
jest.spyOn(global, 'setTimeout');
|
|
162
|
+
|
|
163
|
+
const interval = 1000;
|
|
164
|
+
const startTime = Date.now();
|
|
165
|
+
const generator = setConstantInterval(interval, startTime);
|
|
166
|
+
|
|
167
|
+
let results = [];
|
|
168
|
+
|
|
169
|
+
const fetchNextValue = async () => {
|
|
170
|
+
const next = generator.next();
|
|
171
|
+
jest.advanceTimersByTime(interval);
|
|
172
|
+
|
|
173
|
+
return (await next).value;
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
results.push(await fetchNextValue());
|
|
177
|
+
results.push(await fetchNextValue());
|
|
178
|
+
results.push(await fetchNextValue());
|
|
179
|
+
|
|
180
|
+
expect(results[0]).toBe(startTime);
|
|
181
|
+
expect(results[1]).toBe(startTime + interval);
|
|
182
|
+
expect(results[2]).toBe(startTime + 2 * interval);
|
|
183
|
+
expect(results[3]).toBe(undefined);
|
|
184
|
+
|
|
185
|
+
jest.useRealTimers();
|
|
186
|
+
});
|
|
92
187
|
})
|
package/tests/watcher.test.js
CHANGED
|
@@ -97,8 +97,10 @@ describe('isItTime', () => {
|
|
|
97
97
|
to: '17:00'
|
|
98
98
|
}
|
|
99
99
|
}
|
|
100
|
+
expect(isItTime(dataWatcher, '2023-11-7 9:00:00')).toBe(true)
|
|
100
101
|
expect(isItTime(dataWatcher, '2023-11-7 9:00:01')).toBe(true)
|
|
101
102
|
expect(isItTime(dataWatcher, '2023-11-7 9:05:01')).toBe(true)
|
|
103
|
+
expect(isItTime(dataWatcher, '2023-11-7 17:00:00')).toBe(true)
|
|
102
104
|
expect(isItTime(dataWatcher, '2023-11-7 17:00:01')).toBe(true)
|
|
103
105
|
expect(isItTime(dataWatcher, '2023-11-7 16:55:59')).toBe(true)
|
|
104
106
|
expect(isItTime(dataWatcher, '2023-11-7 8:59:59')).toBe(false)
|