Skip to content

Commit

Permalink
#102 🐘 add tests, add readme, move formatTokenValues into helper
Browse files Browse the repository at this point in the history
  • Loading branch information
RiceWithMeat committed Dec 20, 2024
1 parent 2973218 commit 219b847
Show file tree
Hide file tree
Showing 12 changed files with 468 additions and 77 deletions.
274 changes: 238 additions & 36 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ $ npx mock-config-server

Configs are the fundamental part of the mock server. These configs are easy to fill and maintain. Config entities is an object with which you can emulate various application behaviors. You can specify `headers` | `cookies` | `query` | `params` | `body` for Rest request or `headers` | `cookies` | `query` | `variables` for GraphQL request to define what contract data you need to get. Using this mechanism, you can easily simulate the operation of the server and emulate various cases

##### Rest request config
#### Rest request config

Every route must be configured to handle response content in one of three ways: data or [queue](#polling) or [file](#file-responses).

Expand All @@ -97,27 +97,10 @@ Every route must be configured to handle response content in one of three ways:
- `queue?` {Array<{ time?: number; data: any}>} queue for polling with opportunity to set time for each response
- `file?` {string} path to file for return in response
- `settings?` {Settings} settings for route (polling on/off, etc.)
- `entities?` Object<headers | cookies | query | params | body> object that helps in data retrieval
- `entities?` {Object<headers | cookies | query | params | body>} object that helps in data retrieval
- `interceptors?` {Interceptors} functions to change request or response parameters, [read](#interceptors)
- `interceptors?` {Interceptors} functions to change request or response parameters, [read](#interceptors)

##### GraphQL request config

Every route must be configured to handle response content in one of two ways: data or [queue](#polling).

- `operationType` {query | mutation} graphql operation type
- `operationName?` {string | RegExp} graphql operation name
- `query?`: {string} graphql query as string
- `routes` {GraphQLRouteConfig[]} request routes
- `data?` {any} mock data of request
- `queue?` {Array<{ time?: number; data: any}>} queue for polling with opportunity to set time for each response
- `settings?` {Settings} settings for route (polling on/off, etc.)
- `entities?` Object<headers | cookies | query | variables> object that helps in data retrieval
- `interceptors?` {Interceptors} functions to change request or response parameters, [read](#interceptors)
- `interceptors?` {Interceptors} functions to change request or response parameters, [read](#interceptors)

> Every graphql config should contain `operationName` or `query` or both of them
##### Rest example

```javascript
Expand Down Expand Up @@ -164,6 +147,23 @@ fetch('http://localhost:31299/api/user', {
.then((data) => console.log(data)); // { emoji: '🦁', name: 'Nursultan' }
```

#### GraphQL request config

Every route must be configured to handle response content in one of two ways: data or [queue](#polling).

- `operationType` {query | mutation} graphql operation type
- `operationName?` {string | RegExp} graphql operation name
- `query?`: {string} graphql query as string
- `routes` {GraphQLRouteConfig[]} request routes
- `data?` {any} mock data of request
- `queue?` {Array<{ time?: number; data: any}>} queue for polling with opportunity to set time for each response
- `settings?` {Settings} settings for route (polling on/off, etc.)
- `entities?` {Object<headers | cookies | query | variables>} object that helps in data retrieval
- `interceptors?` {Interceptors} functions to change request or response parameters, [read](#interceptors)
- `interceptors?` {Interceptors} functions to change request or response parameters, [read](#interceptors)

> Every graphql config should contain `operationName` or `query` or both of them
##### GraphQL example

```javascript
Expand Down Expand Up @@ -319,6 +319,45 @@ Return `true` if `actualValue` matches your logic or `false` otherwise.
You can use the `checkFunction` from second argument if you want to describe your logic in a more declarative way.
`checkFunction` has the following signature `(checkMode, actualValue, descriptorValue?) => boolean`.

```javascript
/** @type {import('mock-config-server').MockServerConfig} */
const mockServerConfig = {
rest: {
baseUrl: '/api',
configs: [
{
path: '/posts/:postId',
method: 'post',
routes: [
{
entities: {
params: {
postId: {
checkMode: 'function',
value: (actualValue) => +actualValue >= 0 && +actualValue <= 50
},

},
cookies: {
authToken: {
checkMode: 'function',
value: (actualValue, checkFunction) =>
checkFunction('equals', actualValue, 123) ||
checkFunction('startsWith', actualValue, 2)
}
}
},
data: 'Some user data'
}
]
}
]
}
};

module.exports = mockServerConfig;
```

##### Using descriptors for part of REST body or GraphQL variables

If you want to check a certain field of your body or variables, you can use descriptors in flatten object style. In this case server will check every field in entity with corresponding actual field.
Expand Down Expand Up @@ -567,53 +606,216 @@ Object with settings for [CORS](https://developer.mozilla.org/en-US/docs/Web/HTT

Functions to change request or response parameters

- `request?` (params) => void
- `response?` (data, params) => any
- `request?` {(params) => void}
- `response?` {(data, params) => any}

> request interceptors (except interceptor for route) are called regardless of whether the server found a route match or not. So changes in request interceptors can affect whether the server finds the route or not
##### Request

- `params`
- `request` request object
- `setDelay` (delay) => Promise<void>
- `setDelay` {(delay) => Promise<void>}
- `delay` {number} milliseconds of delay time
- `getHeader` (field) => string | number | string[] | undefined
- `getHeader` {(field) => string | number | string[] | undefined}
- `field` {string} name of response header
- `getHeaders` () => Record<string | number | string[] | undefined>
- `getCookie` (name) => string | undefined
- `getHeaders` {() => Record<string | number | string[] | undefined>}
- `getCookie` {(name) => string | undefined}
- `name` {string} name of cookie
- `log` {(logger) => Partial<LoggerTokenValues> | null} logger function [read](#logger)
- `logger` {Logger | undefined} logger options

##### Response

- `data` {any} mock data of request
- `params`
- `request` request object
- `response` response object
- `setDelay` (delay) => Promise<void>
- `setDelay` {(delay) => Promise<void>}
- `delay` {number} milliseconds of delay time
- `setStatusCode` (statusCode) => void
- `setStatusCode` {(statusCode) => void}
- `statusCode` {number} status code for response
- `setHeader` (field, value) => void
- `setHeader` {(field, value) => void}
- `field` {string} name of response header
- `value` {string | string[] | undefined} value of response header
- `appendHeader` (field, value) => void
- `appendHeader` {(field, value) => void}
- `field` {string} name of response header
- `value` {string | string[] | undefined} value of response header
- `getHeader` (field) => string | number | string[] | undefined
- `getHeader` {(field) => string | number | string[] | undefined}
- `field` {string} name of response header
- `getHeaders` () => Record<string | number | string[] | undefined>
- `setCookie` (name, value, options) => void
- `getHeaders` {() => Record<string | number | string[] | undefined>}
- `setCookie` {(name, value, options) => void}
- `name` {string} name of cookie
- `value` {string} value of cookie
- `options` {[CookieOptions](https://expressjs.com/en/resources/middleware/cookie-session.html) | undefined} cookie options (like path, expires, etc.)
- `getCookie` (name) => string | undefined
- `getCookie` {(name) => string | undefined}
- `name` {string} name of cookie
- `clearCookie` (name, options) => void
- `clearCookie` {(name, options) => void}
- `name` {string} name of cookie
- `options` {[CookieOptions](https://expressjs.com/en/resources/middleware/cookie-session.html) | undefined} cookie options (like path, expires, etc.)
- `attachment` (filename) => void
- `attachment` {(filename) => void}
- `filename` {string} name of file in 'Content-Disposition' header
- `log` {(logger) => Partial<LoggerTokenValues> | null} logger function [read](#logger)
- `logger` {Logger | undefined} logger options

#### Logger

You can log requests and responses using `log` function in any [interceptor](#interceptors).

`log` has the following signature `(logger?: Logger) => Partial<LoggerTokenValues> | null`.

`logger` parameter has the following optional properties

- `enabled` {boolean} draw log or not, `true` by default
- `tokenOptions` {LoggerTokenOptions} object map containing tokens to log. Keys is token names, values is boolean. `true` will add token into log, `false` will remove. If tokenOptions is not passed, following tokens will be logged
- Request
- type
- id
- timestamp
- method
- url
- Response
- type
- id
- timestamp
- method
- url
- data
- `rewrite` {(tokenValues: Partial<LoggerTokenValues>) => void} function to customize log default `console.dir(tokenValues, { depth: null })` appearance

returns object with logged token values or `null` if log was not `enabled`

```javascript
/** @type {import('mock-config-server').MockServerConfig} */
const mockServerConfig = {
rest: {
baseUrl: '/api',
configs: [
{
path: '/posts',
method: 'get',
routes: [
{
interceptors: {
request: ({ log }) => {
log({ // logs following object in terminal
enabled: true, //
tokenOptions: { // {
id: true, // id: 1,
type: true, // type: 'request',
timestamp: true, // timestamp: '31.12.2024, 23:59:59,999',
method: true, // method: 'GET',
url: true // url: 'http://localhost:31299/api/rest/posts/1'
} // }
}); //
},
response: (data, { log }) => {
log({ // logs following string in terminal
enabled: true, //
tokenOptions: { // response get: http://localhost:31299/api/rest/posts/1 => 200
type: true,
statusCode: true,
method: true,
url: true
},
rewrite: ({ type, statusCode, method, url }) => {
console.info(`${type} ${method}: ${url} => ${statusCode}`);
}
});
return data;
}
}
}
]
}
]
}
};

export default mockServerConfig;
```

> By default, `timestamp` and `method` tokens are prettified.
> Timestamp transforms from UNIX-timestamp number to `DD.MM.YYYY, HH:mm:ss,sss` string.
> Method transforms from lower case to upper case.
> If `rewrite` function is used, those tokens will be unformatted. You can format them as you need.
##### Logger tokens

- `type` {'request' | 'response'} type of log
- `id` {number} unique id of request to reference request log with response log
- `timestamp` {number} UNIX-timestamp in milliseconds
- `method` {'get' | 'post' | 'delete' | 'put' | 'patch' | 'options'} HTTP method
- `url` {string} requested URL
- `graphQLOperationType` {'query' | 'mutation' | null} GraphQL operation type. null if request is not GraphQL
- `graphQLOperationName` {string} GraphQL operation name. null if request is not GraphQL
- `variables`: {Record<string, any>} GraphQL variables. null if request is not GraphQL or variables is not passed
- `headers` {Record<string, any>} headers object
- `cookies` {Record<string, any>} cookies object
- `query` {Record<string, any>} query object
- `params` {Record<string, any>} params object
- `body` {any} body

Response logger has additional tokens
- `statusCode` {number} response status code
- `data` {any} data returned to client

If you need to log specific mapped entities (headers, cookies, query, params), use `Record<string, boolean>` object instead of boolean.
In that case log will use following logic:

- if some token is `true`, entity will be filtered by `whitelist` logic. *Only* enabled ones will be logged.
- if some token is `false`, entity will be filtered by `blacklist` logic. All entities will be logged *except* disabled.

> Whitelist logic have priority over blacklist if you pass `true` and `false` in same entity.
```javascript
/** @type {import('mock-config-server').MockServerConfig} */
const mockServerConfig = {
rest: {
baseUrl: '/api',
configs: [
{
path: '/posts',
method: 'get',
routes: [
{
interceptors: {
request: ({ log }) => {
log({ // whitelist. only query1 and query2 will be logged
tokenOptions: {
query: {
query1: true,
query2: true
}
}
});
log({ // whitelist. only query1 and query2 will be logged
tokenOptions: {
query: {
query1: true,
query2: true,
query3: false
}
}
});
log({ // blacklist. whole query will be logged except query1
tokenOptions: {
query: {
query1: false
}
}
});
}
}
}
]
}
]
}
};

export default mockServerConfig;
```

## Database

Expand Down Expand Up @@ -815,7 +1017,7 @@ Examples:
mcs --help
```

# Init Command
## Init Command

The init command is used to initialize a new project or set up the initial configuration for a tool. It helps users get started with a new project by providing a streamlined setup process.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ declare global {
operationName: GraphQLOperationName;
variables?: GraphQLEntity<'variables'>;
} | null;
context: any;
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { formatTimestamp } from './formatTimestamp';

describe('formatTimestamp', () => {
test('Should correctly format timestamp', () => {
expect(formatTimestamp(1735623296789)).toBe('31.12.2024, 12:34:56,789');
});
});
Loading

0 comments on commit 219b847

Please sign in to comment.