Skip to content

Commit

Permalink
feat: Add an onIdentifyRequest hook to allow adapter level serializat…
Browse files Browse the repository at this point in the history
…ion (#140)
  • Loading branch information
offirgolan authored Nov 27, 2018
1 parent 19147f7 commit 548002c
Show file tree
Hide file tree
Showing 23 changed files with 318 additions and 132 deletions.
7 changes: 4 additions & 3 deletions packages/@pollyjs/adapter-fetch/src/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import Adapter from '@pollyjs/adapter';
import { Fetch as FetchUtils } from '@pollyjs/utils';

import serializeHeaders from './utils/serializer-headers';

const { defineProperty } = Object;
const IS_STUBBED = Symbol();
Expand Down Expand Up @@ -31,7 +32,7 @@ export default class FetchAdapter extends Adapter {
this.handleRequest({
url,
method: options.method || 'GET',
headers: FetchUtils.serializeHeaders(options.headers),
headers: serializeHeaders(options.headers),
body: options.body,
requestArguments: [url, options]
});
Expand Down Expand Up @@ -72,7 +73,7 @@ export default class FetchAdapter extends Adapter {
return this.respond(
pollyRequest,
response.status,
FetchUtils.serializeHeaders(response.headers),
serializeHeaders(response.headers),
await response.text()
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import '@pollyjs-tests/helpers/global-fetch';

import setupFetchRecord from '@pollyjs-tests/helpers/setup-fetch-record';
import adapterTests from '@pollyjs-tests/integration/adapter-tests';
import adapterNodeTests from '@pollyjs-tests/integration/adapter-node-tests';
import FSPersister from '@pollyjs/persister-fs';
import { setupMocha as setupPolly } from '@pollyjs/core';

Expand All @@ -22,5 +23,6 @@ describe('Integration | Fetch Adapter | Node', function() {
setupPolly.afterEach();

adapterTests();
adapterNodeTests();
commonTests();
});
4 changes: 2 additions & 2 deletions packages/@pollyjs/adapter-xhr/src/index.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import FakeXHR from 'nise/lib/fake-xhr';
import Adapter from '@pollyjs/adapter';
import { XHR as XHRUtils } from '@pollyjs/utils';

import resolveXhr from './utils/resolve-xhr';
import serializeResponseHeaders from './utils/serialize-response-headers';

const SEND = Symbol();
const IS_STUBBED = Symbol();
Expand Down Expand Up @@ -98,7 +98,7 @@ export default class XHRAdapter extends Adapter {
await resolveXhr(xhr, pollyRequest.body);
await pollyRequest.respond(
xhr.status,
XHRUtils.serializeResponseHeaders(xhr.getAllResponseHeaders()),
serializeResponseHeaders(xhr.getAllResponseHeaders()),
xhr.responseText
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import setupFetchRecord from '@pollyjs-tests/helpers/setup-fetch-record';
import adapterTests from '@pollyjs-tests/integration/adapter-tests';
import adapterBrowserTests from '@pollyjs-tests/integration/adapter-browser-tests';
import RESTPersister from '@pollyjs/persister-rest';
import xhrRequest from '@pollyjs-tests/helpers/xhr-request';

import xhrRequest from '../utils/xhr-request';
import XHRAdapter from '../../src';

describe('Integration | XHR Adapter', function() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { XHR as XHRUtils } from '@pollyjs/utils';
import serializeResponseHeaders from '../../src/utils/serialize-response-headers';

export default function request(url, obj = {}) {
return new Promise(resolve => {
Expand All @@ -24,7 +24,7 @@ export default function request(url, obj = {}) {
return new Response(responseBody, {
status: xhr.status,
statusText: xhr.statusText,
headers: XHRUtils.serializeResponseHeaders(xhr.getAllResponseHeaders())
headers: serializeResponseHeaders(xhr.getAllResponseHeaders())
});
});
}
16 changes: 15 additions & 1 deletion packages/@pollyjs/adapter/src/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ACTIONS, MODES, assert } from '@pollyjs/utils';
import { ACTIONS, MODES, Serializers, assert } from '@pollyjs/utils';

import Interceptor from './-private/interceptor';
import isExpired from './utils/is-expired';
Expand Down Expand Up @@ -92,6 +92,8 @@ export default class Adapter {
async handleRequest(request) {
const pollyRequest = this.polly.registerRequest(request);

pollyRequest.on('identify', (...args) => this.onIdentifyRequest(...args));

await pollyRequest.setup();
await this.onRequest(pollyRequest);

Expand Down Expand Up @@ -261,6 +263,18 @@ export default class Adapter {
}

/* Other Hooks */
/**
* @param {PollyRequest} pollyRequest
*/
async onIdentifyRequest(pollyRequest) {
const { identifiers } = pollyRequest;

// Serialize the request body so it can be properly hashed
for (const type of ['blob', 'formData', 'buffer']) {
identifiers.body = await Serializers[type](identifiers.body);
}
}

/**
* @param {PollyRequest} pollyRequest
*/
Expand Down
42 changes: 32 additions & 10 deletions packages/@pollyjs/core/src/-private/request.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { URL, assert, timestamp } from '@pollyjs/utils';

import NormalizeRequest from '../utils/normalize-request';
import parseUrl from '../utils/parse-url';
import serializeRequestBody from '../utils/serialize-request-body';
import guidForRecording from '../utils/guid-for-recording';
import defer from '../utils/deferred-promise';
import {
Expand All @@ -16,12 +15,16 @@ import {

import HTTPBase from './http-base';
import PollyResponse from './response';
import EventEmitter from './event-emitter';

const { keys, freeze } = Object;

const PARSED_URL = Symbol();
const ROUTE = Symbol();
const POLLY = Symbol();
const PARSED_URL = Symbol();
const EVENT_EMITTER = Symbol();

const SUPPORTED_EVENTS = ['identify'];

export default class PollyRequest extends HTTPBase {
constructor(polly, request) {
Expand All @@ -43,6 +46,7 @@ export default class PollyRequest extends HTTPBase {
this.requestArguments = freeze(request.requestArguments || []);
this.promise = defer();
this[POLLY] = polly;
this[EVENT_EMITTER] = new EventEmitter({ eventNames: SUPPORTED_EVENTS });

/*
The action taken with this request (e.g. record, replay, intercept, or passthrough)
Expand Down Expand Up @@ -126,6 +130,24 @@ export default class PollyRequest extends HTTPBase {
return this[ROUTE].shouldIntercept();
}

on(eventName, listener) {
this[EVENT_EMITTER].on(eventName, listener);

return this;
}

once(eventName, listener) {
this[EVENT_EMITTER].once(eventName, listener);

return this;
}

off(eventName, listener) {
this[EVENT_EMITTER].off(eventName, listener);

return this;
}

async setup() {
// Trigger the `request` event
await this._emit('request');
Expand Down Expand Up @@ -197,27 +219,27 @@ export default class PollyRequest extends HTTPBase {
const polly = this[POLLY];
const { _requests: requests } = polly;
const { matchRequestsBy } = this.config;
const identifiers = {};

this.identifiers = {};

// Iterate through each normalizer
keys(NormalizeRequest).forEach(key => {
if (this[key] && matchRequestsBy[key]) {
identifiers[key] = NormalizeRequest[key](
this.identifiers[key] = NormalizeRequest[key](
this[key],
matchRequestsBy[key]
);
}
});

if (identifiers.body) {
identifiers.body = await serializeRequestBody(identifiers.body);
}
// Emit the `identify` event which adapters can use to serialize the request body
await this[EVENT_EMITTER].emit('identify', this);

// Store the identifiers for debugging and testing
this.identifiers = freeze(identifiers);
// Freeze the identifiers so they can no longer be modified
freeze(this.identifiers);

// Guid is a string representation of the identifiers
this.id = md5(stringify(identifiers));
this.id = md5(stringify(this.identifiers));

// Order is calculated on other requests with the same id
// Only requests before this current one are taken into account.
Expand Down
51 changes: 0 additions & 51 deletions packages/@pollyjs/core/src/utils/serialize-request-body.js

This file was deleted.

This file was deleted.

3 changes: 1 addition & 2 deletions packages/@pollyjs/utils/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ export { default as timeout } from './utils/timeout';
export { default as timestamp } from './utils/timestamp';
export { default as buildUrl } from './utils/build-url';

export { default as XHR } from './utils/xhr';
export { default as Fetch } from './utils/fetch';
export { default as Serializers } from './utils/serializers';

export { default as URL } from 'url-parse';
3 changes: 0 additions & 3 deletions packages/@pollyjs/utils/src/utils/fetch/index.js

This file was deleted.

26 changes: 26 additions & 0 deletions packages/@pollyjs/utils/src/utils/serializers/blob.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
export const supportsBlob = (() => {
try {
return !!new Blob();
} catch (e) {
return false;
}
})();

export function readBlob(blob) {
return new Promise((resolve, reject) => {
const reader = new FileReader();

reader.onend = reject;
reader.onabort = reject;
reader.onload = () => resolve(reader.result);
reader.readAsDataURL(new Blob([blob], { type: blob.type }));
});
}

export async function serialize(body) {
if (supportsBlob && body instanceof Blob) {
return await readBlob(body);
}

return body;
}
29 changes: 29 additions & 0 deletions packages/@pollyjs/utils/src/utils/serializers/buffer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/* eslint-env node */

export const supportsBuffer = typeof Buffer !== 'undefined';
export const supportsArrayBuffer = typeof ArrayBuffer !== 'undefined';

export function serialize(body) {
if (supportsBuffer && body) {
let buffer;

if (Buffer.isBuffer(body)) {
buffer = body;
} else if (Array.isArray(body) && body.some(c => Buffer.isBuffer(c))) {
// Body is a chunked array
const chunks = body.map(c => Buffer.from(c));

buffer = Buffer.concat(chunks);
} else if (`${body}` === '[object ArrayBuffer]') {
buffer = Buffer.from(body);
} else if (supportsArrayBuffer && ArrayBuffer.isView(body)) {
buffer = Buffer.from(body.buffer, body.byteOffset, body.byteLength);
}

if (Buffer.isBuffer(buffer)) {
return buffer.toString('hex');
}
}

return body;
}
Loading

0 comments on commit 548002c

Please sign in to comment.