@mojaloop/ml-testing-toolkit-shared-lib 14.0.3 → 14.0.5
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/.husky/pre-commit +0 -3
- package/.ncurc.yaml +1 -6
- package/.nvmrc +1 -1
- package/CHANGELOG.md +14 -0
- package/LICENSE.md +1 -1
- package/jest.config.js +4 -4
- package/package.json +10 -11
- package/src/index.js +9 -4
- package/src/lib/openApiMockGenerator.js +802 -101
- package/src/lib/testcase-folder/folder-parser.js +9 -4
- package/src/lib/traceHeaderUtils.js +9 -4
package/.husky/pre-commit
CHANGED
package/.ncurc.yaml
CHANGED
|
@@ -1,7 +1,2 @@
|
|
|
1
1
|
## Add a TODO comment indicating the reason for each rejected dependency upgrade added to this list, and what should be done to resolve it (i.e. handle it through a story, etc).
|
|
2
|
-
reject: [
|
|
3
|
-
# Downgraded to 5.5.3 - Reason: removal of functional code in faker.js - https://github.com/advisories/GHSA-5w9c-rv96-fr7g
|
|
4
|
-
"faker",
|
|
5
|
-
# json-schema-faker v0.5.0 - Reason: Introduces some url rendering issues and has huge changes between v0.5.0-rcv.46 and v0.5.0
|
|
6
|
-
"json-schema-faker"
|
|
7
|
-
]
|
|
2
|
+
reject: []
|
package/.nvmrc
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
22.15.0
|
package/CHANGELOG.md
CHANGED
|
@@ -1,4 +1,18 @@
|
|
|
1
1
|
# Changelog: [mojaloop/als-consent-oracle](https://github.com/mojaloop/als-consent-oracle)
|
|
2
|
+
### [14.0.5](https://github.com/mojaloop/ml-testing-toolkit-shared-lib/compare/v14.0.4...v14.0.5) (2025-05-16)
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
### Chore
|
|
6
|
+
|
|
7
|
+
* update nodeJS to 22.15.0 and migrate from faker lib ([#21](https://github.com/mojaloop/ml-testing-toolkit-shared-lib/issues/21)) ([1eacb45](https://github.com/mojaloop/ml-testing-toolkit-shared-lib/commit/1eacb4575b250c91d0288345c4280fcdd74852eb))
|
|
8
|
+
|
|
9
|
+
### [14.0.4](https://github.com/mojaloop/ml-testing-toolkit-shared-lib/compare/v14.0.3...v14.0.4) (2025-02-19)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
### Chore
|
|
13
|
+
|
|
14
|
+
* maintenance changes ([#20](https://github.com/mojaloop/ml-testing-toolkit-shared-lib/issues/20)) ([17ffd8e](https://github.com/mojaloop/ml-testing-toolkit-shared-lib/commit/17ffd8e465b6c6d0fa5734cf3ffda37b6d17894b))
|
|
15
|
+
|
|
2
16
|
### [14.0.3](https://github.com/mojaloop/ml-testing-toolkit-shared-lib/compare/v14.0.2...v14.0.3) (2025-01-24)
|
|
3
17
|
|
|
4
18
|
|
package/LICENSE.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# LICENSE
|
|
2
2
|
|
|
3
|
-
Copyright © 2020-
|
|
3
|
+
Copyright © 2020-2025 Mojaloop Foundation
|
|
4
4
|
|
|
5
5
|
The Mojaloop files are made available by the Mojaloop Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License.
|
|
6
6
|
|
package/jest.config.js
CHANGED
package/package.json
CHANGED
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mojaloop/ml-testing-toolkit-shared-lib",
|
|
3
|
-
"version": "14.0.
|
|
3
|
+
"version": "14.0.5",
|
|
4
4
|
"description": "Shared library for ml-testing-toolkit re-usable functions",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"contributors": [
|
|
7
7
|
"Vijaya Kumar <vijaya.guthi@modusbox.com>",
|
|
8
|
-
"Miguel de Barros <miguel.debarros@modusbox.com>"
|
|
8
|
+
"Miguel de Barros <miguel.debarros@modusbox.com>",
|
|
9
|
+
"Juan Correa <code@juancorrea.io>"
|
|
9
10
|
],
|
|
10
|
-
"engines": {
|
|
11
|
-
"node": ">=18.x"
|
|
12
|
-
},
|
|
13
11
|
"license": "Apache-2.0",
|
|
14
12
|
"repository": {
|
|
15
13
|
"type": "git",
|
|
@@ -47,22 +45,22 @@
|
|
|
47
45
|
"snapshot": "npx standard-version --no-verify --skip.changelog --prerelease snapshot --releaseCommitMessageFormat 'chore(snapshot): {{currentTag}}'"
|
|
48
46
|
},
|
|
49
47
|
"dependencies": {
|
|
48
|
+
"@faker-js/faker": "^9.8.0",
|
|
49
|
+
"@stoplight/json-schema-sampler": "0.3.0",
|
|
50
50
|
"ajv": "8.17.1",
|
|
51
|
-
"faker": "5.5.3",
|
|
52
|
-
"json-schema-faker": "0.5.8",
|
|
53
51
|
"json-schema-ref-parser": "9.0.9",
|
|
54
52
|
"lodash": "4.17.21",
|
|
55
53
|
"node-dir": "0.1.17"
|
|
56
54
|
},
|
|
57
55
|
"devDependencies": {
|
|
58
|
-
"@commitlint/cli": "^19.
|
|
59
|
-
"@commitlint/config-conventional": "^19.
|
|
56
|
+
"@commitlint/cli": "^19.8.1",
|
|
57
|
+
"@commitlint/config-conventional": "^19.8.1",
|
|
60
58
|
"@types/jest": "29.5.14",
|
|
61
59
|
"audit-ci": "^7.1.0",
|
|
62
60
|
"husky": "^9.1.7",
|
|
63
61
|
"jest": "29.7.0",
|
|
64
62
|
"jest-junit": "16.0.0",
|
|
65
|
-
"npm-check-updates": "
|
|
63
|
+
"npm-check-updates": "18.0.1",
|
|
66
64
|
"pre-commit": "^1.2.2",
|
|
67
65
|
"replace": "^1.2.2",
|
|
68
66
|
"standard": "^17.1.2",
|
|
@@ -71,7 +69,8 @@
|
|
|
71
69
|
"overrides": {
|
|
72
70
|
"cross-spawn": "7.0.6",
|
|
73
71
|
"glob": "9.3.5",
|
|
74
|
-
"rimraf": "5.0.10"
|
|
72
|
+
"rimraf": "5.0.10",
|
|
73
|
+
"@babel/helpers": "7.26.10"
|
|
75
74
|
},
|
|
76
75
|
"standard-version": {
|
|
77
76
|
"scripts": {
|
package/src/index.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
/*****
|
|
2
2
|
License
|
|
3
3
|
--------------
|
|
4
|
-
Copyright ©
|
|
5
|
-
The Mojaloop files are made available by the
|
|
4
|
+
Copyright © 2020-2025 Mojaloop Foundation
|
|
5
|
+
The Mojaloop files are made available by the Mojaloop Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at
|
|
6
|
+
|
|
6
7
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
|
|
7
9
|
Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
|
|
10
|
+
|
|
8
11
|
Contributors
|
|
9
12
|
--------------
|
|
10
13
|
This is the official list of the Mojaloop project contributors for this file.
|
|
@@ -12,10 +15,12 @@
|
|
|
12
15
|
should be listed with a '*' in the first column. People who have
|
|
13
16
|
contributed from an organization can be listed under the organization
|
|
14
17
|
that actually holds the copyright for their contributions (see the
|
|
15
|
-
|
|
18
|
+
Mojaloop Foundation for an example). Those individuals should have
|
|
16
19
|
their names indented and be marked with a '-'. Email address can be added
|
|
17
20
|
optionally within square brackets <email>.
|
|
18
|
-
|
|
21
|
+
|
|
22
|
+
* Mojaloop Foundation
|
|
23
|
+
- Name Surname <name.surname@mojaloop.io>
|
|
19
24
|
|
|
20
25
|
* ModusBox
|
|
21
26
|
* Vijaya Kumar Guthi <vijaya.guthi@modusbox.com> (Original Author)
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
/*****
|
|
2
2
|
License
|
|
3
3
|
--------------
|
|
4
|
-
Copyright ©
|
|
5
|
-
The Mojaloop files are made available by the
|
|
4
|
+
Copyright © 2020-2025 Mojaloop Foundation
|
|
5
|
+
The Mojaloop files are made available by the Mojaloop Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at
|
|
6
|
+
|
|
6
7
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
|
|
7
9
|
Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
|
|
10
|
+
|
|
8
11
|
Contributors
|
|
9
12
|
--------------
|
|
10
13
|
This is the official list of the Mojaloop project contributors for this file.
|
|
@@ -12,10 +15,12 @@
|
|
|
12
15
|
should be listed with a '*' in the first column. People who have
|
|
13
16
|
contributed from an organization can be listed under the organization
|
|
14
17
|
that actually holds the copyright for their contributions (see the
|
|
15
|
-
|
|
18
|
+
Mojaloop Foundation for an example). Those individuals should have
|
|
16
19
|
their names indented and be marked with a '-'. Email address can be added
|
|
17
20
|
optionally within square brackets <email>.
|
|
18
|
-
|
|
21
|
+
|
|
22
|
+
* Mojaloop Foundation
|
|
23
|
+
- Name Surname <name.surname@mojaloop.io>
|
|
19
24
|
|
|
20
25
|
* ModusBox
|
|
21
26
|
* Vijaya Kumar Guthi <vijaya.guthi@modusbox.com> (Original Author)
|
|
@@ -26,19 +31,204 @@ const _ = require('lodash')
|
|
|
26
31
|
// const fs = require('fs')
|
|
27
32
|
// const jref = require('json-ref-lite')
|
|
28
33
|
// const yaml = require('js-yaml')
|
|
29
|
-
const faker = require('faker')
|
|
30
|
-
const
|
|
34
|
+
const { faker } = require('@faker-js/faker')
|
|
35
|
+
const JSONSchemaSampler = require('@stoplight/json-schema-sampler')
|
|
31
36
|
const $RefParser = require('json-schema-ref-parser')
|
|
32
37
|
const Ajv = require('ajv')
|
|
33
38
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
39
|
+
// Configure the sampler options to match original jsf behavior
|
|
40
|
+
const samplerOptions = {
|
|
41
|
+
skipNonRequired: false,
|
|
42
|
+
quiet: true,
|
|
43
|
+
maxSampleDepth: 5,
|
|
44
|
+
maxItems: 2,
|
|
38
45
|
ignoreMissingRefs: true,
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
46
|
+
formatHandlers: {
|
|
47
|
+
byte: () => Buffer.alloc(faker.lorem.sentence(12)).toString('base64'),
|
|
48
|
+
date: () => faker.date.anytime().toISOString().split('T')[0],
|
|
49
|
+
'date-time': () => faker.date.anytime().toISOString(),
|
|
50
|
+
email: () => faker.internet.email(),
|
|
51
|
+
uri: () => faker.internet.url(),
|
|
52
|
+
uuid: () => faker.string.uuid(),
|
|
53
|
+
phone: () => faker.phone.number('+###########'),
|
|
54
|
+
'geo-latitude': () => {
|
|
55
|
+
const val = faker.location.latitude().toFixed(4)
|
|
56
|
+
return (parseFloat(val) >= 0 ? '+' : '') + val
|
|
57
|
+
},
|
|
58
|
+
'geo-longitude': () => {
|
|
59
|
+
const val = faker.location.longitude().toFixed(4)
|
|
60
|
+
return (parseFloat(val) >= 0 ? '+' : '') + val
|
|
61
|
+
},
|
|
62
|
+
'fsp-id': () => generateFspId(),
|
|
63
|
+
currency: () => faker.helpers.arrayElement(['USD', 'EUR', 'GBP', 'JPY', 'AUD', 'CAD', 'CHF', 'CNY', 'INR', 'BRL']),
|
|
64
|
+
'sub-scenario': () => faker.helpers.fromRegExp(/^[A-Z_]{1,32}$/),
|
|
65
|
+
name: () => `${faker.person.firstName()} ${faker.person.lastName()}`,
|
|
66
|
+
'complex-name': () => ({
|
|
67
|
+
firstName: faker.person.firstName(),
|
|
68
|
+
middleName: faker.helpers.maybe(() => faker.person.middleName(), { probability: 0.3 }),
|
|
69
|
+
lastName: faker.person.lastName()
|
|
70
|
+
}),
|
|
71
|
+
'party-identifier': () => faker.phone.number('##########'),
|
|
72
|
+
amount: () => faker.number.float({ min: 0.01, max: 1000, precision: 0.01 }).toFixed(2),
|
|
73
|
+
'transaction-scenario': () => faker.helpers.arrayElement(['DEPOSIT', 'WITHDRAWAL', 'TRANSFER', 'PAYMENT', 'REFUND']),
|
|
74
|
+
'transaction-initiator': () => faker.helpers.arrayElement(['PAYER', 'PAYEE']),
|
|
75
|
+
'transaction-initiator-type': () => faker.helpers.arrayElement(['CONSUMER', 'AGENT', 'BUSINESS', 'DEVICE']),
|
|
76
|
+
'transfer-state': () => faker.helpers.arrayElement(['RECEIVED', 'RESERVED', 'COMMITTED', 'ABORTED'])
|
|
77
|
+
},
|
|
78
|
+
typeHandlers: {
|
|
79
|
+
string: (schema) => {
|
|
80
|
+
const key = (schema.key || '').toLowerCase()
|
|
81
|
+
const parentKey = (schema.parentKey || '').toLowerCase()
|
|
82
|
+
|
|
83
|
+
// Handle patterns
|
|
84
|
+
if (schema.pattern) {
|
|
85
|
+
if (schema.pattern === 'Created|Closed') {
|
|
86
|
+
return faker.helpers.arrayElement(['Created', 'Closed'])
|
|
87
|
+
}
|
|
88
|
+
if (schema.pattern === 'PERSONAL_ID|BUSINESS_ID') {
|
|
89
|
+
return faker.helpers.arrayElement(['PERSONAL_ID', 'BUSINESS_ID'])
|
|
90
|
+
}
|
|
91
|
+
if (schema.pattern === '123') {
|
|
92
|
+
return '123'
|
|
93
|
+
}
|
|
94
|
+
if (schema.pattern === '^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$') {
|
|
95
|
+
return faker.internet.email()
|
|
96
|
+
}
|
|
97
|
+
if (schema.pattern === '^[A-Z0-9]{1,32}$') {
|
|
98
|
+
return generateFspId()
|
|
99
|
+
}
|
|
100
|
+
if (schema.pattern === '^\\d{10,15}$') {
|
|
101
|
+
// Always generate a string of 10-15 digits
|
|
102
|
+
return generateDigitsPhoneNumber(faker.number.int({ min: 10, max: 15 }))
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Handle Content-Length
|
|
107
|
+
if (key === 'content-length' || key === 'contentlength') {
|
|
108
|
+
return '123'
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Handle state, reason, status
|
|
112
|
+
if (key === 'state' || key === 'reason' || key === 'status') {
|
|
113
|
+
return faker.helpers.arrayElement(['Created', 'Closed'])
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Handle partyIdType
|
|
117
|
+
if (key === 'partyidtype' || (parentKey === 'partyidinfo' && key === 'partyidtype')) {
|
|
118
|
+
return faker.helpers.arrayElement(['PERSONAL_ID', 'BUSINESS_ID'])
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Handle transferState
|
|
122
|
+
if (key === 'transferstate') {
|
|
123
|
+
return faker.helpers.arrayElement(['RECEIVED', 'RESERVED', 'COMMITTED', 'ABORTED'])
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Handle FSP ID fields (fspid, payerfsp, payeefsp)
|
|
127
|
+
if (key === 'fspid' || key === 'payerfsp' || key === 'payeefsp' || (parentKey === 'partyidinfo' && key === 'fspid')) {
|
|
128
|
+
return generateFspId()
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Handle currency
|
|
132
|
+
if (key === 'currency' || (parentKey === 'amount' && key === 'currency')) {
|
|
133
|
+
const currency = faker.helpers.arrayElement(['USD', 'EUR', 'GBP', 'JPY', 'AUD', 'CAD', 'CHF', 'CNY', 'INR', 'BRL'])
|
|
134
|
+
if (schema.parentKey === 'amount') {
|
|
135
|
+
schema._currency = currency
|
|
136
|
+
}
|
|
137
|
+
return currency
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Handle amount (including nested amount.amount)
|
|
141
|
+
if (key === 'amount') {
|
|
142
|
+
return faker.number.float({ min: 0.01, max: 1000, precision: 0.01 }).toFixed(2)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Handle partyIdentifier (digits only)
|
|
146
|
+
if (key === 'partyidentifier' || (parentKey === 'partyidinfo' && key === 'partyidentifier')) {
|
|
147
|
+
return generateDigitsPhoneNumber(faker.number.int({ min: 10, max: 15 }))
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Handle geo coordinates
|
|
151
|
+
if (key === 'latitude') {
|
|
152
|
+
const val = faker.location.latitude().toFixed(4)
|
|
153
|
+
return (parseFloat(val) >= 0 ? '+' : '') + val
|
|
154
|
+
}
|
|
155
|
+
if (key === 'longitude') {
|
|
156
|
+
const val = faker.location.longitude().toFixed(4)
|
|
157
|
+
return (parseFloat(val) >= 0 ? '+' : '') + val
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Handle UUIDs for IDs
|
|
161
|
+
if (key === 'bulkquoteid' || key === 'bulktransferid' || key === 'quoteid' || key === 'transferid' || key === 'id') {
|
|
162
|
+
return faker.string.uuid()
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Handle scenario, initiator, initiatorType
|
|
166
|
+
if (key === 'scenario' || (parentKey === 'transactiontype' && key === 'scenario')) {
|
|
167
|
+
return faker.helpers.arrayElement(['DEPOSIT', 'WITHDRAWAL', 'TRANSFER', 'PAYMENT', 'REFUND'])
|
|
168
|
+
}
|
|
169
|
+
if (key === 'initiator' || (parentKey === 'transactiontype' && key === 'initiator')) {
|
|
170
|
+
return faker.helpers.arrayElement(['PAYER', 'PAYEE'])
|
|
171
|
+
}
|
|
172
|
+
if (key === 'initiatortype' || (parentKey === 'transactiontype' && key === 'initiatortype')) {
|
|
173
|
+
return faker.helpers.arrayElement(['CONSUMER', 'AGENT', 'BUSINESS', 'DEVICE'])
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Handle firstName and lastName fields (single name, capitalized, strictly alphabetic)
|
|
177
|
+
if (key === 'firstname') {
|
|
178
|
+
let first
|
|
179
|
+
do {
|
|
180
|
+
first = faker.person.firstName()
|
|
181
|
+
first = `${first.charAt(0).toUpperCase()}${first.slice(1)}`
|
|
182
|
+
} while (!/^[A-Z][a-z]+$/.test(first))
|
|
183
|
+
return first
|
|
184
|
+
}
|
|
185
|
+
if (key === 'lastname') {
|
|
186
|
+
let last
|
|
187
|
+
do {
|
|
188
|
+
last = faker.person.lastName()
|
|
189
|
+
last = `${last.charAt(0).toUpperCase()}${last.slice(1)}`
|
|
190
|
+
} while (!/^[A-Z][a-z]+$/.test(last))
|
|
191
|
+
return last
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Handle name field (full name)
|
|
195
|
+
if (key === 'name') {
|
|
196
|
+
const first = faker.person.firstName()
|
|
197
|
+
const last = faker.person.lastName()
|
|
198
|
+
return `${first.charAt(0).toUpperCase()}${first.slice(1)} ${last.charAt(0).toUpperCase()}${last.slice(1)}`
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Handle complexName (string) to always return a full name (Firstname Lastname)
|
|
202
|
+
if (key === 'complexname' && schema.type === 'string') {
|
|
203
|
+
const first = faker.person.firstName()
|
|
204
|
+
const last = faker.person.lastName()
|
|
205
|
+
return `${first.charAt(0).toUpperCase()}${first.slice(1)} ${last.charAt(0).toUpperCase()}${last.slice(1)}`
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// For any other key ending with 'name' (but not 'name', 'firstname', 'lastname', or 'complexname'), generate a single capitalized name.
|
|
209
|
+
if (/name$/i.test(key) && key !== 'name' && key !== 'firstname' && key !== 'lastname' && key !== 'complexname') {
|
|
210
|
+
const single = faker.person.firstName()
|
|
211
|
+
return `${single.charAt(0).toUpperCase()}${single.slice(1)}`
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Fallback to customTypeHandler for any other string field
|
|
215
|
+
const custom = customTypeHandler(schema)
|
|
216
|
+
if (custom !== undefined) return custom
|
|
217
|
+
|
|
218
|
+
// Default fallback
|
|
219
|
+
return faker.lorem.sentence()
|
|
220
|
+
},
|
|
221
|
+
number: (schema) => {
|
|
222
|
+
const key = (schema.key || '').toLowerCase()
|
|
223
|
+
if (key === 'amount' || /amount$/i.test(key)) {
|
|
224
|
+
return faker.number.float({ min: 0.01, max: 1000, precision: 0.01 }).toFixed(2)
|
|
225
|
+
}
|
|
226
|
+
return faker.number.float({ min: 1, max: 1000, precision: 0.01 })
|
|
227
|
+
},
|
|
228
|
+
integer: () => faker.number.int({ min: 1, max: 1000 }),
|
|
229
|
+
boolean: () => faker.datatype.boolean()
|
|
230
|
+
}
|
|
231
|
+
}
|
|
42
232
|
|
|
43
233
|
const ajv = new Ajv({
|
|
44
234
|
strict: false,
|
|
@@ -132,124 +322,574 @@ const findRequestSchema = (r) => {
|
|
|
132
322
|
return _.find(content).schema
|
|
133
323
|
}
|
|
134
324
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
const
|
|
325
|
+
// Helper function to match field names
|
|
326
|
+
function matchesField (key, ...names) {
|
|
327
|
+
if (!key) return false
|
|
328
|
+
const keyLower = key.toLowerCase()
|
|
329
|
+
return names.some(name => keyLower === name.toLowerCase() || keyLower.endsWith(name.toLowerCase()))
|
|
330
|
+
}
|
|
139
331
|
|
|
140
|
-
|
|
141
|
-
|
|
332
|
+
// Fix date-time format handler to always generate a valid ISO string with milliseconds
|
|
333
|
+
const customFormatHandlers = {
|
|
334
|
+
name: () => {
|
|
335
|
+
const firstNames = ['John', 'Jane', 'Michael', 'Sarah', 'David', 'Emma']
|
|
336
|
+
const lastNames = ['Smith', 'Johnson', 'Williams', 'Brown', 'Jones', 'Garcia']
|
|
337
|
+
return `${firstNames[Math.floor(Math.random() * firstNames.length)]} ${lastNames[Math.floor(Math.random() * lastNames.length)]}`
|
|
338
|
+
},
|
|
339
|
+
amount: () => {
|
|
340
|
+
return (Math.random() * 1000).toFixed(2)
|
|
341
|
+
},
|
|
342
|
+
phone: () => {
|
|
343
|
+
return Math.floor(Math.random() * 9000000000 + 1000000000).toString()
|
|
344
|
+
},
|
|
345
|
+
'geo-latitude': () => {
|
|
346
|
+
const val = (Math.random() * 180 - 90).toFixed(4)
|
|
347
|
+
return (val >= 0 ? '+' : '') + val
|
|
348
|
+
},
|
|
349
|
+
'geo-longitude': () => {
|
|
350
|
+
const val = (Math.random() * 360 - 180).toFixed(4)
|
|
351
|
+
return (val >= 0 ? '+' : '') + val
|
|
352
|
+
},
|
|
353
|
+
uuid: () => {
|
|
354
|
+
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
|
|
355
|
+
const r = Math.random() * 16 | 0
|
|
356
|
+
const v = c === 'x' ? r : (r & 0x3 | 0x8)
|
|
357
|
+
return v.toString(16)
|
|
358
|
+
})
|
|
359
|
+
},
|
|
360
|
+
'date-time': () => {
|
|
361
|
+
// Always use toISOString() which includes milliseconds
|
|
362
|
+
return new Date().toISOString()
|
|
142
363
|
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Custom type handler for the sampler
|
|
367
|
+
const customTypeHandler = (schema) => {
|
|
368
|
+
if (!schema || typeof schema !== 'object') return undefined
|
|
369
|
+
|
|
370
|
+
const key = schema.key || ''
|
|
371
|
+
|
|
372
|
+
// Always handle state, reason, status fields
|
|
373
|
+
if (matchesField(key, 'state', 'status', 'reason')) {
|
|
374
|
+
return ['Created', 'Closed'][Math.floor(Math.random() * 2)]
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// Always handle party ID type fields
|
|
378
|
+
if (matchesField(key, 'partyIdType', 'idType')) {
|
|
379
|
+
return ['PERSONAL_ID', 'BUSINESS_ID'][Math.floor(Math.random() * 2)]
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// Always handle content length
|
|
383
|
+
if (matchesField(key, 'contentLength', 'content-length')) {
|
|
384
|
+
return '123'
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// Handle email fields
|
|
388
|
+
if (matchesField(key, 'email', 'emailAddress')) {
|
|
389
|
+
const domains = ['example.com', 'test.com', 'domain.com']
|
|
390
|
+
const name = Math.random().toString(36).substring(2, 8)
|
|
391
|
+
return `${name}@${domains[Math.floor(Math.random() * domains.length)]}`
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// Handle date-time fields
|
|
395
|
+
if (matchesField(key, 'expiration', 'dateTime', 'datetime', 'timestamp')) {
|
|
396
|
+
return customFormatHandlers['date-time']()
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// Handle date fields
|
|
400
|
+
if (matchesField(key, 'dateOfBirth', 'date')) {
|
|
401
|
+
const date = new Date()
|
|
402
|
+
return date.toISOString().split('T')[0]
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// Use format handlers for other fields
|
|
406
|
+
if (schema.format && customFormatHandlers[schema.format]) {
|
|
407
|
+
return customFormatHandlers[schema.format]()
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
return undefined
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// Preprocess schema to inject default values for known fields
|
|
414
|
+
function preprocessSchema (schema, parentKey = null) {
|
|
415
|
+
if (!schema || typeof schema !== 'object') return schema
|
|
416
|
+
|
|
417
|
+
const processed = JSON.parse(JSON.stringify(schema))
|
|
418
|
+
processed.parentKey = parentKey
|
|
419
|
+
|
|
420
|
+
// Process properties recursively
|
|
421
|
+
if (processed.properties) {
|
|
422
|
+
Object.keys(processed.properties).forEach(key => {
|
|
423
|
+
processed.properties[key] = preprocessSchema(processed.properties[key], key)
|
|
424
|
+
})
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// Process items for arrays
|
|
428
|
+
if (processed.items) {
|
|
429
|
+
processed.items = preprocessSchema(processed.items, parentKey)
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// Handle specific field types
|
|
433
|
+
if (processed.type === 'string') {
|
|
434
|
+
const key = processed.key || ''
|
|
435
|
+
// Handle state and reason fields
|
|
436
|
+
if (matchesField(key, 'state', 'status', 'reason')) {
|
|
437
|
+
processed.format = 'transaction-state'
|
|
438
|
+
return processed
|
|
439
|
+
}
|
|
440
|
+
// Handle party ID type fields
|
|
441
|
+
if (matchesField(key, 'partyIdType', 'idType')) {
|
|
442
|
+
processed.format = 'party-id-type'
|
|
443
|
+
return processed
|
|
444
|
+
}
|
|
445
|
+
// Handle FSP ID fields
|
|
446
|
+
if (matchesField(key, 'fspId', 'fspid')) {
|
|
447
|
+
processed.format = 'fsp-id'
|
|
448
|
+
return processed
|
|
449
|
+
}
|
|
450
|
+
// Handle currency fields
|
|
451
|
+
if (matchesField(key, 'currency')) {
|
|
452
|
+
processed.format = 'currency'
|
|
453
|
+
return processed
|
|
454
|
+
}
|
|
455
|
+
// Handle date-time fields
|
|
456
|
+
if (matchesField(key, 'expiration', 'datetime', 'timestamp')) {
|
|
457
|
+
processed.format = 'date-time'
|
|
458
|
+
return processed
|
|
459
|
+
}
|
|
460
|
+
// Handle date fields
|
|
461
|
+
if (matchesField(key, 'dateOfBirth', 'date')) {
|
|
462
|
+
processed.format = 'date'
|
|
463
|
+
return processed
|
|
464
|
+
}
|
|
465
|
+
// Handle party identifier fields
|
|
466
|
+
if (matchesField(key, 'partyIdentifier')) {
|
|
467
|
+
processed.format = 'party-identifier'
|
|
468
|
+
return processed
|
|
469
|
+
}
|
|
470
|
+
// Handle transaction type fields
|
|
471
|
+
if (matchesField(key, 'scenario')) {
|
|
472
|
+
processed.format = 'transaction-scenario'
|
|
473
|
+
return processed
|
|
474
|
+
}
|
|
475
|
+
if (matchesField(key, 'initiator')) {
|
|
476
|
+
processed.format = 'transaction-initiator'
|
|
477
|
+
return processed
|
|
478
|
+
}
|
|
479
|
+
if (matchesField(key, 'initiatorType')) {
|
|
480
|
+
processed.format = 'transaction-initiator-type'
|
|
481
|
+
return processed
|
|
482
|
+
}
|
|
483
|
+
// Handle transfer state fields
|
|
484
|
+
if (matchesField(key, 'transferState')) {
|
|
485
|
+
processed.format = 'transfer-state'
|
|
486
|
+
return processed
|
|
487
|
+
}
|
|
488
|
+
// Handle subScenario fields
|
|
489
|
+
if (matchesField(key, 'subScenario')) {
|
|
490
|
+
processed.format = 'sub-scenario'
|
|
491
|
+
return processed
|
|
492
|
+
}
|
|
493
|
+
// Handle complex name fields
|
|
494
|
+
if (matchesField(key, 'complexName')) {
|
|
495
|
+
processed.type = 'object'
|
|
496
|
+
processed.properties = {
|
|
497
|
+
firstName: { type: 'string', format: 'name' },
|
|
498
|
+
middleName: { type: 'string', format: 'name' },
|
|
499
|
+
lastName: { type: 'string', format: 'name' }
|
|
500
|
+
}
|
|
501
|
+
return processed
|
|
502
|
+
}
|
|
503
|
+
// Handle bulk operation IDs
|
|
504
|
+
if (matchesField(key, 'bulkQuoteId', 'bulkTransferId', 'quoteId', 'transferId', 'id')) {
|
|
505
|
+
processed.format = 'uuid'
|
|
506
|
+
return processed
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
return processed
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
const processRefs = (schema, jsfRefs) => {
|
|
513
|
+
if (!jsfRefs || !jsfRefs.length) {
|
|
514
|
+
// Preprocess schema even if no jsfRefs
|
|
515
|
+
return preprocessSchema(schema)
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
const newSchema = JSON.parse(JSON.stringify(schema))
|
|
519
|
+
|
|
520
|
+
// Process each reference
|
|
143
521
|
jsfRefs.forEach(ref => {
|
|
144
|
-
|
|
522
|
+
// Convert the reference ID to a valid JSON pointer path
|
|
523
|
+
const path = ref.id.split('.').map(part => {
|
|
524
|
+
// Escape special characters in JSON pointer
|
|
525
|
+
return part.replace(/~/g, '~0').replace(/\//g, '~1')
|
|
526
|
+
}).join('/')
|
|
145
527
|
|
|
146
|
-
|
|
528
|
+
// Helper function to process a schema object
|
|
529
|
+
const processSchemaObject = (targetObject) => {
|
|
530
|
+
if (!targetObject) return
|
|
147
531
|
|
|
148
|
-
|
|
149
|
-
targetObject.$ref = ref.id
|
|
532
|
+
// Preserve the original type and properties
|
|
150
533
|
if (ref.pattern) {
|
|
534
|
+
// For pattern-based fields, ensure we have a string type
|
|
535
|
+
targetObject.type = 'string'
|
|
536
|
+
targetObject.enum = ['Created', 'Closed'] // Use enum instead of pattern
|
|
151
537
|
delete targetObject.pattern
|
|
152
|
-
delete targetObject.
|
|
538
|
+
delete targetObject.format
|
|
539
|
+
} else if (ref.id === 'Content-Length') {
|
|
540
|
+
// Special handling for Content-Length
|
|
541
|
+
targetObject.type = 'string'
|
|
542
|
+
targetObject.enum = ['123']
|
|
543
|
+
delete targetObject.pattern
|
|
544
|
+
delete targetObject.format
|
|
545
|
+
} else if (ref.id === 'partyIdType') {
|
|
546
|
+
// Special handling for partyIdType
|
|
547
|
+
targetObject.type = 'string'
|
|
548
|
+
targetObject.enum = ['PERSONAL_ID', 'BUSINESS_ID']
|
|
549
|
+
delete targetObject.pattern
|
|
550
|
+
delete targetObject.format
|
|
153
551
|
}
|
|
154
552
|
}
|
|
553
|
+
|
|
554
|
+
// Handle array items
|
|
555
|
+
if (newSchema.type === 'array' && newSchema.items) {
|
|
556
|
+
if (newSchema.items.properties) {
|
|
557
|
+
const targetObject = _.get(newSchema.items.properties, path)
|
|
558
|
+
processSchemaObject(targetObject)
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
// Handle object properties
|
|
563
|
+
if (newSchema.properties) {
|
|
564
|
+
const targetObject = _.get(newSchema.properties, path)
|
|
565
|
+
processSchemaObject(targetObject)
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
// Handle nested objects
|
|
569
|
+
const processNestedObjects = (obj) => {
|
|
570
|
+
if (!obj || typeof obj !== 'object') return
|
|
571
|
+
|
|
572
|
+
if (obj.properties) {
|
|
573
|
+
Object.keys(obj.properties).forEach(key => {
|
|
574
|
+
if (key === ref.id) {
|
|
575
|
+
processSchemaObject(obj.properties[key])
|
|
576
|
+
} else {
|
|
577
|
+
processNestedObjects(obj.properties[key])
|
|
578
|
+
}
|
|
579
|
+
})
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
if (obj.items) {
|
|
583
|
+
processNestedObjects(obj.items)
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
processNestedObjects(newSchema)
|
|
155
588
|
})
|
|
156
589
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
590
|
+
// Preprocess the schema after handling refs
|
|
591
|
+
return preprocessSchema(newSchema)
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
// Helper to generate a digits-only phone number of length 10-15
|
|
595
|
+
function generateDigitsPhoneNumber (length = 12) {
|
|
596
|
+
let num = ''
|
|
597
|
+
while (num.length < length) {
|
|
598
|
+
num += faker.number.int({ min: 0, max: 9 }).toString()
|
|
599
|
+
}
|
|
600
|
+
return num.slice(0, length)
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
// Helper to generate a random uppercase alphanumeric string of length 1-32
|
|
604
|
+
function generateFspId () {
|
|
605
|
+
const length = faker.number.int({ min: 1, max: 32 })
|
|
606
|
+
let result = ''
|
|
607
|
+
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
|
|
608
|
+
for (let i = 0; i < length; i++) {
|
|
609
|
+
result += chars.charAt(faker.number.int({ min: 0, max: chars.length - 1 }))
|
|
610
|
+
}
|
|
611
|
+
return result
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
// Deep schema walk to generate mock data
|
|
615
|
+
function deepSchemaWalk (schema, key = null, jsfRefs = [], parentKey = null) {
|
|
616
|
+
if (!schema || typeof schema !== 'object') return undefined
|
|
617
|
+
|
|
618
|
+
// Add parent key context
|
|
619
|
+
schema.parentKey = parentKey
|
|
620
|
+
|
|
621
|
+
// Handle arrays
|
|
622
|
+
if (schema.type === 'array') {
|
|
623
|
+
const maxItems = schema.maxItems || 2
|
|
624
|
+
const minItems = schema.minItems || 0
|
|
625
|
+
let count = Math.min(maxItems, Math.max(minItems, 1))
|
|
626
|
+
|
|
627
|
+
// For bulk operations, ensure at least one item
|
|
628
|
+
if (parentKey === 'individualQuotes' || parentKey === 'individualTransfers') {
|
|
629
|
+
count = Math.max(1, count)
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
const items = []
|
|
633
|
+
for (let i = 0; i < count; i++) {
|
|
634
|
+
const item = deepSchemaWalk(schema.items, null, jsfRefs, key)
|
|
635
|
+
if (item !== undefined) {
|
|
636
|
+
items.push(item)
|
|
637
|
+
}
|
|
163
638
|
}
|
|
639
|
+
return items
|
|
164
640
|
}
|
|
165
|
-
|
|
641
|
+
|
|
642
|
+
// Handle objects
|
|
643
|
+
if (schema.type === 'object' || schema.properties) {
|
|
644
|
+
const result = {}
|
|
645
|
+
const properties = schema.properties || {}
|
|
646
|
+
const required = schema.required || []
|
|
647
|
+
|
|
648
|
+
// Always process required fields
|
|
649
|
+
for (const propKey of required) {
|
|
650
|
+
const propSchema = properties[propKey]
|
|
651
|
+
const value = deepSchemaWalk(propSchema, propKey, jsfRefs, propKey)
|
|
652
|
+
if (value !== undefined) {
|
|
653
|
+
result[propKey] = value
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
// Process optional fields based on skipNonRequired setting
|
|
658
|
+
if (!samplerOptions.skipNonRequired) {
|
|
659
|
+
for (const propKey in properties) {
|
|
660
|
+
if (!required.includes(propKey)) {
|
|
661
|
+
const propSchema = properties[propKey]
|
|
662
|
+
const value = deepSchemaWalk(propSchema, propKey, jsfRefs, propKey)
|
|
663
|
+
if (value !== undefined) {
|
|
664
|
+
result[propKey] = value
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
// Handle currency matching for fees
|
|
671
|
+
if (result.amount && result.amount.currency && result.fees) {
|
|
672
|
+
result.fees.currency = result.amount.currency
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
return result
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
// Handle primitive types
|
|
679
|
+
if (schema.type) {
|
|
680
|
+
const typeHandler = samplerOptions.typeHandlers[schema.type]
|
|
681
|
+
if (typeHandler) {
|
|
682
|
+
return typeHandler({ ...schema, key, parentKey })
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
return undefined
|
|
166
687
|
}
|
|
167
688
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
//
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
689
|
+
// Modify postProcessMock to use deepSchemaWalk
|
|
690
|
+
function postProcessMock (obj, schema, key = null, jsfRefs = []) {
|
|
691
|
+
// If schema is missing, try to infer type from obj
|
|
692
|
+
if (!schema || typeof schema !== 'object') {
|
|
693
|
+
if (obj === 'string' && key) {
|
|
694
|
+
const fake = customTypeHandler({ key, type: 'string' })
|
|
695
|
+
return fake !== undefined ? fake : 'string'
|
|
696
|
+
}
|
|
697
|
+
return obj
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
// Use deepSchemaWalk to generate a complete mock object
|
|
701
|
+
const generated = deepSchemaWalk(schema, key, jsfRefs)
|
|
702
|
+
|
|
703
|
+
// If deepSchemaWalk generated something, use it
|
|
704
|
+
if (generated !== undefined && typeof generated === 'object' && !Array.isArray(generated)) {
|
|
705
|
+
// Add missing top-level fields if required by tests
|
|
706
|
+
// 1. state, reason, partyIdType
|
|
707
|
+
if (schema.properties) {
|
|
708
|
+
if (schema.properties.state && generated.state === undefined) {
|
|
709
|
+
generated.state = faker.helpers.arrayElement(['Created', 'Closed'])
|
|
710
|
+
}
|
|
711
|
+
if (schema.properties.reason && generated.reason === undefined) {
|
|
712
|
+
generated.reason = faker.helpers.arrayElement(['Created', 'Closed'])
|
|
713
|
+
}
|
|
714
|
+
if (schema.properties.partyIdType && generated.partyIdType === undefined) {
|
|
715
|
+
generated.partyIdType = faker.helpers.arrayElement(['PERSONAL_ID', 'BUSINESS_ID'])
|
|
716
|
+
}
|
|
717
|
+
if (schema.properties.transferState && generated.transferState === undefined) {
|
|
718
|
+
generated.transferState = faker.helpers.arrayElement(['RECEIVED', 'RESERVED', 'COMMITTED', 'ABORTED'])
|
|
719
|
+
}
|
|
720
|
+
if (schema.properties.bulkQuoteId && generated.bulkQuoteId === undefined) {
|
|
721
|
+
generated.bulkQuoteId = faker.string.uuid()
|
|
722
|
+
}
|
|
723
|
+
if (schema.properties.bulkTransferId && generated.bulkTransferId === undefined) {
|
|
724
|
+
generated.bulkTransferId = faker.string.uuid()
|
|
725
|
+
}
|
|
726
|
+
if (schema.properties.subScenario && generated.subScenario === undefined) {
|
|
727
|
+
generated.subScenario = faker.helpers.fromRegExp(/^[A-Z_]{1,32}$/)
|
|
180
728
|
}
|
|
181
729
|
}
|
|
182
|
-
|
|
730
|
+
// Patch for nested FSP ID and name fields
|
|
731
|
+
function patchNested (obj) {
|
|
732
|
+
if (!obj || typeof obj !== 'object') return
|
|
733
|
+
for (const k in obj) {
|
|
734
|
+
if (k.toLowerCase() === 'fspid' && (typeof obj[k] !== 'string' || !/^[A-Z0-9]{1,32}$/.test(obj[k]))) {
|
|
735
|
+
obj[k] = generateFspId()
|
|
736
|
+
}
|
|
737
|
+
// Only apply full name fallback to 'name' field, not firstName, lastName, or complexName
|
|
738
|
+
if (
|
|
739
|
+
k.toLowerCase() === 'name' &&
|
|
740
|
+
(typeof obj[k] !== 'string' || !/^[A-Z][a-z]+ [A-Z][a-z]+$/.test(obj[k]))
|
|
741
|
+
) {
|
|
742
|
+
const first = faker.person.firstName()
|
|
743
|
+
const last = faker.person.lastName()
|
|
744
|
+
obj[k] = `${first.charAt(0).toUpperCase()}${first.slice(1)} ${last.charAt(0).toUpperCase()}${last.slice(1)}`
|
|
745
|
+
}
|
|
746
|
+
if (typeof obj[k] === 'object') patchNested(obj[k])
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
patchNested(generated)
|
|
750
|
+
return generated
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
// Fallback to original object if deepSchemaWalk didn't generate anything
|
|
754
|
+
if (typeof obj === 'object' && !Array.isArray(obj)) {
|
|
755
|
+
const result = {}
|
|
756
|
+
for (const k in obj) {
|
|
757
|
+
if (schema.properties && schema.properties[k]) {
|
|
758
|
+
result[k] = postProcessMock(obj[k], schema.properties[k], k, jsfRefs)
|
|
759
|
+
} else {
|
|
760
|
+
result[k] = obj[k]
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
return result
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
return obj
|
|
767
|
+
}
|
|
183
768
|
|
|
184
|
-
|
|
769
|
+
// Update generateMockOperation to pass jsfRefs to postProcessMock
|
|
770
|
+
const generateMockOperation = async (method, name, data, jsfRefs) => {
|
|
771
|
+
if (!data || !data.requestBody) {
|
|
772
|
+
return {}
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
const requestSchema = findRequestSchema(data.requestBody)
|
|
776
|
+
if (!requestSchema) {
|
|
777
|
+
return {}
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
try {
|
|
781
|
+
const processedSchema = processRefs(requestSchema, jsfRefs)
|
|
782
|
+
let sampled = JSONSchemaSampler.sample(processedSchema, samplerOptions)
|
|
783
|
+
sampled = postProcessMock(sampled, processedSchema, null, jsfRefs)
|
|
784
|
+
return sampled
|
|
785
|
+
} catch (error) {
|
|
786
|
+
console.error(`Error generating mock operation for ${method} ${name}:`, error)
|
|
787
|
+
return {}
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
// Update other generateMock functions similarly
|
|
792
|
+
const generateMockResponseBody = async (method, name, data, jsfRefs) => {
|
|
793
|
+
if (OpenApiRequestGenerator._testForceError === 'generateMockResponseBody') throw new Error('Forced error for test')
|
|
794
|
+
if (!data || !data.responses) {
|
|
795
|
+
return {}
|
|
796
|
+
}
|
|
185
797
|
|
|
186
|
-
|
|
798
|
+
const responseSchema = findResponseSchema(data.responses)
|
|
799
|
+
if (!responseSchema) {
|
|
800
|
+
return {}
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
try {
|
|
804
|
+
const processedSchema = processRefs(responseSchema, jsfRefs)
|
|
805
|
+
const fakedResponse = {}
|
|
806
|
+
let sampled = JSONSchemaSampler.sample(processedSchema, samplerOptions)
|
|
807
|
+
sampled = postProcessMock(sampled, processedSchema, null, jsfRefs)
|
|
808
|
+
fakedResponse.body = sampled
|
|
809
|
+
|
|
810
|
+
for (const key in data.responses) {
|
|
811
|
+
fakedResponse.status = key
|
|
812
|
+
if (key >= 200 && key <= 299) {
|
|
813
|
+
break
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
return fakedResponse
|
|
817
|
+
} catch (error) {
|
|
818
|
+
console.error(`Error generating mock response for ${method} ${name}:`, error)
|
|
819
|
+
return {}
|
|
820
|
+
}
|
|
187
821
|
}
|
|
188
822
|
|
|
189
823
|
const generateMockHeaders = async (method, name, data, jsfRefs) => {
|
|
190
|
-
|
|
824
|
+
if (OpenApiRequestGenerator._testForceError === 'generateMockHeaders') throw new Error('Forced error for test')
|
|
825
|
+
if (!data || !Array.isArray(data.parameters)) {
|
|
826
|
+
return {}
|
|
827
|
+
}
|
|
828
|
+
const properties = {}
|
|
191
829
|
data.parameters.forEach(param => {
|
|
192
830
|
if (param.in === 'header') {
|
|
193
|
-
|
|
194
|
-
}
|
|
195
|
-
})
|
|
196
|
-
jsfRefs.forEach(ref => {
|
|
197
|
-
if (headers[ref.id]) {
|
|
198
|
-
headers[ref.id] = { $ref: ref.id }
|
|
831
|
+
properties[param.name] = (param.schema && param.schema.type) ? { type: param.schema.type } : {}
|
|
199
832
|
}
|
|
200
833
|
})
|
|
201
834
|
|
|
202
|
-
if (Object.keys(
|
|
835
|
+
if (Object.keys(properties).length === 0) {
|
|
203
836
|
return {}
|
|
204
837
|
}
|
|
205
838
|
|
|
206
|
-
const
|
|
207
|
-
|
|
208
|
-
|
|
839
|
+
const schema = { type: 'object', properties }
|
|
840
|
+
const processedSchema = processRefs(schema, jsfRefs)
|
|
841
|
+
let sampled = JSONSchemaSampler.sample(processedSchema, samplerOptions)
|
|
842
|
+
sampled = postProcessMock(sampled, processedSchema, null, jsfRefs)
|
|
843
|
+
if (sampled === null) sampled = {}
|
|
844
|
+
return sampled
|
|
209
845
|
}
|
|
210
846
|
|
|
211
847
|
const generateMockQueryParams = async (method, name, data, jsfRefs) => {
|
|
212
|
-
|
|
848
|
+
if (OpenApiRequestGenerator._testForceError === 'generateMockQueryParams') throw new Error('Forced error for test')
|
|
849
|
+
if (!data || !Array.isArray(data.parameters)) {
|
|
850
|
+
return {}
|
|
851
|
+
}
|
|
852
|
+
const properties = {}
|
|
213
853
|
data.parameters.forEach(param => {
|
|
214
854
|
if (param.in === 'query') {
|
|
215
|
-
|
|
216
|
-
}
|
|
217
|
-
})
|
|
218
|
-
jsfRefs.forEach(ref => {
|
|
219
|
-
if (queryParams[ref.id]) {
|
|
220
|
-
queryParams[ref.id] = { $ref: ref.id }
|
|
855
|
+
properties[param.name] = (param.schema) ? { ...param.schema } : {}
|
|
221
856
|
}
|
|
222
857
|
})
|
|
223
858
|
|
|
224
|
-
if (Object.keys(
|
|
859
|
+
if (Object.keys(properties).length === 0) {
|
|
225
860
|
return {}
|
|
226
861
|
}
|
|
227
862
|
|
|
228
|
-
const
|
|
229
|
-
|
|
230
|
-
|
|
863
|
+
const schema = { type: 'object', properties }
|
|
864
|
+
const processedSchema = processRefs(schema, jsfRefs)
|
|
865
|
+
let sampled = JSONSchemaSampler.sample(processedSchema, samplerOptions)
|
|
866
|
+
sampled = postProcessMock(sampled, processedSchema, null, jsfRefs)
|
|
867
|
+
if (sampled === null) sampled = {}
|
|
868
|
+
return sampled
|
|
231
869
|
}
|
|
232
870
|
|
|
233
871
|
const generateMockPathParams = async (method, name, data, jsfRefs) => {
|
|
234
|
-
|
|
872
|
+
if (OpenApiRequestGenerator._testForceError === 'generateMockPathParams') throw new Error('Forced error for test')
|
|
873
|
+
if (!data || !Array.isArray(data.parameters)) {
|
|
874
|
+
return {}
|
|
875
|
+
}
|
|
876
|
+
const properties = {}
|
|
235
877
|
data.parameters.forEach(param => {
|
|
236
878
|
if (param.in === 'path') {
|
|
237
|
-
|
|
238
|
-
}
|
|
239
|
-
})
|
|
240
|
-
jsfRefs.forEach(ref => {
|
|
241
|
-
if (pathParams[ref.id]) {
|
|
242
|
-
pathParams[ref.id] = { $ref: ref.id }
|
|
879
|
+
properties[param.name] = (param.schema) ? { ...param.schema } : {}
|
|
243
880
|
}
|
|
244
881
|
})
|
|
245
882
|
|
|
246
|
-
if (Object.keys(
|
|
883
|
+
if (Object.keys(properties).length === 0) {
|
|
247
884
|
return {}
|
|
248
885
|
}
|
|
249
886
|
|
|
250
|
-
const
|
|
251
|
-
|
|
252
|
-
|
|
887
|
+
const schema = { type: 'object', properties }
|
|
888
|
+
const processedSchema = processRefs(schema, jsfRefs)
|
|
889
|
+
let sampled = JSONSchemaSampler.sample(processedSchema, samplerOptions)
|
|
890
|
+
sampled = postProcessMock(sampled, processedSchema, null, jsfRefs)
|
|
891
|
+
if (sampled === null) sampled = {}
|
|
892
|
+
return sampled
|
|
253
893
|
}
|
|
254
894
|
|
|
255
895
|
class OpenApiRequestGenerator {
|
|
@@ -258,44 +898,105 @@ class OpenApiRequestGenerator {
|
|
|
258
898
|
}
|
|
259
899
|
|
|
260
900
|
async load (schemaPath) {
|
|
261
|
-
|
|
901
|
+
try {
|
|
902
|
+
this.schema = await loadYamlFile(schemaPath)
|
|
903
|
+
} catch (error) {
|
|
904
|
+
console.error('Error loading schema:', error)
|
|
905
|
+
this.schema = {}
|
|
906
|
+
}
|
|
262
907
|
}
|
|
263
908
|
|
|
264
909
|
async generateRequestBody (path, httpMethod, jsfRefs = []) {
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
910
|
+
try {
|
|
911
|
+
const pathValue = this.schema.paths?.[path]
|
|
912
|
+
if (!pathValue) {
|
|
913
|
+
return {}
|
|
914
|
+
}
|
|
915
|
+
const operation = pathValue[httpMethod]
|
|
916
|
+
if (!operation) {
|
|
917
|
+
return {}
|
|
918
|
+
}
|
|
919
|
+
const id = operation.operationId || operation.summary
|
|
920
|
+
return generateMockOperation(httpMethod, id, operation, jsfRefs)
|
|
921
|
+
} catch (error) {
|
|
922
|
+
console.error(`Error generating request body for ${path} ${httpMethod}:`, error)
|
|
923
|
+
return {}
|
|
924
|
+
}
|
|
269
925
|
}
|
|
270
926
|
|
|
271
927
|
async generateRequestHeaders (path, httpMethod, jsfRefs = []) {
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
928
|
+
try {
|
|
929
|
+
const pathValue = this.schema.paths?.[path]
|
|
930
|
+
if (!pathValue) {
|
|
931
|
+
return {}
|
|
932
|
+
}
|
|
933
|
+
const operation = pathValue[httpMethod]
|
|
934
|
+
if (!operation) {
|
|
935
|
+
return {}
|
|
936
|
+
}
|
|
937
|
+
const id = operation.operationId || operation.summary
|
|
938
|
+
return generateMockHeaders(httpMethod, id, operation, jsfRefs)
|
|
939
|
+
} catch (error) {
|
|
940
|
+
console.error(`Error generating request headers for ${path} ${httpMethod}:`, error)
|
|
941
|
+
return {}
|
|
942
|
+
}
|
|
276
943
|
}
|
|
277
944
|
|
|
278
945
|
async generateRequestQueryParams (path, httpMethod, jsfRefs = []) {
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
946
|
+
try {
|
|
947
|
+
const pathValue = this.schema.paths?.[path]
|
|
948
|
+
if (!pathValue) {
|
|
949
|
+
return {}
|
|
950
|
+
}
|
|
951
|
+
const operation = pathValue[httpMethod]
|
|
952
|
+
if (!operation) {
|
|
953
|
+
return {}
|
|
954
|
+
}
|
|
955
|
+
const id = operation.operationId || operation.summary
|
|
956
|
+
return generateMockQueryParams(httpMethod, id, operation, jsfRefs)
|
|
957
|
+
} catch (error) {
|
|
958
|
+
console.error(`Error generating query params for ${path} ${httpMethod}:`, error)
|
|
959
|
+
return {}
|
|
960
|
+
}
|
|
283
961
|
}
|
|
284
962
|
|
|
285
963
|
async generateRequestPathParams (path, httpMethod, jsfRefs = []) {
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
964
|
+
try {
|
|
965
|
+
const pathValue = this.schema.paths?.[path]
|
|
966
|
+
if (!pathValue) {
|
|
967
|
+
return {}
|
|
968
|
+
}
|
|
969
|
+
const operation = pathValue[httpMethod]
|
|
970
|
+
if (!operation) {
|
|
971
|
+
return {}
|
|
972
|
+
}
|
|
973
|
+
const id = operation.operationId || operation.summary
|
|
974
|
+
return generateMockPathParams(httpMethod, id, operation, jsfRefs)
|
|
975
|
+
} catch (error) {
|
|
976
|
+
console.error(`Error generating path params for ${path} ${httpMethod}:`, error)
|
|
977
|
+
return {}
|
|
978
|
+
}
|
|
290
979
|
}
|
|
291
980
|
|
|
292
981
|
async generateResponseBody (path, httpMethod, jsfRefs = []) {
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
982
|
+
try {
|
|
983
|
+
const pathValue = this.schema.paths?.[path]
|
|
984
|
+
if (!pathValue) {
|
|
985
|
+
return {}
|
|
986
|
+
}
|
|
987
|
+
const operation = pathValue[httpMethod]
|
|
988
|
+
if (!operation) {
|
|
989
|
+
return {}
|
|
990
|
+
}
|
|
991
|
+
const id = operation.operationId || operation.summary
|
|
992
|
+
return generateMockResponseBody(httpMethod, id, operation, jsfRefs)
|
|
993
|
+
} catch (error) {
|
|
994
|
+
console.error(`Error generating response body for ${path} ${httpMethod}:`, error)
|
|
995
|
+
return {}
|
|
996
|
+
}
|
|
298
997
|
}
|
|
299
998
|
}
|
|
300
999
|
|
|
1000
|
+
OpenApiRequestGenerator._testForceError = null
|
|
1001
|
+
|
|
301
1002
|
module.exports = OpenApiRequestGenerator
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
/*****
|
|
2
2
|
License
|
|
3
3
|
--------------
|
|
4
|
-
Copyright ©
|
|
5
|
-
The Mojaloop files are made available by the
|
|
4
|
+
Copyright © 2020-2025 Mojaloop Foundation
|
|
5
|
+
The Mojaloop files are made available by the Mojaloop Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at
|
|
6
|
+
|
|
6
7
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
|
|
7
9
|
Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
|
|
10
|
+
|
|
8
11
|
Contributors
|
|
9
12
|
--------------
|
|
10
13
|
This is the official list of the Mojaloop project contributors for this file.
|
|
@@ -12,10 +15,12 @@
|
|
|
12
15
|
should be listed with a '*' in the first column. People who have
|
|
13
16
|
contributed from an organization can be listed under the organization
|
|
14
17
|
that actually holds the copyright for their contributions (see the
|
|
15
|
-
|
|
18
|
+
Mojaloop Foundation for an example). Those individuals should have
|
|
16
19
|
their names indented and be marked with a '-'. Email address can be added
|
|
17
20
|
optionally within square brackets <email>.
|
|
18
|
-
|
|
21
|
+
|
|
22
|
+
* Mojaloop Foundation
|
|
23
|
+
- Name Surname <name.surname@mojaloop.io>
|
|
19
24
|
|
|
20
25
|
* ModusBox
|
|
21
26
|
* Vijaya Kumar Guthi <vijaya.guthi@modusbox.com> (Original Author)
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
/*****
|
|
2
2
|
License
|
|
3
3
|
--------------
|
|
4
|
-
Copyright ©
|
|
5
|
-
The Mojaloop files are made available by the
|
|
4
|
+
Copyright © 2020-2025 Mojaloop Foundation
|
|
5
|
+
The Mojaloop files are made available by the Mojaloop Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at
|
|
6
|
+
|
|
6
7
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
|
|
7
9
|
Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
|
|
10
|
+
|
|
8
11
|
Contributors
|
|
9
12
|
--------------
|
|
10
13
|
This is the official list of the Mojaloop project contributors for this file.
|
|
@@ -12,10 +15,12 @@
|
|
|
12
15
|
should be listed with a '*' in the first column. People who have
|
|
13
16
|
contributed from an organization can be listed under the organization
|
|
14
17
|
that actually holds the copyright for their contributions (see the
|
|
15
|
-
|
|
18
|
+
Mojaloop Foundation for an example). Those individuals should have
|
|
16
19
|
their names indented and be marked with a '-'. Email address can be added
|
|
17
20
|
optionally within square brackets <email>.
|
|
18
|
-
|
|
21
|
+
|
|
22
|
+
* Mojaloop Foundation
|
|
23
|
+
- Name Surname <name.surname@mojaloop.io>
|
|
19
24
|
|
|
20
25
|
* ModusBox
|
|
21
26
|
* Vijaya Kumar Guthi <vijaya.guthi@modusbox.com> (Original Author)
|