ml-testing-toolkit 18.19.0 → 18.19.2

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.
@@ -21,6 +21,7 @@
21
21
 
22
22
  * Mojaloop Foundation
23
23
  - Name Surname <name.surname@mojaloop.io>
24
+ - Shashikant Hirugade <shashi.mojaloop@gmail.com>
24
25
 
25
26
  * ModusBox
26
27
  * Georgi Logodazhki <georgi.logodazhki@modusbox.com>
@@ -275,8 +276,11 @@ const openApiBackendNotImplementedHandler = async (context, req, h, item) => {
275
276
  let responseBody, responseStatus
276
277
  // Check for response map file
277
278
  try {
278
- const respMapRawdata = await utils.readFileAsync(item.responseMapFile)
279
- const responseMap = JSON.parse(respMapRawdata)
279
+ const respMapRawdata = await utils.resolveAndLoad(item.responseMapFile)
280
+ const responseMap =
281
+ (typeof respMapRawdata === 'string')
282
+ ? JSON.parse(respMapRawdata)
283
+ : respMapRawdata
280
284
  if (responseMap && responseMap[context.operation.path] && responseMap[context.operation.path][context.request.method]) {
281
285
  const responseInfo = responseMap[context.operation.path][context.request.method]
282
286
  req.customInfo.responseInfo = responseInfo
@@ -346,8 +350,11 @@ const generateAsyncCallback = async (item, context, req) => {
346
350
 
347
351
  // Getting callback info from callback map file
348
352
  try {
349
- const cbMapRawdata = await utils.readFileAsync(item.callbackMapFile)
350
- const callbackMap = JSON.parse(cbMapRawdata)
353
+ const cbMapRawdata = await utils.resolveAndLoad(item.callbackMapFile)
354
+ const callbackMap =
355
+ (typeof cbMapRawdata === 'string')
356
+ ? JSON.parse(cbMapRawdata)
357
+ : cbMapRawdata
351
358
  if (!callbackMap[context.operation.path]) {
352
359
  customLogger.logMessage('error', 'Callback not found for path in callback map file for ' + context.operation.path, req.customInfo.user, { request: req })
353
360
  return
@@ -41,6 +41,7 @@ const uuid = require('uuid')
41
41
  const postmanContext = require('../scripting-engines/postman-sandbox')
42
42
  const javascriptContext = require('../scripting-engines/vm-javascript-sandbox')
43
43
  const { OpenApiMockGenerator } = require('@mojaloop/ml-testing-toolkit-shared-lib')
44
+ const utils = require('../utils')
44
45
 
45
46
  // const jsfRefFilePathPrefix = 'spec_files/jsf_ref_files/'
46
47
 
@@ -342,7 +343,8 @@ const responseRules = async (context, req) => {
342
343
  } else if (curEvent.type === 'MOCK_RESPONSE') {
343
344
  if (req.customInfo.specFile) {
344
345
  const responseGenerator = new OpenApiMockGenerator()
345
- await responseGenerator.load(path.join(req.customInfo.specFile))
346
+ const filePath = await utils.checkUrl(path.join(req.customInfo.specFile))
347
+ await responseGenerator.load(filePath)
346
348
  let jsfRefs1 = []
347
349
  if (req.customInfo.jsfRefFile) {
348
350
  try {
@@ -21,6 +21,7 @@
21
21
 
22
22
  * Mojaloop Foundation
23
23
  - Name Surname <name.surname@mojaloop.io>
24
+ - Shashikant Hirugade <shashi.mojaloop@gmail.com>
24
25
 
25
26
  * ModusBox
26
27
  * Georgi Logodazhki <georgi.logodazhki@modusbox.com>
@@ -42,7 +43,7 @@ const Config = require('../config')
42
43
  const customLogger = require('../requestLogger')
43
44
  const MyEventEmitter = require('../MyEventEmitter')
44
45
  const notificationEmitter = require('../notificationEmitter.js')
45
- const { readFileAsync, headersToLowerCase } = require('../utils')
46
+ const { resolveAndLoad, headersToLowerCase } = require('../utils')
46
47
  const expectOriginal = require('chai').expect // eslint-disable-line
47
48
  const JwsSigning = require('../jws/JwsSigning')
48
49
  const ConnectionProvider = require('../configuration-providers/mb-connection-manager')
@@ -362,8 +363,11 @@ const processTestCase = async (
362
363
  let successCallbackUrl = null
363
364
  let errorCallbackUrl = null
364
365
  if (reqApiDefinition?.asynchronous === true) {
365
- const cbMapRawdata = await readFileAsync(reqApiDefinition.callbackMapFile)
366
- const reqCallbackMap = JSON.parse(cbMapRawdata)
366
+ const cbMapRawdata = await resolveAndLoad(reqApiDefinition.callbackMapFile)
367
+ const reqCallbackMap =
368
+ (typeof cbMapRawdata === 'string')
369
+ ? JSON.parse(cbMapRawdata)
370
+ : cbMapRawdata
367
371
  if (reqCallbackMap[request.operationPath] && reqCallbackMap[request.operationPath][request.method]) {
368
372
  const successCallback = reqCallbackMap[request.operationPath][request.method].successCallback
369
373
  const errorCallback = reqCallbackMap[request.operationPath][request.method].errorCallback
package/src/lib/utils.js CHANGED
@@ -21,12 +21,14 @@
21
21
 
22
22
  * Mojaloop Foundation
23
23
  - Name Surname <name.surname@mojaloop.io>
24
+ - Shashikant Hirugade <shashi.mojaloop@gmail.com>
24
25
 
25
26
  * ModusBox
26
27
  * Georgi Logodazhki <georgi.logodazhki@modusbox.com> (Original Author)
27
28
  --------------
28
29
  ******/
29
30
 
31
+ 'use strict'
30
32
  const fs = require('fs')
31
33
  const mv = require('mv')
32
34
  const { files } = require('node-dir')
@@ -44,6 +46,8 @@ const rmdirAsync = promisify(fs.rmdir)
44
46
  const mvAsync = promisify(mv)
45
47
  const { resolve } = require('path')
46
48
  const yaml = require('js-yaml')
49
+ const http = require('http')
50
+ const https = require('https')
47
51
 
48
52
  const getHeader = (headers, name) => {
49
53
  return Object.entries(headers).find(
@@ -97,6 +101,83 @@ const checkUrl = async fileName => {
97
101
  }
98
102
  }
99
103
 
104
+ const isHttpUrl = (s) => typeof s === 'string' && /^https?:\/\//i.test(s)
105
+
106
+ const fetchText = (url) => new Promise((resolve, reject) => {
107
+ const lib = url.startsWith('https://') ? https : http
108
+ const req = lib.get(
109
+ url,
110
+ { headers: { 'User-Agent': 'mojaloop-ttk' } },
111
+ (res) => {
112
+ // follow redirects
113
+ if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
114
+ res.resume()
115
+ return resolve(fetchText(res.headers.location))
116
+ }
117
+
118
+ if (res.statusCode < 200 || res.statusCode >= 300) {
119
+ const chunks = []
120
+ res.on('data', (c) => chunks.push(c))
121
+ res.on('end', () => {
122
+ const body = Buffer.concat(chunks).toString('utf8')
123
+ reject(new Error(`Failed to fetch ${url}: HTTP ${res.statusCode}\n${body.slice(0, 500)}`))
124
+ })
125
+ return
126
+ }
127
+
128
+ const chunks = []
129
+ res.on('data', (c) => chunks.push(c))
130
+ res.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')))
131
+ }
132
+ )
133
+ req.on('error', reject)
134
+ })
135
+
136
+ const parseJsonOrYaml = (content, label = 'content') => {
137
+ try {
138
+ return JSON.parse(content)
139
+ } catch (jsonErr) {
140
+ try {
141
+ return yaml.load(content)
142
+ } catch (yamlErr) {
143
+ throw new Error(`Failed to parse ${label} as JSON or YAML: ${jsonErr.message}; ${yamlErr.message}`)
144
+ }
145
+ }
146
+ }
147
+
148
+ /**
149
+ * resolveAndLoad:
150
+ * - accepts a local file path OR a direct URL
151
+ * - if local file content is JSON/YAML -> returns parsed value
152
+ * - if local file content is a URL string -> fetches it and returns parsed value
153
+ * - if argument itself is a URL -> fetches it and returns parsed value
154
+ *
155
+ * NOTE: This function resolves relative file paths using TTK_ROOT.
156
+ */
157
+ const resolveAndLoad = async (filePathOrUrl) => {
158
+ // If caller passes URL directly
159
+ if (isHttpUrl(filePathOrUrl)) {
160
+ const remoteText = await fetchText(filePathOrUrl)
161
+ return parseJsonOrYaml(remoteText, filePathOrUrl)
162
+ }
163
+
164
+ // Treat as local file; resolve relative path via TTK_ROOT
165
+ const localPath = resolveRoot(filePathOrUrl)
166
+ const localText = await fs.promises.readFile(localPath, 'utf8')
167
+
168
+ // Parse local JSON/YAML
169
+ const parsedLocal = parseJsonOrYaml(localText, localPath)
170
+
171
+ // If local content is a URL string, follow it
172
+ if (isHttpUrl(parsedLocal)) {
173
+ const remoteText = await fetchText(parsedLocal)
174
+ return parseJsonOrYaml(remoteText, parsedLocal)
175
+ }
176
+
177
+ // Otherwise local file contained actual JSON/YAML content
178
+ return parsedLocal
179
+ }
180
+
100
181
  module.exports = {
101
182
  readFileAsync: resolve1(readFileAsync),
102
183
  writeFileAsync: resolve1(writeFileAsync),
@@ -113,5 +194,6 @@ module.exports = {
113
194
  headersToLowerCase,
114
195
  urlToPath,
115
196
  checkUrl,
197
+ resolveAndLoad,
116
198
  resolve: resolveRoot
117
199
  }