mockaton 8.2.23 → 8.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/README.md CHANGED
@@ -13,13 +13,10 @@ URL paths. For example, the following file will be served on `/api/user/1234`
13
13
  my-mocks-dir/api/user/[user-id].GET.200.json
14
14
  ```
15
15
 
16
- By the way, [this browser
17
- extension](https://github.com/ericfortis/devtools-ext-tar-http-requests)
18
- can create a TAR of your requests following that convention.
19
-
20
- Nonetheless, you don’t need to mock all your APIs. Mockaton
21
- can request from your backend the routes you don’t have mocks for.
22
- That’s done with `config.proxyFallback = 'http://mybackend'`
16
+ By the way, you don’t need to mock all your APIs. Mockaton can request
17
+ from your backend the routes you don’t have mocks for. That’s done
18
+ with `config.proxyFallback = 'http://mybackend'`. For convenience, you
19
+ can save mocks for those responses with `config.collectProxied = true`
23
20
 
24
21
  ## Multiple Mock Variants
25
22
  Each route can have many mocks, which could either be:
@@ -280,6 +277,31 @@ the amount is globally configurable. It defaults to `config.delay=1200` millisec
280
277
  Lets you specify a target server for serving routes you don’t have mocks for.
281
278
  For example, `config.proxyFallback = 'http://example.com'`
282
279
 
280
+ ### `collectProxied?: boolean`
281
+ Defaults to `false`. With this flag you can save mocks that hit
282
+ your proxy fallback to `config.mocksDir`. If there are UUIDv4 in the
283
+ URL the filename will have `[id]` in their place. For example,
284
+
285
+ ```
286
+ /api/user/d14e09c8-d970-4b07-be42-b2f4ee22f0a6/likes =>
287
+ my-mocks-dir/api/user/[id]/likes.GET.200.json
288
+ ```
289
+
290
+ Note that newly saved mocks won’t be served until you
291
+ **register them** by reinitializing Mockaton or clicking "Reset".
292
+
293
+ Registered mocks won’t be overwritten (they don’t hit the fallback server).
294
+ On the other hand, newly saved mocks get overwritten while they are unregistered.
295
+
296
+ <details>
297
+ <summary>Extension Details</summary>
298
+ <p>
299
+ If you see an <code>.unknown</code> extension, that’s because the
300
+ <code>Content-Type</code> sent by your backend is either missing or not present
301
+ in the predefined list. For the latter, add it to <code>config.extraMimes</code>
302
+ </p>
303
+ </details>
304
+
283
305
 
284
306
  ### `staticDir?: string`
285
307
  - Use Case 1: If you have a bunch of static assets you don’t want to add `.GET.200.ext`
package/index.d.ts CHANGED
@@ -18,6 +18,7 @@ interface Config {
18
18
  host?: string,
19
19
  port?: number
20
20
  proxyFallback?: string
21
+ collectProxied?: boolean
21
22
 
22
23
  delay?: number
23
24
  cookies?: { [label: string]: string }
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "mockaton",
3
3
  "description": "A deterministic server-side for developing and testing frontend clients",
4
4
  "type": "module",
5
- "version": "8.2.23",
5
+ "version": "8.3.0",
6
6
  "main": "index.js",
7
7
  "types": "index.d.ts",
8
8
  "license": "MIT",
@@ -21,3 +21,5 @@ export const DF = { // Dashboard Fields (XHR)
21
21
 
22
22
  export const DEFAULT_500_COMMENT = '(Mockaton 500)'
23
23
  export const DEFAULT_MOCK_COMMENT = '(default)'
24
+
25
+ export const EXT_FOR_UNKNOWN_MIME = 'unknown'
package/src/Config.js CHANGED
@@ -14,6 +14,7 @@ export const Config = Object.seal({
14
14
  host: '127.0.0.1',
15
15
  port: 0, // auto-assigned
16
16
  proxyFallback: '', // e.g. http://localhost:9999
17
+ collectProxied: false,
17
18
 
18
19
  delay: 1200, // milliseconds
19
20
  cookies: {}, // defaults to the first kv
@@ -47,6 +48,7 @@ export function setup(options) {
47
48
  host: is(String),
48
49
  port: port => Number.isInteger(port) && port >= 0 && port < 2 ** 16,
49
50
  proxyFallback: optional(URL.canParse),
51
+ collectProxied: is(Boolean),
50
52
 
51
53
  delay: ms => Number.isInteger(ms) && ms > 0,
52
54
  cookies: is(Object),
package/src/Filename.js CHANGED
@@ -44,7 +44,6 @@ export function parseFilename(file) {
44
44
  }
45
45
  }
46
46
 
47
-
48
47
  function removeTrailingSlash(url = '') {
49
48
  return url
50
49
  .replace(/\/$/, '')
@@ -59,5 +58,14 @@ function responseStatusIsValid(status) {
59
58
  }
60
59
 
61
60
 
61
+ export function makeMockFilename(url, method, status, ext) {
62
+ const urlMask = replaceIds(removeTrailingSlash(url))
63
+ return [urlMask, method, status, ext].join('.')
64
+ }
65
+
66
+ const reUuidV4 = /([0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12})/gi
67
+ function replaceIds(filename) {
68
+ return filename.replaceAll(reUuidV4, '[id]')
69
+ }
62
70
 
63
71
 
package/src/ProxyRelay.js CHANGED
@@ -1,4 +1,8 @@
1
+ import { join } from 'node:path'
2
+ import { write } from './utils/fs.js'
1
3
  import { Config } from './Config.js'
4
+ import { extFor } from './utils/mime.js'
5
+ import { makeMockFilename } from './Filename.js'
2
6
 
3
7
 
4
8
  export async function proxy(req, response) {
@@ -6,6 +10,14 @@ export async function proxy(req, response) {
6
10
  method: req.method,
7
11
  headers: req.headers
8
12
  })
13
+ // TODO investigate how to include many repeated headers such as set-cookie
9
14
  response.writeHead(proxyResponse.status, Object.fromEntries(proxyResponse.headers))
10
- response.end(await proxyResponse.text())
15
+ const body = await proxyResponse.text()
16
+ response.end(body)
17
+
18
+ if (Config.collectProxied) { // TESTME
19
+ const ext = extFor(proxyResponse.headers.get('content-type'))
20
+ const filename = makeMockFilename(req.url, req.method, proxyResponse.status, ext)
21
+ write(join(Config.mocksDir, filename), body)
22
+ }
11
23
  }
package/src/utils/fs.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import path, { join } from 'node:path'
2
- import { lstatSync, readFileSync, readdirSync } from 'node:fs'
2
+ import { lstatSync, readFileSync, readdirSync, writeFileSync, mkdirSync } from 'node:fs'
3
3
 
4
4
 
5
5
  export const isFile = path => lstatSync(path, { throwIfNoEntry: false })?.isFile()
@@ -14,3 +14,8 @@ export const listFilesRecursively = dir => {
14
14
  ? files.map(f => f.replaceAll(path.sep, path.posix.sep)) // TESTME
15
15
  : files
16
16
  }
17
+
18
+ export const write = (fPath, body) => {
19
+ mkdirSync(path.dirname(fPath), { recursive: true })
20
+ writeFileSync(fPath, body)
21
+ }
package/src/utils/mime.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { Config } from '../Config.js'
2
+ import { EXT_FOR_UNKNOWN_MIME } from '../ApiConstants.js'
2
3
 
3
4
  // https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types
4
5
  // m = {};
@@ -93,3 +94,22 @@ export function mimeFor(filename) {
93
94
  console.info(`Missing MIME for ${filename}`)
94
95
  return mime
95
96
  }
97
+
98
+ export function extFor(mime) {
99
+ const ext = findExt(mime)
100
+ if (!ext) {
101
+ console.info(`Missing extension for ${mime}`)
102
+ return EXT_FOR_UNKNOWN_MIME
103
+ }
104
+ return ext
105
+ }
106
+
107
+ function findExt(targetMime) {
108
+ for (const [ext, mime] of Object.entries(Config.extraMimes))
109
+ if (targetMime === mime)
110
+ return ext
111
+ for (const [ext, mime] of Object.entries(mimes))
112
+ if (targetMime === mime)
113
+ return ext
114
+ return ''
115
+ }