Skip to content

Commit

Permalink
feat: add support for disabling caching in configuration
Browse files Browse the repository at this point in the history
  • Loading branch information
antoinechalifour committed Aug 31, 2019
1 parent 63bcbd6 commit 2b513a8
Show file tree
Hide file tree
Showing 10 changed files with 313 additions and 7 deletions.
41 changes: 35 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,41 @@ _Note: `npx` is a command that comes with `npm` when installing Node and enables

The following options are supported:

| Option | Description | Example | Default value |
| ------------------- | ------------------------------------------------------------- | --------------------- | -------------- |
| targetUrl | The API base URL | http://localhost:4000 | None |
| port | The port used to launch Memento | 9876 | 3344 |
| cacheDirectory | The cache directory used for storing responses | memento-integration | .memento-cache |
| useRealResponseTime | Whether Memento should respond using the actual response time | true | false |
| Option | Description | Example | Default value |
| ---------------------- | ------------------------------------------------------------- | ------------------------------------------------------ | -------------- |
| targetUrl | The API base URL | http://localhost:4000 | None |
| port | The port used to launch Memento | 9876 | 3344 |
| cacheDirectory | The cache directory used for storing responses | memento-integration | .memento-cache |
| useRealResponseTime | Whether Memento should respond using the actual response time | true | false |
| disableCachingPatterns | An array of patterns usd to ignore caching certain requests | [{ method: 'post', urlPattern: '/pokemon/*/sprites' }] | [] |

### Option: disableCachingPatterns

You may use `disableCachingPatterns` in your configuration to tell Memento to ignore caching responses based on the request method and URL. As an example, if you wish to not cache routes likes `/pokemon/mew/abilities` and `pokemon/ditto/abilities`, you may use the following configuration :

```
{
// ... your configuration
"disableCachingPatterns": [{
"method": "GET",
"urlPattern": "/pokemon/*/abilities"
}]
}
```

The [minimatch](https://www.npmjs.com/package/minimatch) package is used for comparing glob patterns and the actual url. You may use a tool like [globtester](http://www.globtester.com) to test your configurations.

### Recipe: ignore caching all POST requests

```
{
// ... your configuration
"disableCachingPatterns": [{
"method": "post",
"urlPattern": "**"
}]
}
```

## Examples

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@
"fs-extra": "8.1.0",
"koa": "2.8.1",
"koa-bodyparser": "4.2.1",
"minimatch": "^3.0.4",
"object-hash": "1.3.1",
"text-table": "0.2.0",
"vorpal": "1.12.0"
Expand Down
14 changes: 14 additions & 0 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
GetRequestDetails,
SetResponseTime,
} from './domain/usecase';
import { DisableCachePattern } from './domain/entity';
import { getRequestDirectory } from './utils/path';

interface CreateCliOptions {
Expand All @@ -23,6 +24,9 @@ interface CreateCliOptions {
export function createCli({ container }: CreateCliOptions) {
const targetUrl = container.resolve<string>('targetUrl');
const cacheDirectory = container.resolve<string>('cacheDirectory');
const disableCachingPatterns = container.resolve<DisableCachePattern[]>(
'disableCachingPatterns'
);
const clearAllRequestsUseCase = container.resolve<ClearAllRequests>(
'clearAllRequestsUseCase'
);
Expand Down Expand Up @@ -210,6 +214,16 @@ export function createCli({ container }: CreateCliOptions) {
console.log(chalk`Using Memento {yellow ${appVersion}}`);
console.log(chalk`Request will be forwarded to {yellow ${targetUrl}}`);
console.log(chalk`Cache directory is set to {yellow ${cacheDirectory}}`);

if (disableCachingPatterns.length) {
console.log(chalk`Caching will be disabled for the following patterns:`);

disableCachingPatterns.forEach(option => {
console.log(
chalk`\t- {green ${option.method}} {yellow ${option.urlPattern}}`
);
});
}
console.log(chalk`Type {green help} to get available commands`);

return vorpal;
Expand Down
10 changes: 10 additions & 0 deletions src/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,16 @@ export const configuration = {
useRealResponseTime: getUseRealResponseTime(
cosmicConfiguration.config.useRealResponseTime
),
disableCachingPatterns:
cosmicConfiguration.config.disableCachingPatterns || [],
};

assert(configuration.targetUrl, 'targetUrl option is required');

configuration.disableCachingPatterns.forEach((option: any) => {
assert(option.method, 'Invalid disableCachingPatterns: method is required');
assert(
option.urlPattern,
'Invalid disableCachingPatterns: urlPattern is required'
);
});
4 changes: 4 additions & 0 deletions src/domain/entity/DisableCachePattern.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export interface DisableCachePattern {
method: string;
urlPattern: string;
}
1 change: 1 addition & 0 deletions src/domain/entity/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export * from './Request';
export * from './Response';
export * from './Headers';
export * from './Method';
export * from './DisableCachePattern';
225 changes: 225 additions & 0 deletions src/domain/usecase/RespondToRequest.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ describe('when the response is in the cache', () => {
requestRepository,
networkService,
useRealResponseTime: false,
disableCachingPatterns: [],
});
const method = 'GET';
const url = '/beers/1';
Expand All @@ -56,6 +57,7 @@ describe('when the response is in the cache', () => {
requestRepository,
networkService,
useRealResponseTime: true,
disableCachingPatterns: [],
});
const method = 'GET';
const url = '/beers/1';
Expand Down Expand Up @@ -107,6 +109,12 @@ describe('when no response is in the cache', () => {
requestRepository,
networkService,
useRealResponseTime: true,
disableCachingPatterns: [
{
method: 'POST',
urlPattern: '/beers/1',
},
],
});
const method = 'GET';
const url = '/beers/1';
Expand Down Expand Up @@ -148,3 +156,220 @@ describe('when no response is in the cache', () => {
);
});
});

describe('when caching is disabled for the method and url', () => {
beforeEach(() => {
(networkService.executeRequest as jest.Mock).mockResolvedValue(
new Response(
200,
{ 'cache-control': 'something' },
Buffer.from('some body'),
66
)
);
});

it('should not cache the response (1 matching pattern)', async () => {
// Given
const useCase = new RespondToRequest({
networkService,
requestRepository,
useRealResponseTime: false,
disableCachingPatterns: [
{
method: 'post',
urlPattern: '/pokemon/ditto',
},
],
});
const method = 'POST';
const url = '/pokemon/ditto';

// When
const response = await useCase.execute(method, url, {}, '');

//Then
expect(requestRepository.persistResponseForRequest).not.toHaveBeenCalled();
expect(requestRepository.getResponseByRequestId).not.toHaveBeenCalled();
expect(response).toEqual(
new Response(
200,
{ 'cache-control': 'something' },
Buffer.from('some body'),
66
)
);
});

it('should not cache the response (3 patterns / 1 matching pattern)', async () => {
// Given
const useCase = new RespondToRequest({
networkService,
requestRepository,
useRealResponseTime: false,
disableCachingPatterns: [
{
method: 'POST',
urlPattern: '/pokemon/ditto',
},
{
method: 'get',
urlPattern: '/pokemon/ditto',
},
{
method: 'post',
urlPattern: '/pokemon/ditto?format=true',
},
],
});
const method = 'POST';
const url = '/pokemon/ditto';

// When
const response = await useCase.execute(method, url, {}, '');

//Then
expect(requestRepository.persistResponseForRequest).not.toHaveBeenCalled();
expect(requestRepository.getResponseByRequestId).not.toHaveBeenCalled();
expect(response).toEqual(
new Response(
200,
{ 'cache-control': 'something' },
Buffer.from('some body'),
66
)
);
});

it('should not cache the response (glob style)', async () => {
// Given
const useCase = new RespondToRequest({
networkService,
requestRepository,
useRealResponseTime: false,
disableCachingPatterns: [
{
method: 'post',
urlPattern: '/pokemon/ditto*',
},
],
});
const method = 'POST';
const url = '/pokemon/ditto?format=true';

// When
const response = await useCase.execute(method, url, {}, '');

//Then
expect(requestRepository.persistResponseForRequest).not.toHaveBeenCalled();
expect(requestRepository.getResponseByRequestId).not.toHaveBeenCalled();
expect(response).toEqual(
new Response(
200,
{ 'cache-control': 'something' },
Buffer.from('some body'),
66
)
);
});

it('should not cache the response (glob style)', async () => {
// Given
const useCase = new RespondToRequest({
networkService,
requestRepository,
useRealResponseTime: false,
disableCachingPatterns: [
{
method: 'get',
urlPattern: '/pokemon/mew',
},
],
});
const method = 'GET';
const url = '/pokemon/mew/';

// When
const response = await useCase.execute(method, url, {}, '');

//Then
expect(requestRepository.persistResponseForRequest).not.toHaveBeenCalled();
expect(requestRepository.getResponseByRequestId).not.toHaveBeenCalled();
expect(response).toEqual(
new Response(
200,
{ 'cache-control': 'something' },
Buffer.from('some body'),
66
)
);
});

it('should not cache the response (nested route)', async () => {
// Given
const useCase = new RespondToRequest({
networkService,
requestRepository,
useRealResponseTime: false,
disableCachingPatterns: [
{
method: 'get',
urlPattern: '/pokemon/mew/**/*',
},
],
});
const method = 'GET';
const url = '/pokemon/mew/abilities/2/stats';

// When
const response = await useCase.execute(method, url, {}, '');

//Then
expect(requestRepository.persistResponseForRequest).not.toHaveBeenCalled();
expect(requestRepository.getResponseByRequestId).not.toHaveBeenCalled();
expect(response).toEqual(
new Response(
200,
{ 'cache-control': 'something' },
Buffer.from('some body'),
66
)
);
});

it('should not cache the response (nested route)', async () => {
// Given
const useCase = new RespondToRequest({
networkService,
requestRepository,
useRealResponseTime: false,
disableCachingPatterns: [
{
method: 'get',
urlPattern: '/pokemon/*/sprites/**',
},
{
method: 'post',
urlPattern: '/pokemon/*/sprites/**',
},
],
});
const method = 'GET';
const url = '/pokemon/mew/sprites/2/back';

// When
const response = await useCase.execute(method, url, {}, '');

//Then
expect(requestRepository.persistResponseForRequest).not.toHaveBeenCalled();
expect(requestRepository.getResponseByRequestId).not.toHaveBeenCalled();
expect(response).toEqual(
new Response(
200,
{ 'cache-control': 'something' },
Buffer.from('some body'),
66
)
);
});
});
Loading

0 comments on commit 2b513a8

Please sign in to comment.