mockaton 5.0.3 → 6.1.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
@@ -48,10 +48,9 @@ interface Config {
48
48
  host?: string, // defaults to 'localhost'
49
49
  port?: number // defaults to 0, which means auto-assigned
50
50
  delay?: number // defaults to 1200 (ms)
51
- onReady?: (dashboardUrl: string) => void // defaults to openInBrowser. pass a noop to prevent opening the dashboard
51
+ onReady?: (dashboardUrl: string) => void // defaults to trying to open macOS default browser. pass a noop to prevent opening the dashboard
52
52
  cookies?: object
53
53
  proxyFallback?: string // e.g. http://localhost:9999 Target for relaying routes without mocks
54
- allowedExt?: RegExp // /\.(md|json|txt|js)$/ Just for excluding temporary editor files (e.g. JetBrains appends a ~)
55
54
  extraHeaders?: []
56
55
  }
57
56
  ```
@@ -118,8 +117,6 @@ The delay is globally configurable via `Config.delay = 1200` (milliseconds).
118
117
 
119
118
  The **file extension** can be anything, but `.md` is reserved for documentation.
120
119
 
121
- The `Config.allowedExt` regex defaults to: `/\.(md|json|txt|js)$/`
122
-
123
120
 
124
121
  ### Dynamic Parameters
125
122
  Anything within square brackets. For example:
@@ -149,18 +146,20 @@ permitted](https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file)
149
146
  but since that’s part of the query string, it’s ignored anyway.
150
147
 
151
148
 
152
-
153
149
  ### Default (index-like) route
154
- For the default route of a directory, omit the mock filename name
155
- (<b>just use the extension</b>). For example, the following files will be
156
- routed to `api/foo` because comments and the query string are ignored.
150
+ **Option A.** Place it outside the directory:
151
+ ```
152
+ api/foo/
153
+ api/foo.GET.200.json
154
+ ```
155
+
156
+ **Option B.** Omit the filename:
157
157
  ```text
158
158
  api/foo/.GET.200.json
159
- api/foo/?bar=[bar].GET.200.json
160
- api/foo/(my comment).GET.200.json
161
159
  ```
162
160
 
163
161
 
162
+
164
163
  ## `Config.cookies`
165
164
  The selected cookie is sent in every response in the `Set-Cookie` header.
166
165
 
@@ -197,15 +196,6 @@ Config.extraHeaders = [
197
196
  ]
198
197
  ```
199
198
 
200
- ## Documenting Contracts (.md)
201
- This is handy for documenting request payload parameters. The
202
- dashboard prints the Markdown document as plain text (I know, I know).
203
-
204
- ```text
205
- api/foo/[user-id].POST.md
206
- api/foo/[user-id].POST.201.json
207
- ```
208
-
209
199
  ---
210
200
 
211
201
  ## API
package/Tests.js CHANGED
@@ -354,7 +354,6 @@ async function testInvalidFilenamesAreIgnored() {
354
354
  const consoleErrorSpy = t.mock.method(console, 'error')
355
355
  consoleErrorSpy.mock.mockImplementation(() => {}) // so they don’t render in the test report
356
356
 
357
- // An extension is needed for testing because of `Config.allowedExt`
358
357
  write('api/_INVALID_FILENAME_CONVENTION_.json', '')
359
358
  write('api/bad-filename.GET._INVALID_STATUS_.json', '')
360
359
  write('api/bad-filename._INVALID_METHOD_.200.json', '')
package/index.d.ts CHANGED
@@ -9,7 +9,6 @@ interface Config {
9
9
  onReady?: (address: string) => void
10
10
  cookies?: object
11
11
  proxyFallback?: string
12
- allowedExt?: RegExp
13
12
  extraHeaders?: [string, string][]
14
13
  }
15
14
 
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": "5.0.3",
5
+ "version": "6.1.0",
6
6
  "main": "index.js",
7
7
  "types": "index.d.ts",
8
8
  "license": "MIT",
@@ -0,0 +1 @@
1
+ "This mock is for /api/user"
package/src/Config.js CHANGED
@@ -12,7 +12,6 @@ export const Config = {
12
12
  cookies: {}, // defaults to the first kv
13
13
  onReady: openInBrowser,
14
14
  proxyFallback: '', // e.g. http://localhost:9999
15
- allowedExt: /\.(md|json|txt|js)$/, // Just for excluding temporary editor files (e.g. JetBrains appends a ~)
16
15
  extraHeaders: []
17
16
  }
18
17
 
@@ -27,7 +26,6 @@ export function setup(options) {
27
26
  cookies: is(Object),
28
27
  onReady: is(Function),
29
28
  proxyFallback: optional(URL.canParse),
30
- allowedExt: is(RegExp),
31
29
  extraHeaders: Array.isArray
32
30
  })
33
31
  }
package/src/Dashboard.css CHANGED
@@ -107,6 +107,10 @@ menu {
107
107
  top: 0;
108
108
  width: 50%;
109
109
  margin-left: 16px;
110
+
111
+ h2 {
112
+ padding-top: 20px;
113
+ }
110
114
 
111
115
  pre {
112
116
  tab-size: 2;
@@ -119,10 +123,6 @@ menu {
119
123
  background: var(--colorLightGrey);
120
124
  font-family: monospace;
121
125
  }
122
-
123
- &.Documentation {
124
- margin-bottom: 12px;
125
- }
126
126
  }
127
127
  }
128
128
 
package/src/Dashboard.js CHANGED
@@ -19,7 +19,6 @@ const Strings = {
19
19
  const CSS = {
20
20
  DelayToggler: 'DelayToggler',
21
21
  InternalServerErrorToggler: 'InternalServerErrorToggler',
22
- Documentation: 'Documentation',
23
22
  MockSelector: 'MockSelector',
24
23
  PayloadViewer: 'PayloadViewer',
25
24
  PreviewLink: 'PreviewLink',
@@ -30,7 +29,6 @@ const CSS = {
30
29
  }
31
30
 
32
31
  const r = createElement
33
- const refDocumentation = useRef()
34
32
  const refPayloadViewer = useRef()
35
33
  const refPayloadFile = useRef()
36
34
 
@@ -64,7 +62,6 @@ function DevPanel(brokersByMethod, cookies, comments) {
64
62
  r('table', null, Object.entries(brokersByMethod).map(([method, brokers]) =>
65
63
  r(SectionByMethod, { method, brokers }))),
66
64
  r('div', { className: CSS.PayloadViewer },
67
- r('pre', { ref: refDocumentation, className: CSS.Documentation }),
68
65
  r('h2', { ref: refPayloadFile }, Strings.mock),
69
66
  r('pre', { ref: refPayloadViewer }, Strings.click_link_to_preview)))))
70
67
  }
@@ -134,14 +131,14 @@ function SectionByMethod({ method, brokers }) {
134
131
  .filter(([, broker]) => broker.mocks.length > 1) // Excludes Markdown only routes (>1 because of the autogen500)
135
132
  .map(([urlMask, broker]) =>
136
133
  r('tr', null,
137
- r('td', null, r(PreviewLink, { method, urlMask, documentation: broker.documentation })),
134
+ r('td', null, r(PreviewLink, { method, urlMask })),
138
135
  r('td', null, r(MockSelector, { broker })),
139
136
  r('td', null, r(DelayToggler, { broker })),
140
137
  r('td', null, r(InternalServerErrorToggler, { broker }))
141
138
  ))))
142
139
  }
143
140
 
144
- function PreviewLink({ method, urlMask, documentation }) {
141
+ function PreviewLink({ method, urlMask }) {
145
142
  return (
146
143
  r('a', {
147
144
  className: CSS.PreviewLink,
@@ -150,13 +147,6 @@ function PreviewLink({ method, urlMask, documentation }) {
150
147
  async onClick(event) {
151
148
  event.preventDefault()
152
149
  try {
153
- if (documentation) {
154
- const r = await fetch(documentation)
155
- refDocumentation.current.innerText = await r.text()
156
- }
157
- else
158
- refDocumentation.current.innerText = ''
159
-
160
150
  const spinner = setTimeout(() => refPayloadViewer.current.innerText = Strings.fetching, 180)
161
151
  const res = await fetch(this.href, {
162
152
  method: this.getAttribute('data-method')
package/src/MockBroker.js CHANGED
@@ -5,19 +5,15 @@ import { isDirectory } from './utils/fs.js'
5
5
  import { DEFAULT_500_COMMENT } from './ApiConstants.js'
6
6
 
7
7
 
8
- // MockBroker is a state for a particular route. It knows the available
9
- // mock files that can be served for the route, the currently selected
10
- // file, and its delay. Also, knows if the route has documentation (md)
8
+ // MockBroker is a state for a particular route. It knows the available mock files
9
+ // that can be served for the route, the currently selected file, and its delay.
11
10
  export class MockBroker {
12
11
  #route
13
12
 
14
13
  constructor(file) {
15
14
  this.#route = new Route(file)
16
15
  this.method = this.#route.method
17
-
18
- this.documentation = '' // .md
19
-
20
- this.mocks = [] // *.json,txt,js
16
+ this.mocks = []
21
17
  this.currentMock = {
22
18
  file: '',
23
19
  delay: 0
@@ -27,13 +23,9 @@ export class MockBroker {
27
23
  }
28
24
 
29
25
  register(file) {
30
- if (file.endsWith('.md'))
31
- this.documentation = file
32
- else {
33
- if (!this.mocks.length)
34
- this.currentMock.file = file // The first mock file option for a particular route becomes the default
35
- this.mocks.push(file)
36
- }
26
+ if (!this.mocks.length)
27
+ this.currentMock.file = file // The first mock file option for a particular route becomes the default
28
+ this.mocks.push(file)
37
29
  }
38
30
 
39
31
  urlMaskMatches(url) { return this.#route.urlMaskMatches(url) }
@@ -77,7 +69,7 @@ export class MockBroker {
77
69
  }
78
70
 
79
71
  #registerTemp500() {
80
- const { urlMask, method } = Route.parseFilename(this.mocks[0] || this.documentation)
72
+ const { urlMask, method } = Route.parseFilename(this.mocks[0])
81
73
  let mask = urlMask
82
74
  if (isDirectory(join(Config.mocksDir, urlMask)))
83
75
  mask = urlMask + '/'
@@ -7,16 +7,10 @@ import { Config } from './Config.js'
7
7
  import { mimeFor } from './utils/mime.js'
8
8
  import * as mockBrokerCollection from './mockBrokersCollection.js'
9
9
  import { JsonBodyParserError } from './utils/http-request.js'
10
- import { sendInternalServerError, sendNotFound, sendFile, sendBadRequest } from './utils/http-response.js'
10
+ import { sendInternalServerError, sendNotFound, sendBadRequest } from './utils/http-response.js'
11
11
 
12
12
 
13
13
  export async function dispatchMock(req, response) {
14
- /* Serve Documentation */
15
- if (req.method === 'GET' && req.url.endsWith('.md')) {
16
- sendFile(response, join(Config.mocksDir, decodeURIComponent(req.url)))
17
- return
18
- }
19
-
20
14
  const broker = mockBrokerCollection.getBrokerForUrl(req.method, req.url)
21
15
  if (!broker) {
22
16
  if (Config.proxyFallback)
package/src/Route.js CHANGED
@@ -44,43 +44,22 @@ export class Route {
44
44
 
45
45
  static parseFilename(file) {
46
46
  const tokens = file.replace(Route.reComments, '').split('.')
47
- return file.endsWith('.md')
48
- ? parseDocumentation(tokens)
49
- : parseMock(tokens)
50
- }
51
- }
52
-
53
- function parseDocumentation(tokens) { // TESTME
54
- if (tokens.length < 3)
55
- return { error: 'Invalid Documentation Filename Convention' }
56
-
57
- const method = tokens.at(-2)
58
- if (!httpMethods.includes(method))
59
- return { error: `Unrecognized HTTP Method: "${method}"` }
60
-
61
- return {
62
- urlMask: '/' + removeTrailingSlash(tokens.at(-3)),
63
- method,
64
- status: 200
65
- }
66
- }
67
-
68
- function parseMock(tokens) {
69
- if (tokens.length < 4)
70
- return { error: 'Invalid Filename Convention' }
71
-
72
- const status = Number(tokens.at(-2))
73
- if (!responseStatusIsValid(status))
74
- return { error: `Invalid HTTP Response Status: "${status}"` }
75
-
76
- const method = tokens.at(-3)
77
- if (!httpMethods.includes(method))
78
- return { error: `Unrecognized HTTP Method: "${method}"` }
79
-
80
- return {
81
- urlMask: '/' + removeTrailingSlash(tokens.at(-4)),
82
- method,
83
- status
47
+ if (tokens.length < 4)
48
+ return { error: 'Invalid Filename Convention' }
49
+
50
+ const status = Number(tokens.at(-2))
51
+ if (!responseStatusIsValid(status))
52
+ return { error: `Invalid HTTP Response Status: "${status}"` }
53
+
54
+ const method = tokens.at(-3)
55
+ if (!httpMethods.includes(method))
56
+ return { error: `Unrecognized HTTP Method: "${method}"` }
57
+
58
+ return {
59
+ urlMask: '/' + removeTrailingSlash(tokens.at(-4)),
60
+ method,
61
+ status
62
+ }
84
63
  }
85
64
  }
86
65
 
@@ -5,6 +5,7 @@ import { Route } from './Route.js'
5
5
  import { Config } from './Config.js'
6
6
  import { cookie } from './cookie.js'
7
7
  import { isFile } from './utils/fs.js'
8
+ import { mimeFor } from './utils/mime.js'
8
9
  import { MockBroker } from './MockBroker.js'
9
10
 
10
11
 
@@ -25,7 +26,7 @@ export function init() {
25
26
  cookie.init(Config.cookies)
26
27
 
27
28
  const files = readDir(Config.mocksDir, { recursive: true })
28
- .filter(f => Config.allowedExt.test(f) && isFile(join(Config.mocksDir, f)))
29
+ .filter(f => isFile(join(Config.mocksDir, f)) && mimeFor(f))
29
30
  .sort()
30
31
 
31
32
  for (const file of files) {
package/src/utils/mime.js CHANGED
@@ -1,9 +1,28 @@
1
1
  // https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types
2
+ // m = {};
3
+ // for (const row of tbody.children)
4
+ // m[row.children[0].querySelector('code').innerText] = row.children[2].querySelector('code').innerText
5
+
2
6
  const mimes = {
3
- aac: 'audio/acc',
7
+ '3g2': 'video/3gpp2',
8
+ '3gp': 'video/3gpp',
9
+ '7z': 'application/x-7z-compressed',
10
+ aac: 'audio/aac',
11
+ abw: 'application/x-abiword',
4
12
  apng: 'image/apng',
13
+ arc: 'application/x-freearc',
14
+ avi: 'video/x-msvideo',
5
15
  avif: 'image/avif',
16
+ azw: 'application/vnd.amazon.ebook',
17
+ bin: 'application/octet-stream',
18
+ bmp: 'image/bmp',
19
+ bz2: 'application/x-bzip2',
20
+ bz: 'application/x-bzip',
21
+ cda: 'application/x-cdf',
22
+ cjs: 'text/javascript',
23
+ csh: 'application/x-csh',
6
24
  css: 'text/css',
25
+ csv: 'text/csv',
7
26
  doc: 'application/msword',
8
27
  docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
9
28
  eot: 'application/vnd.ms-fontobject',
@@ -14,34 +33,59 @@ const mimes = {
14
33
  html: 'text/html',
15
34
  ico: 'image/vnd.microsoft.icon',
16
35
  ics: 'text/calendar',
36
+ jar: 'application/java-archive',
17
37
  jpeg: 'image/jpeg',
18
38
  jpg: 'image/jpeg',
19
- js: 'application/javascript',
39
+ js: 'text/javascript',
20
40
  json: 'application/json',
21
41
  jsonld: 'application/ld+json',
22
- md: 'text/markdown',
42
+ mid: 'audio/midi',
23
43
  mjs: 'text/javascript',
24
44
  mp3: 'audio/mpeg',
25
45
  mp4: 'video/mp4',
26
- oft: 'font/otf',
46
+ mpeg: 'video/mpeg',
47
+ mpkg: 'application/vnd.apple.installer+xml',
48
+ odp: 'application/vnd.oasis.opendocument.presentation',
49
+ ods: 'application/vnd.oasis.opendocument.spreadsheet',
50
+ odt: 'application/vnd.oasis.opendocument.text',
51
+ oga: 'audio/ogg',
52
+ ogv: 'video/ogg',
53
+ ogx: 'application/ogg',
54
+ opus: 'audio/ogg',
55
+ otf: 'font/otf',
27
56
  pdf: 'application/pdf',
57
+ php: 'application/x-httpd-php',
28
58
  png: 'image/png',
59
+ ppt: 'application/vnd.ms-powerpoint',
60
+ pptx: 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
61
+ rar: 'application/vnd.rar',
62
+ rtf: 'application/rtf',
63
+ sh: 'application/x-sh',
64
+ svg: 'image/svg+xml',
65
+ tar: 'application/x-tar',
66
+ tif: 'image/tiff',
67
+ ts: 'video/mp2t',
29
68
  ttf: 'font/ttf',
30
- txt: 'plain/text',
69
+ txt: 'text/plain',
70
+ vsd: 'application/vnd.visio',
31
71
  wav: 'audio/wav',
32
72
  weba: 'audio/webm',
33
73
  webm: 'video/webm',
34
74
  webp: 'image/webp',
35
75
  woff2: 'font/woff2',
36
76
  woff: 'font/woff',
77
+ xhtml: 'application/xhtml+xml',
78
+ xls: 'application/vnd.ms-excel',
79
+ xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
37
80
  xml: 'application/xml',
81
+ xul: 'application/vnd.mozilla.xul+xml',
38
82
  zip: 'application/zip'
39
83
  }
40
84
 
41
85
  export function mimeFor(filename) {
42
- const ext = filename.replace(/.*\./, '')
86
+ const ext = filename.replace(/.*\./, '').toLowerCase()
43
87
  const mime = mimes[ext] || ''
44
88
  if (!mime)
45
- console.error(`Missing MIME for ${filename}`)
89
+ console.info(`Missing MIME for ${filename}`)
46
90
  return mime
47
- }
91
+ }
@@ -1 +0,0 @@
1
- "This is an index-like route (i.e. /api/user). This file has no name, only the extension convention is needed for the index path."
@@ -1,9 +0,0 @@
1
- # Request Body
2
-
3
- ```json
4
- {
5
- "new_name": "John"
6
- }
7
- ```
8
-
9
- This is just documentation. In this example, for the request body payload.