Files
flipper/desktop/plugins/public/network/__tests__/chunks.node.tsx
Michel Weststrate 72e34bbd0d Parse request bodies eagerly
Summary:
Currently the network plugin was always storing the transfer format of our request/ response bodies: a base64 string. However, those are not searchable, and every formatter (and multiple can be invoked in a single view) was responsible for its own decompressing.

This diff changes parsing requests / responses into an accurate format: a decompressed, de-base64-ed utf8 string, or a Uint8array for binary data.

We will use this in the next diffs to do some more efficient searching

Reviewed By: passy

Differential Revision: D28200190

fbshipit-source-id: 33a71aeb7b340b58305e97fff4fa5ce472169b25
2021-05-06 04:27:59 -07:00

216 lines
5.3 KiB
TypeScript

/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
import {combineBase64Chunks} from '../chunks';
import {TestUtils} from 'flipper-plugin';
import * as NetworkPlugin from '../index';
import {assembleChunksIfResponseIsComplete} from '../chunks';
import path from 'path';
import {Base64} from 'js-base64';
import * as fs from 'fs';
import {promisify} from 'util';
const readFile = promisify(fs.readFile);
test('Test assembling base64 chunks', () => {
const message = 'wassup john?';
const chunks = message.match(/.{1,2}/g)?.map(btoa);
if (chunks === undefined) {
throw new Error('invalid chunks');
}
const output = combineBase64Chunks(chunks);
expect(Base64.decode(output)).toBe('wassup john?');
});
test('Reducer correctly adds initial chunk', () => {
const {instance, sendEvent} = TestUtils.startPlugin(NetworkPlugin);
expect(instance.partialResponses.get()).toEqual({});
sendEvent('partialResponse', {
id: '1',
timestamp: 123,
status: 200,
data: 'hello',
reason: 'nothing',
headers: [],
isMock: false,
insights: null,
index: 0,
totalChunks: 2,
});
expect(instance.partialResponses.get()['1']).toMatchInlineSnapshot(`
Object {
"followupChunks": Object {},
"initialResponse": Object {
"data": "hello",
"headers": Array [],
"id": "1",
"index": 0,
"insights": null,
"isMock": false,
"reason": "nothing",
"status": 200,
"timestamp": 123,
"totalChunks": 2,
},
}
`);
});
test('Reducer correctly adds followup chunk', () => {
const {instance, sendEvent} = TestUtils.startPlugin(NetworkPlugin);
expect(instance.partialResponses.get()).toEqual({});
sendEvent('partialResponse', {
id: '1',
totalChunks: 2,
index: 1,
data: 'hello',
});
expect(instance.partialResponses.get()['1']).toMatchInlineSnapshot(`
Object {
"followupChunks": Object {
"1": "hello",
},
}
`);
});
test('Reducer correctly combines initial response and followup chunk', () => {
const {instance, sendEvent} = TestUtils.startPlugin(NetworkPlugin);
sendEvent('newRequest', {
data: btoa('x'),
headers: [
{key: 'y', value: 'z'},
{
key: 'Content-Type',
value: 'text/plain',
},
],
id: '1',
method: 'GET',
timestamp: 0,
url: 'http://test.com',
});
sendEvent('partialResponse', {
data: 'aGVs',
headers: [{key: 'Content-Type', value: 'text/plain'}],
id: '1',
insights: null,
isMock: false,
reason: 'nothing',
status: 200,
timestamp: 123,
index: 0,
totalChunks: 2,
});
expect(instance.partialResponses.get()).toMatchInlineSnapshot(`
Object {
"1": Object {
"followupChunks": Object {},
"initialResponse": Object {
"data": "aGVs",
"headers": Array [
Object {
"key": "Content-Type",
"value": "text/plain",
},
],
"id": "1",
"index": 0,
"insights": null,
"isMock": false,
"reason": "nothing",
"status": 200,
"timestamp": 123,
"totalChunks": 2,
},
},
}
`);
expect(instance.requests.records()[0]).toMatchObject({
requestData: 'x',
requestHeaders: [
{key: 'y', value: 'z'},
{
key: 'Content-Type',
value: 'text/plain',
},
],
id: '1',
method: 'GET',
url: 'http://test.com',
domain: 'test.com/',
});
sendEvent('partialResponse', {
id: '1',
totalChunks: 2,
index: 1,
data: 'bG8=',
});
expect(instance.partialResponses.get()).toEqual({});
expect(instance.requests.records()[0]).toMatchObject({
domain: 'test.com/',
duration: 123,
id: '1',
insights: undefined,
method: 'GET',
reason: 'nothing',
requestData: 'x',
requestHeaders: [
{
key: 'y',
value: 'z',
},
{
key: 'Content-Type',
value: 'text/plain',
},
],
responseData: 'hello',
responseHeaders: [{key: 'Content-Type', value: 'text/plain'}],
responseIsMock: false,
responseLength: 5,
status: 200,
url: 'http://test.com',
});
});
async function readJsonFixture(filename: string) {
return JSON.parse(
await readFile(path.join(__dirname, 'fixtures', filename), 'utf-8'),
);
}
test('handle small binary payloads correctly', async () => {
const input = await readJsonFixture('partial_failing_example.json');
expect(() => {
// this used to throw
assembleChunksIfResponseIsComplete(input);
}).not.toThrow();
});
test('handle non binary payloads correcty', async () => {
const input = await readJsonFixture('partial_utf8_before.json');
const expected = await readJsonFixture('partial_utf8_after.json');
const response = assembleChunksIfResponseIsComplete(input);
expect(response).toEqual(expected);
});
test('handle binary payloads correcty', async () => {
const input = await readJsonFixture('partial_binary_before.json');
const expected = await readJsonFixture('partial_binary_after.json');
const response = assembleChunksIfResponseIsComplete(input);
expect(response).toEqual(expected);
});