mockaton 2.2.0 → 2.3.1

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.
Binary file
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # Mockaton
2
2
  _Mockaton_ is a mock server for developing and testing frontends.
3
3
 
4
- It scans `Config.mocksDir` for files following a specific
4
+ It scans a given directory for files following a specific
5
5
  file name convention, which is similar to the URL paths. For
6
6
  example, the following file will be served for `/api/user/1234`
7
7
  ```
@@ -13,60 +13,16 @@ extension](https://github.com/ericfortis/devtools-ext-tar-http-requests) can
13
13
  be used for downloading a TAR of your XHR requests following that convention.
14
14
 
15
15
 
16
- ### Mock Variants
17
- Each route can have many mocks, which could either be:
18
- - Different response __status code__.
19
- - e.g. for testing error responses.
20
- - __Comment__ on the filename, which is anything within parentheses.
21
- - e.g. `api/user(my-comment).POST.201.json`
22
-
23
- Those alternatives can be manually selected in the dashboard
24
- UI, or programmatically, for instance, for setting up tests.
25
-
26
- The first file in **alphabetical order** becomes the default mock.
27
-
28
- ### Optionally, you can write mocks in JavaScript
29
- An Object, Array, or String is sent as JSON.
30
-
31
- `api/foo.GET.200.js`
32
- ```js
33
- export default [
34
- { id: 0 }
35
- ]
36
- ```
37
-
38
- Or, export default a function. There, you
39
- can override the response status and the default JSON content
40
- type. But don’t call `response.end()`, just return a string.
41
- ```js
42
- export default function (req, response) {
43
- return JSON.stringify({ a: 1 })
44
- }
45
- ```
46
-
47
-
48
- ### Proxying Routes
49
- `Config.proxyFallback` lets you specify a target
50
- server for serving routes you don’t have mocks for.
51
-
52
-
53
16
  ## Getting Started
54
17
  The best way to learn _Mockaton_ is by checking out this repo and
55
18
  exploring its [sample-mocks/](./sample-mocks) directory. Then, run
56
19
  [`./_usage_example.js`](./_usage_example.js) and you’ll see this dashboard:
57
20
 
58
- <img src="./README-dashboard.png" style="max-width:890px"/>
59
-
60
-
61
- ## Delay 🕓
62
- The clock icon next to the mock selector is a checkbox for delaying a
63
- particular response. They are handy for testing spinners.
64
21
 
65
- The delay is globally configurable via `Config.delay = 1200` (milliseconds).
22
+ <img src="./README-dashboard.png" style="max-width:820px"/>
66
23
 
67
- ---
68
24
 
69
- ## Basic Usage (see [_usage_example.js](./_usage_example.js))
25
+ ## Basic Usage
70
26
  ```
71
27
  npm install mockaton
72
28
  ```
@@ -101,8 +57,54 @@ interface Config {
101
57
  extraHeaders?: []
102
58
  }
103
59
  ```
60
+
104
61
  ---
105
62
 
63
+ ## Mock Variants
64
+ Each route can have many mocks, which could either be:
65
+ - Different response __status code__.
66
+ - e.g. for testing error responses.
67
+ - __Comment__ on the filename, which is anything within parentheses.
68
+ - e.g. `api/user(my-comment).POST.201.json`
69
+
70
+ Those alternatives can be manually selected in the dashboard
71
+ UI, or programmatically, for instance, for setting up tests.
72
+
73
+ The first file in **alphabetical order** becomes the default mock.
74
+
75
+ ## You can write JSON mocks in JavaScript
76
+ An Object, Array, or String is sent as JSON.
77
+
78
+ `api/foo.GET.200.js`
79
+ ```js
80
+ export default [
81
+ { id: 0 }
82
+ ]
83
+ ```
84
+
85
+ Or, export default a function. There, you
86
+ can override the response status and the default JSON content
87
+ type. But don’t call `response.end()`, just return a string.
88
+ ```js
89
+ export default function (req, response) {
90
+ return JSON.stringify({ a: 1 })
91
+ }
92
+ ```
93
+
94
+
95
+ ## Proxying Routes
96
+ `Config.proxyFallback` lets you specify a target
97
+ server for serving routes you don’t have mocks for.
98
+
99
+
100
+
101
+ ## Delay 🕓
102
+ The clock icon next to the mock selector is a checkbox for delaying a
103
+ particular response. They are handy for testing spinners.
104
+
105
+ The delay is globally configurable via `Config.delay = 1200` (milliseconds).
106
+
107
+
106
108
  ## File Name Convention
107
109
 
108
110
 
@@ -125,7 +127,7 @@ Comments are anything within parentheses, including them.
125
127
  They are ignored for URL purposes, so they have no effect
126
128
  on the URL mask. For example, these two are for `/api/foo`
127
129
  <pre>
128
- api/foo<b>(my comment)</b>.GET.200.json<b>(foo)</b>
130
+ api/foo<b>(my comment)</b>.GET.200.json<b>(bar)</b>
129
131
  api/foo.GET.200.json
130
132
  </pre>
131
133
 
@@ -143,10 +145,10 @@ but since that’s part of the query string, it’s ignored anyway.
143
145
 
144
146
 
145
147
 
146
- ### Default (index-like) file
147
- For the default route of a directory, omit the name (just use
148
- the extension). For example, the following files will be routed
149
- to `api/foo` because comments and the query string are ignored.
148
+ ### Default (index-like) route
149
+ For the default route of a directory, omit the mock filename name
150
+ (<b>just use the extension</b>). For example, the following files will be
151
+ routed to `api/foo` because comments and the query string are ignored.
150
152
  ```text
151
153
  api/foo/.GET.200.json
152
154
  api/foo/?bar=[bar].GET.200.json
@@ -160,7 +162,7 @@ The selected cookie is sent in every response in the `Set-Cookie` header.
160
162
  import { jwtCookie } from 'mockaton'
161
163
 
162
164
  Config.cookies = {
163
- 'My Admin User': 'my-cookie=1;Path=/;SameSite=strict',
165
+ 'My Admin User': 'my-cookie=1;Path=/;SameSite=strict',
164
166
  'My Normal User': 'my-cookie=0;Path=/;SameSite=strict',
165
167
  'My JWT': jwtCookie('my-cookie', {
166
168
  email: 'john.doe@example.com',
@@ -175,15 +177,14 @@ words, it’s useful if you only care about its payload.
175
177
 
176
178
 
177
179
  ## `Config.extraHeaders`
178
- They are applied last, right before ending the response. In
179
- other words, they can overwrite the `Content-Type`. They are
180
- not tuples, the header name goes in even-numbered indices.
180
+ They are applied last, right before ending the response. In other words, they
181
+ can overwrite the `Content-Type`. The header name goes in even indices.
181
182
 
182
183
  ```js
183
184
  Config.extraHeaders = [
184
185
  'Server', 'Mockaton',
185
- 'Set-Cookie', 'another-cookie=FOO;Path=/;SameSite=strict',
186
- 'Set-Cookie', 'another-cookie=BAR;Path=/;SameSite=strict'
186
+ 'Set-Cookie', 'foo=FOO;Path=/;SameSite=strict',
187
+ 'Set-Cookie', 'bar=BAR;Path=/;SameSite=strict'
187
188
  ]
188
189
  ```
189
190
 
@@ -223,7 +224,7 @@ fetch(addr + '/mockaton/bulk-select-by-comment', {
223
224
  ### Reset
224
225
  Re-Initialize the collection and its states (selected mocks and cookies, delays, etc.).
225
226
  ```js
226
- fetch(add + '/mockaton/reset', {
227
+ fetch(addr + '/mockaton/reset', {
227
228
  method: 'PATCH'
228
229
  })
229
230
  ```
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": "2.2.0",
5
+ "version": "2.3.1",
6
6
  "main": "index.js",
7
7
  "types": "index.d.ts",
8
8
  "license": "MIT",
@@ -3,6 +3,7 @@
3
3
 
4
4
  export default function (req, response) {
5
5
  return JSON.stringify([
6
- { id: 0 }
6
+ 'http://example.com/foo',
7
+ 'http://example.com/bar',
7
8
  ])
8
9
  }
package/src/Api.js CHANGED
@@ -9,7 +9,7 @@ import { Config } from './Config.js'
9
9
  import { DF, API } from './ApiConstants.js'
10
10
  import { parseJSON } from './utils/http-request.js'
11
11
  import * as mockBrokersCollection from './mockBrokersCollection.js'
12
- import { sendOK, sendBadRequest, sendJSON, sendFile } from './utils/http-response.js'
12
+ import { sendOK, sendBadRequest, sendJSON, sendFile, sendUnprocessableContent } from './utils/http-response.js'
13
13
 
14
14
 
15
15
  export const apiGetRequests = new Map([
@@ -24,11 +24,11 @@ export const apiGetRequests = new Map([
24
24
  ])
25
25
 
26
26
  export const apiPatchRequests = new Map([
27
- [API.bulkSelect, bulkUpdateBrokersByCommentTag],
28
27
  [API.edit, updateBroker],
29
28
  [API.reset, reinitialize],
30
29
  [API.cookies, selectCookie],
31
- [API.fallback, updateProxyFallback]
30
+ [API.fallback, updateProxyFallback],
31
+ [API.bulkSelect, bulkUpdateBrokersByCommentTag]
32
32
  ])
33
33
 
34
34
  function serveDashboard(_, response) {
@@ -48,6 +48,11 @@ function listMockBrokers(_, response) {
48
48
  }
49
49
 
50
50
 
51
+ function reinitialize(_, response) {
52
+ mockBrokersCollection.init()
53
+ sendOK(response)
54
+ }
55
+
51
56
  async function selectCookie(req, response) {
52
57
  try {
53
58
  cookie.setCurrent(await parseJSON(req))
@@ -59,15 +64,14 @@ async function selectCookie(req, response) {
59
64
  }
60
65
  }
61
66
 
62
- function reinitialize(_, response) {
63
- mockBrokersCollection.init()
64
- sendOK(response)
65
- }
66
-
67
67
  async function updateBroker(req, response) {
68
68
  try {
69
69
  const body = await parseJSON(req)
70
70
  const broker = mockBrokersCollection.getBrokerByFilename(body[DF.file])
71
+ if (!broker) {
72
+ sendUnprocessableContent(response)
73
+ return
74
+ }
71
75
  if (DF.delayed in body)
72
76
  broker.updateDelay(body[DF.delayed])
73
77
  broker.updateFile(body[DF.file])
@@ -79,9 +83,9 @@ async function updateBroker(req, response) {
79
83
  }
80
84
  }
81
85
 
82
- async function bulkUpdateBrokersByCommentTag(req, response) {
86
+ async function updateProxyFallback(req, response) {
83
87
  try {
84
- mockBrokersCollection.setMocksMatchingComment(await parseJSON(req))
88
+ Config.proxyFallback = await parseJSON(req)
85
89
  sendOK(response)
86
90
  }
87
91
  catch (error) {
@@ -90,9 +94,9 @@ async function bulkUpdateBrokersByCommentTag(req, response) {
90
94
  }
91
95
  }
92
96
 
93
- async function updateProxyFallback(req, response) {
97
+ async function bulkUpdateBrokersByCommentTag(req, response) {
94
98
  try {
95
- Config.proxyFallback = await parseJSON(req)
99
+ mockBrokersCollection.setMocksMatchingComment(await parseJSON(req))
96
100
  sendOK(response)
97
101
  }
98
102
  catch (error) {
package/src/Config.js CHANGED
@@ -26,7 +26,7 @@ export function setup(options) {
26
26
  delay: ms => Number.isInteger(ms) && ms > 0,
27
27
  cookies: is(Object),
28
28
  skipOpen: is(Boolean),
29
- proxyFallback: is(String),
29
+ proxyFallback: optional(URL.canParse),
30
30
  allowedExt: is(RegExp),
31
31
  generate500: is(Boolean),
32
32
  extraHeaders: Array.isArray
package/src/Dashboard.css CHANGED
@@ -14,23 +14,13 @@ body {
14
14
  padding: 16px;
15
15
  }
16
16
  * {
17
+ padding: 0;
17
18
  border: 0;
18
19
  margin: 0;
19
20
  font-family: system-ui, sans-serif;
20
21
  font-size: 100%;
21
22
  }
22
23
 
23
- h1 {
24
- padding: 12px 0;
25
- margin: 0;
26
- font-size: 2rem;
27
- }
28
-
29
-
30
- select {
31
- padding: 3px 0;
32
- border: 1px solid #444;
33
- }
34
24
 
35
25
  fieldset {
36
26
  width: 120px;
@@ -47,15 +37,6 @@ fieldset {
47
37
  }
48
38
  }
49
39
 
50
- .CookieSelector {
51
- display: flex;
52
- align-items: center;
53
- margin-left: 20px;
54
-
55
- select {
56
- margin-left: 5px;
57
- }
58
- }
59
40
 
60
41
  main {
61
42
  display: flex;
@@ -72,14 +53,39 @@ main {
72
53
  }
73
54
  }
74
55
 
75
- .TitleWrap {
56
+ menu {
76
57
  display: flex;
77
- align-items: center;
58
+ margin-bottom: 12px;
59
+ gap: 14px;
60
+ align-items: flex-end;
61
+
62
+ h1 {
63
+ margin: 0;
64
+ margin-right: 14px;
65
+ font-size: 2rem;
66
+ }
67
+
68
+ label {
69
+ span {
70
+ display: block;
71
+ color: #555;
72
+ font-size: .85rem;
73
+ }
74
+
75
+ select {
76
+ width: 143px;
77
+ padding: 3px 0;
78
+ border: 1px solid #bbb;
79
+ margin-top: 1px;
80
+ cursor: pointer;
81
+ border-radius: 4px;
82
+ font-size: 0.9rem;
83
+ }
84
+ }
78
85
 
79
86
  button {
80
- padding: 3px 12px;
87
+ padding: 4px 12px;
81
88
  border: 1px solid var(--colorRed);
82
- margin-left: 20px;
83
89
  background: transparent;
84
90
  color: var(--colorRed);
85
91
  border-radius: 50px;
@@ -132,18 +138,11 @@ main {
132
138
  }
133
139
  }
134
140
 
135
- .BulkSelectSection {
136
- margin: 20px 0;
137
- }
138
-
139
- .BulkSelectSection select {
140
- margin-top: 5px;
141
- }
142
-
143
141
  .MockSelector {
144
142
  width: 300px;
145
143
  padding: 8px 1px;
146
144
  border: 0;
145
+ border-radius: 4px;
147
146
  background: #eee;
148
147
  text-align: right;
149
148
  direction: rtl;
package/src/Dashboard.js CHANGED
@@ -16,14 +16,11 @@ const Strings = {
16
16
  }
17
17
 
18
18
  const CSS = {
19
- BulkSelectSection: 'BulkSelectSection',
20
- CookieSelector: 'CookieSelector',
21
19
  DelayCheckbox: 'DelayCheckbox',
22
20
  Documentation: 'Documentation',
23
21
  MockSelector: 'MockSelector',
24
22
  PayloadViewer: 'PayloadViewer',
25
23
  PreviewLink: 'PreviewLink',
26
- TitleWrap: 'TitleWrap',
27
24
 
28
25
  bold: 'bold',
29
26
  chosen: 'chosen',
@@ -57,13 +54,11 @@ function DevPanel(brokersByMethod, cookies, comments) {
57
54
  document.title = Strings.title
58
55
  return (
59
56
  r('div', null,
60
- r('div', { className: CSS.TitleWrap },
57
+ r('menu', null,
61
58
  r('h1', null, Strings.title),
62
- r(ResetButton),
63
- r(CookieSelector, { list: cookies })),
64
- r('div', { className: CSS.BulkSelectSection },
65
- r('h2', null, Strings.bulk_select_by_comment),
66
- r(BulkSelector, { comments })),
59
+ r(CookieSelector, { list: cookies }),
60
+ r(BulkSelector, { comments }),
61
+ r(ResetButton)),
67
62
  r('main', null,
68
63
  r('table', null, Object.entries(brokersByMethod).map(([method, brokers]) =>
69
64
  r(SectionByMethod, { method, brokers }))),
@@ -88,8 +83,8 @@ function ResetButton() {
88
83
 
89
84
  function CookieSelector({ list }) {
90
85
  return (
91
- r('label', { className: CSS.CookieSelector },
92
- Strings.cookie,
86
+ r('label', null,
87
+ r('span', null, Strings.cookie),
93
88
  r('select', {
94
89
  autocomplete: 'off',
95
90
  disabled: list.length <= 1,
@@ -111,21 +106,23 @@ function CookieSelector({ list }) {
111
106
 
112
107
  function BulkSelector({ comments }) {
113
108
  return (
114
- r('select', {
115
- autocomplete: 'off',
116
- disabled: comments.length <= 1,
117
- onChange() {
118
- fetch(API.bulkSelect, {
119
- method: 'PATCH',
120
- body: JSON.stringify(this.value)
121
- })
122
- .then(init)
123
- .catch(console.error)
124
- }
125
- }, [Strings.select_one].concat(comments).map(item =>
126
- r('option', {
127
- value: item
128
- }, item))))
109
+ r('label', null,
110
+ r('span', null, Strings.bulk_select_by_comment),
111
+ r('select', {
112
+ autocomplete: 'off',
113
+ disabled: comments.length <= 1,
114
+ onChange() {
115
+ fetch(API.bulkSelect, {
116
+ method: 'PATCH',
117
+ body: JSON.stringify(this.value)
118
+ })
119
+ .then(init)
120
+ .catch(console.error)
121
+ }
122
+ }, [Strings.select_one].concat(comments).map(item =>
123
+ r('option', {
124
+ value: item
125
+ }, item)))))
129
126
  }
130
127
 
131
128
 
@@ -36,8 +36,8 @@ export async function dispatchMock(req, response) {
36
36
  response.setHeader('content-type', mimeFor('.json'))
37
37
  const jsExport = await importDefault(file)
38
38
  mockText = typeof jsExport === 'function'
39
- ? jsExport(req, response)
40
- : JSON.stringify(jsExport)
39
+ ? await jsExport(req, response)
40
+ : JSON.stringify(jsExport, null, 2)
41
41
  }
42
42
  else {
43
43
  response.setHeader('content-type', mimeFor(file))
@@ -54,7 +54,8 @@ export const getAll = () => collection
54
54
 
55
55
  export const getBrokerByFilename = file => {
56
56
  const { method, urlMask } = Route.parseFilename(file)
57
- return collection[method][urlMask]
57
+ if (collection[method])
58
+ return collection[method][urlMask]
58
59
  }
59
60
 
60
61
  // Searching the routes in reverse order so dynamic params (e.g.
@@ -54,6 +54,11 @@ export function sendNotFound(response) {
54
54
  response.end()
55
55
  }
56
56
 
57
+ export function sendUnprocessableContent(response) {
58
+ response.statusCode = 422
59
+ response.end()
60
+ }
61
+
57
62
  export function sendInternalServerError(response) {
58
63
  response.statusCode = 500
59
64
  response.end()