mockaton 11.2.6 → 11.3.0

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/index.js CHANGED
@@ -3,6 +3,6 @@ export { Commander } from './src/client/ApiCommander.js'
3
3
  export { Mockaton } from './src/server/Mockaton.js'
4
4
  export { jwtCookie } from './src/server/utils/jwt.js'
5
5
  export { jsToJsonPlugin } from './src/server/MockDispatcher.js'
6
- export { parseJSON, BodyReaderError } from './src/server/utils/http-request.js'
6
+ export { parseJSON, BodyReaderError } from './src/server/utils/HttpIncomingMessage.js'
7
7
 
8
8
  export const defineConfig = opts => opts
package/package.json CHANGED
@@ -2,14 +2,20 @@
2
2
  "name": "mockaton",
3
3
  "description": "HTTP Mock Server",
4
4
  "type": "module",
5
- "version": "11.2.6",
5
+ "version": "11.3.0",
6
6
  "exports": {
7
7
  ".": {
8
8
  "import": "./index.js",
9
9
  "types": "./index.d.ts"
10
10
  }
11
11
  },
12
+ "files": [
13
+ "src",
14
+ "index.js",
15
+ "index.d.ts"
16
+ ],
12
17
  "license": "MIT",
18
+ "homepage": "https://mockaton.com",
13
19
  "repository": "https://github.com/ericfortis/mockaton",
14
20
  "keywords": [
15
21
  "mock-server",
@@ -0,0 +1,81 @@
1
+ import { test } from 'node:test'
2
+ import { deepEqual } from 'node:assert'
3
+
4
+ import { dittoSplitPaths, BrokerRowModel, t } from './app-store.js'
5
+
6
+
7
+ test('dittoSplitPaths', () => {
8
+ const input = [
9
+ '/api/user',
10
+ '/api/user/avatar',
11
+ '/api/user/friends',
12
+ '/api/vid',
13
+ '/api/video/id',
14
+ '/api/video/stats',
15
+ '/v2/foo',
16
+ '/v2/foo/bar'
17
+ ]
18
+ deepEqual(dittoSplitPaths(input), [
19
+ ['', '/api/user'],
20
+ ['/api/user/', 'avatar'],
21
+ ['/api/user/', 'friends'],
22
+ ['/api/', 'vid'],
23
+ ['/api/', 'video/id'],
24
+ ['/api/video/', 'stats'],
25
+ ['', '/v2/foo'],
26
+ ['/v2/foo/', 'bar']
27
+ ])
28
+ })
29
+
30
+
31
+ test('BrokerRowModel', () => {
32
+ test('has Auto500 when is autogenerated 500', () => {
33
+ const broker = {
34
+ auto500: true,
35
+ file: 'api/user.GET.200.json',
36
+ mocks: ['api/user.GET.200.json']
37
+ }
38
+ const row = new BrokerRowModel(broker, false)
39
+ const opts = row.opts.map(([, n, selected]) => [n, selected])
40
+ deepEqual(opts, [
41
+ ['200 json', false],
42
+ [t`Auto500`, true],
43
+ ])
44
+ })
45
+
46
+ test('filename has extension except when empty or unknown', () => {
47
+ const broker = {
48
+ file: `api/user0.GET.200.empty`,
49
+ mocks: [
50
+ `api/user0.GET.200.empty`,
51
+ `api/user1.GET.200.unknown`,
52
+ `api/user2.GET.200.json`,
53
+ `api/user3(another json).GET.200.json`,
54
+ ]
55
+ }
56
+ const row = new BrokerRowModel(broker, false)
57
+ const opts = row.opts.map(([, n, selected]) => [n, selected])
58
+ deepEqual(opts, [
59
+ ['200', true],
60
+ ['200', false],
61
+ ['200 json', false],
62
+ ['200 json (another json)', false]
63
+ ])
64
+ // TODO Think about, in cases like this, the only option the user has
65
+ // for discerning empty and unknown is on the Previewer Title
66
+ })
67
+
68
+ test('appends "Proxied" label iff current is proxied', () => {
69
+ const broker = {
70
+ file: 'api/foo',
71
+ proxied: true,
72
+ mocks: [`api/foo.GET.200.json`]
73
+ }
74
+ const row = new BrokerRowModel(broker, true)
75
+ deepEqual(row.opts.map(([, n, selected]) => [n, selected]), [
76
+ ['200 json', false],
77
+ [t`Proxied`, true]
78
+ ])
79
+ })
80
+ })
81
+
package/src/client/app.js CHANGED
@@ -19,6 +19,7 @@ const CSS = {
19
19
  GlobalDelayField: null,
20
20
  GroupByMethod: null,
21
21
  InternalServerErrorToggler: null,
22
+ Logo: null,
22
23
  MenuTrigger: null,
23
24
  Method: null,
24
25
  MockList: null,
@@ -107,12 +108,16 @@ function App() {
107
108
  function Header() {
108
109
  return (
109
110
  r('header', null,
110
- r('object', {
111
- data: 'logo.svg',
112
- type: 'image/svg+xml',
113
- width: 120,
114
- height: 22
115
- }),
111
+ r('a', {
112
+ className: CSS.Logo,
113
+ href: 'https://mockaton.com',
114
+ },
115
+ r('object', {
116
+ data: 'logo.svg',
117
+ type: 'image/svg+xml',
118
+ width: 120,
119
+ height: 22
120
+ })),
116
121
  r('div', null,
117
122
  GlobalDelayField(),
118
123
  BulkSelector(),
@@ -271,11 +276,17 @@ function SettingsMenu(id) {
271
276
  }),
272
277
  r('span', className(CSS.checkboxBody), t`Group by Method`)),
273
278
 
279
+ r('a', {
280
+ href: 'https://mockaton.com',
281
+ target: '_blank',
282
+ rel: 'noopener noreferrer'
283
+ }, t`Website`),
284
+
274
285
  r('a', {
275
286
  href: 'https://github.com/ericfortis/mockaton',
276
287
  target: '_blank',
277
288
  rel: 'noopener noreferrer'
278
- }, t`Documentation`),
289
+ }, t`Source Code`),
279
290
 
280
291
  r('p', null, `v${store.mockatonVersion}`)
281
292
  )))
@@ -914,3 +925,6 @@ function SyntaxXML(xml) {
914
925
  return frag
915
926
  }
916
927
 
928
+
929
+ /*
930
+ */
@@ -133,10 +133,14 @@ header {
133
133
  border-bottom: 1px solid var(--colorSecondaryActionBorder);
134
134
  background: var(--colorHeaderBackground);
135
135
 
136
- > object {
136
+ .Logo {
137
137
  align-self: end;
138
138
  margin-right: 22px;
139
- margin-bottom: 5px;
139
+ margin-bottom: 3px;
140
+
141
+ object {
142
+ pointer-events: none;
143
+ }
140
144
  }
141
145
 
142
146
  > div {
package/src/server/Api.js CHANGED
@@ -21,9 +21,6 @@ import { config, ConfigValidator } from './config.js'
21
21
  import * as staticCollection from './staticCollection.js'
22
22
  import * as mockBrokersCollection from './mockBrokersCollection.js'
23
23
 
24
- import { parseJSON } from './utils/http-request.js'
25
- import { sendOK, sendJSON, sendUnprocessable, sendFile, sendHTML } from './utils/http-response.js'
26
-
27
24
 
28
25
  export const apiGetReqs = new Map([
29
26
  [API.dashboard, serveDashboard],
@@ -61,17 +58,17 @@ export const apiPatchReqs = new Map([
61
58
  /** # GET */
62
59
 
63
60
  function serveDashboard(_, response) {
64
- sendHTML(response, IndexHtml(config.hotReload), CSP)
61
+ response.html(IndexHtml(config.hotReload), CSP)
65
62
  }
66
63
 
67
64
  function serveStatic(f) {
68
65
  return (_, response) => {
69
- sendFile(response, join(CLIENT_DIR, f))
66
+ response.file(join(CLIENT_DIR, f))
70
67
  }
71
68
  }
72
69
 
73
70
  function getState(_, response) {
74
- sendJSON(response, {
71
+ response.json({
75
72
  cookies: cookie.list(),
76
73
  comments: mockBrokersCollection.extractAllComments(),
77
74
 
@@ -93,161 +90,161 @@ function getState(_, response) {
93
90
  function reinitialize(_, response) {
94
91
  mockBrokersCollection.init()
95
92
  staticCollection.init()
96
- sendOK(response)
93
+ response.ok()
97
94
  }
98
95
 
99
96
 
100
97
  async function setCorsAllowed(req, response) {
101
- const corsAllowed = await parseJSON(req)
98
+ const corsAllowed = await req.json()
102
99
 
103
100
  if (!ConfigValidator.corsAllowed(corsAllowed))
104
- sendUnprocessable(response, `Expected boolean for "corsAllowed"`)
101
+ response.unprocessable(`Expected boolean for "corsAllowed"`)
105
102
  else {
106
103
  config.corsAllowed = corsAllowed
107
- sendOK(response)
104
+ response.ok()
108
105
  }
109
106
  }
110
107
 
111
108
 
112
109
  async function setGlobalDelay(req, response) {
113
- const delay = await parseJSON(req)
110
+ const delay = await req.json()
114
111
 
115
112
  if (!ConfigValidator.delay(delay))
116
- sendUnprocessable(response, `Expected non-negative integer for "delay"`)
113
+ response.unprocessable(`Expected non-negative integer for "delay"`)
117
114
  else {
118
115
  config.delay = delay
119
- sendOK(response)
116
+ response.ok()
120
117
  }
121
118
  }
122
119
 
123
120
 
124
121
  async function selectCookie(req, response) {
125
- const cookieKey = await parseJSON(req)
122
+ const cookieKey = await req.json()
126
123
 
127
124
  const error = cookie.setCurrent(cookieKey)
128
125
  if (error)
129
- sendUnprocessable(response, error?.message || error)
126
+ response.unprocessable(error?.message || error)
130
127
  else
131
- sendJSON(response, cookie.list())
128
+ response.json(cookie.list())
132
129
  }
133
130
 
134
131
 
135
132
  async function setProxyFallback(req, response) {
136
- const fallback = await parseJSON(req)
133
+ const fallback = await req.json()
137
134
 
138
135
  if (!ConfigValidator.proxyFallback(fallback))
139
- sendUnprocessable(response, `Invalid Proxy Fallback URL`)
136
+ response.unprocessable(`Invalid Proxy Fallback URL`)
140
137
  else {
141
138
  config.proxyFallback = fallback
142
- sendOK(response)
139
+ response.ok()
143
140
  }
144
141
  }
145
142
 
146
143
  async function setCollectProxied(req, response) {
147
- const collectProxied = await parseJSON(req)
144
+ const collectProxied = await req.json()
148
145
 
149
146
  if (!ConfigValidator.collectProxied(collectProxied))
150
- sendUnprocessable(response, `Expected a boolean for "collectProxied"`)
147
+ response.unprocessable(`Expected a boolean for "collectProxied"`)
151
148
  else {
152
149
  config.collectProxied = collectProxied
153
- sendOK(response)
150
+ response.ok()
154
151
  }
155
152
  }
156
153
 
157
154
 
158
155
 
159
156
  async function bulkUpdateBrokersByCommentTag(req, response) {
160
- const comment = await parseJSON(req)
157
+ const comment = await req.json()
161
158
 
162
159
  mockBrokersCollection.setMocksMatchingComment(comment)
163
- sendOK(response)
160
+ response.ok()
164
161
  }
165
162
 
166
163
 
167
164
  async function selectMock(req, response) {
168
- const file = await parseJSON(req)
165
+ const file = await req.json()
169
166
 
170
167
  const broker = mockBrokersCollection.brokerByFilename(file)
171
168
  if (!broker || !broker.hasMock(file))
172
- sendUnprocessable(response, `Missing Mock: ${file}`)
169
+ response.unprocessable(`Missing Mock: ${file}`)
173
170
  else {
174
171
  broker.selectFile(file)
175
- sendJSON(response, broker)
172
+ response.json(broker)
176
173
  }
177
174
  }
178
175
 
179
176
 
180
177
  async function toggleRoute500(req, response) {
181
- const [method, urlMask] = await parseJSON(req)
178
+ const [method, urlMask] = await req.json()
182
179
 
183
180
  const broker = mockBrokersCollection.brokerByRoute(method, urlMask)
184
181
  if (!broker)
185
- sendUnprocessable(response, `Route does not exist: ${method} ${urlMask}`)
182
+ response.unprocessable(`Route does not exist: ${method} ${urlMask}`)
186
183
  else {
187
184
  broker.toggle500()
188
- sendJSON(response, broker)
185
+ response.json(broker)
189
186
  }
190
187
  }
191
188
 
192
189
 
193
190
  async function setRouteIsDelayed(req, response) {
194
- const [method, urlMask, delayed] = await parseJSON(req)
191
+ const [method, urlMask, delayed] = await req.json()
195
192
 
196
193
  const broker = mockBrokersCollection.brokerByRoute(method, urlMask)
197
194
  if (!broker)
198
- sendUnprocessable(response, `Route does not exist: ${method} ${urlMask}`)
195
+ response.unprocessable(`Route does not exist: ${method} ${urlMask}`)
199
196
  else if (typeof delayed !== 'boolean')
200
- sendUnprocessable(response, `Expected boolean for "delayed"`)
197
+ response.unprocessable(`Expected boolean for "delayed"`)
201
198
  else {
202
199
  broker.setDelayed(delayed)
203
- sendJSON(response, broker)
200
+ response.json(broker)
204
201
  }
205
202
  }
206
203
 
207
204
 
208
205
  async function setRouteIsProxied(req, response) {
209
- const [method, urlMask, proxied] = await parseJSON(req)
206
+ const [method, urlMask, proxied] = await req.json()
210
207
 
211
208
  const broker = mockBrokersCollection.brokerByRoute(method, urlMask)
212
209
  if (!broker)
213
- sendUnprocessable(response, `Route does not exist: ${method} ${urlMask}`)
210
+ response.unprocessable( `Route does not exist: ${method} ${urlMask}`)
214
211
  else if (typeof proxied !== 'boolean')
215
- sendUnprocessable(response, `Expected boolean for "proxied"`)
212
+ response.unprocessable(`Expected boolean for "proxied"`)
216
213
  else if (proxied && !config.proxyFallback)
217
- sendUnprocessable(response, `There’s no proxy fallback`)
214
+ response.unprocessable(`There’s no proxy fallback`)
218
215
  else {
219
216
  broker.setProxied(proxied)
220
- sendJSON(response, broker)
217
+ response.json(broker)
221
218
  }
222
219
  }
223
220
 
224
221
 
225
222
 
226
223
  async function setStaticRouteStatusCode(req, response) {
227
- const [route, status] = await parseJSON(req)
224
+ const [route, status] = await req.json()
228
225
 
229
226
  const broker = staticCollection.brokerByRoute(route)
230
227
  if (!broker)
231
- sendUnprocessable(response, `Static route does not exist: ${route}`)
228
+ response.unprocessable(`Static route does not exist: ${route}`)
232
229
  else if (!(status === 200 || status === 404))
233
- sendUnprocessable(response, `Expected 200 or 404 status code`)
230
+ response.unprocessable(`Expected 200 or 404 status code`)
234
231
  else {
235
232
  broker.setStatus(status)
236
- sendOK(response)
233
+ response.ok()
237
234
  }
238
235
  }
239
236
 
240
237
 
241
238
  async function setStaticRouteIsDelayed(req, response) {
242
- const [route, delayed] = await parseJSON(req)
239
+ const [route, delayed] = await req.json()
243
240
 
244
241
  const broker = staticCollection.brokerByRoute(route)
245
242
  if (!broker)
246
- sendUnprocessable(response, `Static route does not exist: ${route}`)
243
+ response.unprocessable(`Static route does not exist: ${route}`)
247
244
  else if (typeof delayed !== 'boolean')
248
- sendUnprocessable(response, `Expected boolean for "delayed"`)
245
+ response.unprocessable(`Expected boolean for "delayed"`)
249
246
  else {
250
247
  broker.setDelayed(delayed)
251
- sendOK(response)
248
+ response.ok()
252
249
  }
253
250
  }
@@ -4,7 +4,6 @@ import { pathToFileURL } from 'node:url'
4
4
 
5
5
  import { logger } from './utils/logger.js'
6
6
  import { mimeFor } from './utils/mime.js'
7
- import { sendInternalServerError, sendMockNotFound } from './utils/http-response.js'
8
7
 
9
8
  import { proxy } from './ProxyRelay.js'
10
9
  import { cookie } from './cookie.js'
@@ -25,7 +24,7 @@ export async function dispatchMock(req, response) {
25
24
  return
26
25
  }
27
26
  if (!broker) {
28
- sendMockNotFound(response)
27
+ response.mockNotFound()
29
28
  return
30
29
  }
31
30
 
@@ -52,9 +51,9 @@ export async function dispatchMock(req, response) {
52
51
  }
53
52
  catch (error) { // TESTME
54
53
  if (error?.code === 'ENOENT') // mock-file has been deleted
55
- sendMockNotFound(response)
54
+ response.mockNotFound()
56
55
  else
57
- sendInternalServerError(response, error)
56
+ response.internalServerError(error)
58
57
  }
59
58
  }
60
59
 
@@ -3,12 +3,10 @@ import { createServer } from 'node:http'
3
3
  import pkgJSON from '../../package.json' with { type: 'json' }
4
4
 
5
5
  import { logger } from './utils/logger.js'
6
+ import { ServerResponse } from './utils/HttpServerResponse.js'
7
+ import { IncomingMessage } from './utils/HttpIncomingMessage.js'
6
8
  import { setCorsHeaders, isPreflight } from './utils/http-cors.js'
7
- import { BodyReaderError, hasControlChars } from './utils/http-request.js'
8
- import {
9
- setHeaders, sendNoContent, sendInternalServerError,
10
- sendUnprocessable, sendTooLongURI, sendBadRequest
11
- } from './utils/http-response.js'
9
+ import { BodyReaderError, hasControlChars } from './utils/HttpIncomingMessage.js'
12
10
 
13
11
  import { API } from './ApiConstants.js'
14
12
  import { config, setup } from './config.js'
@@ -38,7 +36,7 @@ export function Mockaton(options) {
38
36
  if (config.hotReload)
39
37
  watchDevSPA()
40
38
 
41
- const server = createServer(onRequest)
39
+ const server = createServer({ IncomingMessage, ServerResponse }, onRequest)
42
40
  server.on('error', reject)
43
41
  server.listen(config.port, config.host, () => {
44
42
  const url = `http://${server.address().address}:${server.address().port}`
@@ -54,17 +52,17 @@ export function Mockaton(options) {
54
52
  async function onRequest(req, response) {
55
53
  response.on('error', logger.warn)
56
54
 
57
- setHeaders(response, ['Server', `Mockaton ${pkgJSON.version}`])
58
- setHeaders(response, config.extraHeaders)
55
+ response.setHeader('Server', `Mockaton ${pkgJSON.version}`)
56
+ response.setHeaderList(config.extraHeaders)
59
57
 
60
58
  const url = req.url || ''
61
59
 
62
60
  if (url.length > 2048) {
63
- sendTooLongURI(response)
61
+ response.uriTooLong()
64
62
  return
65
63
  }
66
64
  if (hasControlChars(url)) {
67
- sendBadRequest(response)
65
+ response.badRequest()
68
66
  return
69
67
  }
70
68
 
@@ -76,7 +74,7 @@ async function onRequest(req, response) {
76
74
  const { pathname } = new URL(url, 'http://_')
77
75
 
78
76
  if (isPreflight(req))
79
- sendNoContent(response)
77
+ response.noContent()
80
78
 
81
79
  else if (method === 'PATCH' && apiPatchReqs.has(pathname))
82
80
  await apiPatchReqs.get(pathname)(req, response)
@@ -92,8 +90,8 @@ async function onRequest(req, response) {
92
90
  }
93
91
  catch (error) {
94
92
  if (error instanceof BodyReaderError)
95
- sendUnprocessable(response, `${error.name}: ${error.message}`)
93
+ response.unprocessable(`${error.name}: ${error.message}`)
96
94
  else
97
- sendInternalServerError(response, error)
95
+ response.internalServerError(error)
98
96
  }
99
97
  }