diff --git a/.travis.yml b/.travis.yml index f16e2883..33226d12 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,8 +3,6 @@ node_js: - '10' - '12' -dist: trusty - addons: chrome: stable diff --git a/packages/@pollyjs/adapter-fetch/package.json b/packages/@pollyjs/adapter-fetch/package.json index 8908c53c..d08dd20d 100644 --- a/packages/@pollyjs/adapter-fetch/package.json +++ b/packages/@pollyjs/adapter-fetch/package.json @@ -42,7 +42,8 @@ "dependencies": { "@pollyjs/adapter": "^4.2.1", "@pollyjs/utils": "^4.1.0", - "detect-node": "^2.0.4" + "detect-node": "^2.0.4", + "to-arraybuffer": "^1.0.1" }, "devDependencies": { "@pollyjs/core": "^4.2.1", diff --git a/packages/@pollyjs/adapter-fetch/src/index.js b/packages/@pollyjs/adapter-fetch/src/index.js index c6bd0048..007b0136 100644 --- a/packages/@pollyjs/adapter-fetch/src/index.js +++ b/packages/@pollyjs/adapter-fetch/src/index.js @@ -1,7 +1,10 @@ import Adapter from '@pollyjs/adapter'; import isNode from 'detect-node'; +import { Buffer } from 'buffer/'; +import bufferToArrayBuffer from 'to-arraybuffer'; import serializeHeaders from './utils/serializer-headers'; +import isBufferUtf8Representable from './utils/is-buffer-utf8-representable'; const { defineProperty } = Object; const IS_STUBBED = Symbol(); @@ -164,10 +167,14 @@ export default class FetchAdapter extends Adapter { } ]); + const buffer = Buffer.from(await response.arrayBuffer()); + const isBinaryBuffer = !isBufferUtf8Representable(buffer); + return { statusCode: response.status, headers: serializeHeaders(response.headers), - body: await response.text() + body: buffer.toString(isBinaryBuffer ? 'hex' : 'utf8'), + isBinary: isBinaryBuffer }; } @@ -199,11 +206,16 @@ export default class FetchAdapter extends Adapter { } const { absoluteUrl, response: pollyResponse } = pollyRequest; - const { statusCode } = pollyResponse; - const responseBody = - statusCode === 204 && pollyResponse.body === '' - ? null - : pollyResponse.body; + const { statusCode, body, isBinary } = pollyResponse; + + let responseBody = body; + + if (statusCode === 204 && responseBody === '') { + responseBody = null; + } else if (isBinary) { + responseBody = bufferToArrayBuffer(Buffer.from(body, 'hex')); + } + const response = new Response(responseBody, { status: statusCode, headers: pollyResponse.headers diff --git a/packages/@pollyjs/adapter-fetch/src/utils/is-buffer-utf8-representable.js b/packages/@pollyjs/adapter-fetch/src/utils/is-buffer-utf8-representable.js new file mode 100644 index 00000000..c80b61e0 --- /dev/null +++ b/packages/@pollyjs/adapter-fetch/src/utils/is-buffer-utf8-representable.js @@ -0,0 +1,12 @@ +import { Buffer } from 'buffer/'; + +/** + * Determine if the given buffer is utf8. + * @param {Buffer} buffer + */ +export default function isBufferUtf8Representable(buffer) { + const utfEncodedBuffer = buffer.toString('utf8'); + const reconstructedBuffer = Buffer.from(utfEncodedBuffer, 'utf8'); + + return reconstructedBuffer.equals(buffer); +} diff --git a/packages/@pollyjs/adapter-fetch/tests/integration/adapter-test.js b/packages/@pollyjs/adapter-fetch/tests/integration/adapter-test.js index c145582c..708b5171 100644 --- a/packages/@pollyjs/adapter-fetch/tests/integration/adapter-test.js +++ b/packages/@pollyjs/adapter-fetch/tests/integration/adapter-test.js @@ -5,6 +5,7 @@ import adapterTests from '@pollyjs-tests/integration/adapter-tests'; import adapterPollyTests from '@pollyjs-tests/integration/adapter-polly-tests'; import adapterBrowserTests from '@pollyjs-tests/integration/adapter-browser-tests'; import adapterIdentifierTests from '@pollyjs-tests/integration/adapter-identifier-tests'; +import { Buffer } from 'buffer/'; import FetchAdapter from '../../src'; import pollyConfig from '../utils/polly-config'; @@ -103,6 +104,37 @@ describe('Integration | Fetch Adapter', function() { expect(error.message).to.contain('The user aborted a request.'); }); + it('should be able to download binary content', async function() { + this.timeout(10000); + + const fetch = async () => + Buffer.from( + await this.fetch('/assets/32x32.png').then(res => res.arrayBuffer()) + ); + + this.polly.disconnectFrom(FetchAdapter); + + const nativeResponseBuffer = await fetch(); + + this.polly.connectTo(FetchAdapter); + + const recordedResponseBuffer = await fetch(); + + const { recordingName, config } = this.polly; + + await this.polly.stop(); + this.polly = new Polly(recordingName, config); + this.polly.replay(); + + const replayedResponseBuffer = await fetch(); + + expect(nativeResponseBuffer.equals(recordedResponseBuffer)).to.equal(true); + expect(recordedResponseBuffer.equals(replayedResponseBuffer)).to.equal( + true + ); + expect(nativeResponseBuffer.equals(replayedResponseBuffer)).to.equal(true); + }); + describe('Request', function() { it('should support Request objects', async function() { const { server } = this.polly; diff --git a/tests/assets/32x32.png b/tests/assets/32x32.png new file mode 100644 index 00000000..48dc2998 Binary files /dev/null and b/tests/assets/32x32.png differ diff --git a/tests/integration/persister-tests.js b/tests/integration/persister-tests.js index efef5421..7778f10f 100644 --- a/tests/integration/persister-tests.js +++ b/tests/integration/persister-tests.js @@ -370,7 +370,7 @@ export default function persisterTests() { // Binary content server.get(this.recordUrl()).once('beforeResponse', (req, res) => { res.isBinary = true; - res.body = 'Some binary content'; + res.body = '536f6d6520636f6e74656e74'; }); await this.fetchRecord(); diff --git a/tests/middleware.js b/tests/middleware.js index f8e22978..76ddf78f 100644 --- a/tests/middleware.js +++ b/tests/middleware.js @@ -13,6 +13,10 @@ module.exports = function attachMiddleware(app) { recordingsDir: path.join(__dirname, 'recordings') }); + app.get('/assets/:name', (req, res) => { + res.sendFile(path.join(__dirname, 'assets', req.params.name)); + }); + app.use(bodyParser.json()); app.get('/echo', (req, res) => { diff --git a/yarn.lock b/yarn.lock index bec44a0d..c043b93a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13542,7 +13542,7 @@ to-array@0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/to-array/-/to-array-0.1.4.tgz#17e6c11f73dd4f3d74cda7a4ff3238e9ad9bf890" -to-arraybuffer@^1.0.0: +to-arraybuffer@^1.0.0, to-arraybuffer@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43"