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 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: true
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: "string",
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
- // console.error(e.response?.data ? e.response.data : e)
266
- console.error({e})
266
+ console.error(e.response?.data ? e.response.data : e)
267
+ // console.error({e})
267
268
  }
268
269
  }
269
270
 
@@ -1,57 +1,49 @@
1
- import axios from 'axios'
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
- function notifyUser(){
7
- console.log(`${new Date()}: Direct hub only available within local LAN. Please restart CLI to try again.`)
8
- }
9
- const qt = QuickTunnel.quick('127.0.0.1:'+port)
10
- qt.on('Disconnected', ()=>{notifyUser()})
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
- if (!url) {
19
- notifyUser()
20
- }
21
- return url
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('BizA Client Socket', clientSocket.id)
28
- clientSocket.on('disconnect', (reason)=>{console.log(`Socket ${clientSocket.id} disconnected. Reason : ${reason}`)})
44
+ console.log(clientSocket.id, 'connected')
45
+ clientSocket.on('disconnect', (reason)=>{console.log(`${clientSocket.id||'Direct Hub'} disconnected. Reason : ${reason}`)})
29
46
  }
30
- clientSocket.on('APISocket', (reqData, resCB)=>{
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: true
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: true
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.publicUrl}
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
- console.log(`${new Date()}: CLI is listening at ${getProtocol() + (process.env.HOST || cliAddress.ip || 'localhost')}:${cliAddress.port} `);
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
- // BizA CLI as socket client to BizA SERVER
150
- await hubEvent(ioClient(argv['server'], {query: {cliAddress: cliAddress.address }}), argv);
151
- console.log(`${new Date()}: CLI connected to BizA Server using sub domain "${argv['subdomain']}"`)
152
-
153
-
154
- // BizA CLI as socket server for BizA CLIENT
155
- const serverCORSOrigin = ['https://biz-a.id', 'https://test.biz-a.id', /\.biz-a\.id$/].concat((process.env.NODE_ENV === 'production') ? [] : [`http://${cliAddress.ip}:4200`, 'http://localhost:4200'])
156
- // console.log('Allowed Origins', serverCORSOrigin)
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
- export const IDLE_SOCKET_TIMEOUT_MILLISECONDS = 1000 * 30;
10
+ const IDLE_SOCKET_TIMEOUT_MILLISECONDS = 1000 * 30;
11
11
 
12
12
  export const socketAgent = isUsingHttps=>(isUsingHttps==true) ? tls : net
13
13
 
14
- export default async (socket, argv) => new Promise((resolve, reject) => {
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
- // console.log(new Date() + ': registered with server successfully');
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
- let addCLIAddressAsResponseHeader = new Transform({
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) && (cliAddress.publicUrl || cliAddress.address)) {
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.publicUrl ? cliAddress.publicUrl : cliAddress.address}\r\n` +
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.53",
5
- "versionDev": "0.0.30",
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": "^20.16.0",
11
- "npm": "^10.8.1"
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=false
85
+ hub --server https://biz-a.herokuapp.com --sub imamatek --hostname localhost --port 212 --publish
@@ -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
- .then(([historyResult, watcherResult]) => {
56
- const histories = historyRecordToJson(historyResult);
57
- const watchers = watcherRecordToJson(watcherResult);
58
- runHistory(histories, config, watchers, new Date());
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 startTime of setInterval(SCHEDULE_INTERVAL, Date.now())) {
80
+ for await (const intervalTime of setConstantInterval(SCHEDULE_INTERVAL, Date.now())) {
63
81
  if (!intervalStartTime) {
64
- console.log('Interval Time starts at :', new Date(startTime));
65
- intervalStartTime = new Date(startTime);
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
- // // reload == true ?
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
  ]
@@ -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 ioc } from "socket.io-client";
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 createSocket2CLI = (socket) => {
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('cli req test', () => {
35
- let io, port;
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 Server(server);
45
+ let io = new ioServer(server);
38
46
  io.on('connection', (socket) => {
39
- socket.on('createTunnel', createSocket2CLI(socket));
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
- const httpServer = createServer()
48
- io = startSocket(httpServer)
65
+ cliHttpServer = createServer()
66
+ bizAServer_ServerSocket = startSocket(cliHttpServer)
49
67
 
50
- httpServer.listen(async () => {
51
- port = httpServer.address().port;
68
+ cliHttpServer.listen(async () => {
69
+ port = cliHttpServer.address().port;
52
70
  done()
53
71
  })
54
72
  })
55
73
 
56
74
  afterAll(() => {
57
- io.close();
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 = ioc(`http://localhost:${port}`);
66
- await hubEvent(socket, {
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
  })
@@ -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
  })
@@ -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
- const { ObjectId } = await import('mongodb');
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
- const CONFIGS = [{
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
  })
@@ -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)