Skip to content

Commit

Permalink
refactor: making the library async
Browse files Browse the repository at this point in the history
  • Loading branch information
erunion committed Sep 14, 2023
1 parent 487030f commit a080016
Show file tree
Hide file tree
Showing 6 changed files with 101 additions and 43 deletions.
10 changes: 8 additions & 2 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
{
"files.insertFinalNewline": false, // controlled by the .editorconfig at root since we can't map vscode settings directly to files (see: https://github.com/microsoft/vscode/issues/35350)
"editor.defaultFormatter": "esbenp.prettier-vscode"
"editor.codeActionsOnSave": {
"source.fixAll": true
},
"editor.defaultFormatter": "esbenp.prettier-vscode",

// controlled by the .editorconfig at root since we can't map vscode settings directly to files
// https://github.com/microsoft/vscode/issues/35350
"files.insertFinalNewline": false
}
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ const snippet = new HTTPSnippet({
url: 'httsp://httpbin.org/anything',
});

await snippet.init();

// generate Node.js: Native output
console.log(snippet.convert('node'));

Expand Down Expand Up @@ -102,6 +104,8 @@ const snippet = new HTTPSnippet({
url: 'https://httpbin.org/anything',
});

await snippet.init();

// generate Shell: cURL output
console.log(
snippet.convert('shell', 'curl', {
Expand Down
7 changes: 5 additions & 2 deletions src/fixtures/runCustomFixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,16 @@ export interface CustomFixture {

export const runCustomFixtures = ({ targetId, clientId, tests }: CustomFixture) => {
describe(`custom fixtures for ${targetId}:${clientId}`, () => {
tests.forEach(({ it: title, expected: fixtureFile, options, input: request }) => {
tests.forEach(async ({ it: title, expected: fixtureFile, options, input: request }) => {
const opts: HTTPSnippetOptions = {};
if (options.harIsAlreadyEncoded) {
opts.harIsAlreadyEncoded = options.harIsAlreadyEncoded;
}

const result = new HTTPSnippet(request, opts).convert(targetId, clientId, options);
const snippet = new HTTPSnippet(request, opts);
await snippet.init();

const result = snippet.convert(targetId, clientId, options);
const filePath = path.join(__dirname, '..', 'targets', targetId, clientId, 'fixtures', fixtureFile);
if (process.env.OVERWRITE_EVERYTHING) {
writeFileSync(filePath, String(result));
Expand Down
72 changes: 50 additions & 22 deletions src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,29 +10,40 @@ import short from './fixtures/requests/short.cjs';
import { HTTPSnippet } from './index.js';

describe('HTTPSnippet', () => {
it('should return false if no matching target', () => {
it('should return false if no matching target', async () => {
const snippet = new HTTPSnippet(short.log.entries[0].request as Request);
await snippet.init();

// @ts-expect-error intentionally incorrect
const result = snippet.convert(null);

expect(result).toBe(false);
});

it('should throw an error if you try to convert before initializing', () => {
const snippet = new HTTPSnippet(short.log.entries[0].request as Request);

expect(() => {
snippet.convert('node');
}).toThrow(new Error('The `.init()` method must be invoked before `.convert()`.'));
});

describe('repair malformed `postData`', () => {
it('should repair a HAR with an empty `postData` object', () => {
it('should repair a HAR with an empty `postData` object', async () => {
const snippet = new HTTPSnippet({
method: 'POST',
url: 'https://httpbin.org/anything',
postData: {},
} as Request);
await snippet.init();

const request = snippet.requests[0];
expect(request.postData).toStrictEqual({
mimeType: 'application/octet-stream',
});
});

it('should repair a HAR with a `postData` params object missing `mimeType`', () => {
it('should repair a HAR with a `postData` params object missing `mimeType`', async () => {
// @ts-expect-error Testing a malformed HAR case.
const snippet = new HTTPSnippet({
method: 'POST',
Expand All @@ -41,6 +52,7 @@ describe('HTTPSnippet', () => {
params: [],
},
} as Request);
await snippet.init();

const request = snippet.requests[0];
expect(request.postData).toStrictEqual({
Expand All @@ -49,14 +61,15 @@ describe('HTTPSnippet', () => {
});
});

it('should repair a HAR with a `postData` text object missing `mimeType`', () => {
it('should repair a HAR with a `postData` text object missing `mimeType`', async () => {
const snippet = new HTTPSnippet({
method: 'POST',
url: 'https://httpbin.org/anything',
postData: {
text: '',
},
} as Request);
await snippet.init();

const request = snippet.requests[0];
expect(request.postData).toStrictEqual({
Expand All @@ -66,7 +79,7 @@ describe('HTTPSnippet', () => {
});
});

it('should parse HAR file with multiple entries', () => {
it('should parse HAR file with multiple entries', async () => {
const snippet = new HTTPSnippet({
log: {
version: '1.2',
Expand All @@ -91,6 +104,8 @@ describe('HTTPSnippet', () => {
},
});

await snippet.init();

expect(snippet).toHaveProperty('requests');
expect(Array.isArray(snippet.requests)).toBeTruthy();
expect(snippet.requests).toHaveLength(2);
Expand Down Expand Up @@ -129,25 +144,29 @@ describe('HTTPSnippet', () => {
] as {
expected: string;
input: keyof typeof mimetypes;
}[])('mimetype conversion of $input to $output', ({ input, expected }) => {
}[])('mimetype conversion of $input to $output', async ({ input, expected }) => {
const snippet = new HTTPSnippet(mimetypes[input]);
const request = snippet.requests[0];
await snippet.init();

const request = snippet.requests[0];
expect(request.postData.mimeType).toStrictEqual(expected);
});
});

it('should set postData.text to empty string when postData.params is undefined in application/x-www-form-urlencoded', () => {
it('should set postData.text to empty string when postData.params is undefined in application/x-www-form-urlencoded', async () => {
const snippet = new HTTPSnippet(mimetypes['application/x-www-form-urlencoded']);
const request = snippet.requests[0];
await snippet.init();

const request = snippet.requests[0];
expect(request.postData.text).toBe('');
});

describe('requestExtras', () => {
describe('uriObj', () => {
it('should add uriObj', () => {
it('should add uriObj', async () => {
const snippet = new HTTPSnippet(query.log.entries[0].request as Request);
await snippet.init();

const request = snippet.requests[0];

expect(request.uriObj).toMatchObject({
Expand All @@ -170,35 +189,38 @@ describe('HTTPSnippet', () => {
});
});

it('should fix the `path` property of uriObj to match queryString', () => {
it('should fix the `path` property of uriObj to match queryString', async () => {
const snippet = new HTTPSnippet(query.log.entries[0].request as Request);
const request = snippet.requests[0];
await snippet.init();

const request = snippet.requests[0];
expect(request.uriObj.path).toBe('/anything?foo=bar&foo=baz&baz=abc&key=value');
});
});

describe('queryObj', () => {
it('should add queryObj', () => {
it('should add queryObj', async () => {
const snippet = new HTTPSnippet(query.log.entries[0].request as Request);
const request = snippet.requests[0];
await snippet.init();

const request = snippet.requests[0];
expect(request.queryObj).toMatchObject({ baz: 'abc', key: 'value', foo: ['bar', 'baz'] });
});
});

describe('headersObj', () => {
it('should add headersObj', () => {
it('should add headersObj', async () => {
const snippet = new HTTPSnippet(headers.log.entries[0].request as Request);
const request = snippet.requests[0];
await snippet.init();

const request = snippet.requests[0];
expect(request.headersObj).toMatchObject({
accept: 'application/json',
'x-foo': 'Bar',
});
});

it('should add headersObj to source object case insensitive when HTTP/1.0', () => {
it('should add headersObj to source object case insensitive when HTTP/1.0', async () => {
const snippet = new HTTPSnippet({
...headers.log.entries[0].request,
httpVersion: 'HTTP/1.1',
Expand All @@ -211,6 +233,8 @@ describe('HTTPSnippet', () => {
],
} as Request);

await snippet.init();

const request = snippet.requests[0];

expect(request.headersObj).toMatchObject({
Expand All @@ -220,7 +244,7 @@ describe('HTTPSnippet', () => {
});
});

it('should add headersObj to source object lowercased when HTTP/2.x', () => {
it('should add headersObj to source object lowercased when HTTP/2.x', async () => {
const snippet = new HTTPSnippet({
...headers.log.entries[0].request,
httpVersion: 'HTTP/2',
Expand All @@ -233,6 +257,8 @@ describe('HTTPSnippet', () => {
],
} as Request);

await snippet.init();

const request = snippet.requests[0];

expect(request.headersObj).toMatchObject({
Expand All @@ -244,19 +270,21 @@ describe('HTTPSnippet', () => {
});

describe('url', () => {
it('should modify the original url to strip query string', () => {
it('should modify the original url to strip query string', async () => {
const snippet = new HTTPSnippet(query.log.entries[0].request as Request);
const request = snippet.requests[0];
await snippet.init();

const request = snippet.requests[0];
expect(request.url).toBe('https://httpbin.org/anything');
});
});

describe('fullUrl', () => {
it('adds fullURL', () => {
it('adds fullURL', async () => {
const snippet = new HTTPSnippet(query.log.entries[0].request as Request);
const request = snippet.requests[0];
await snippet.init();

const request = snippet.requests[0];
expect(request.fullUrl).toBe('https://httpbin.org/anything?foo=bar&foo=baz&baz=abc&key=value');
});
});
Expand Down
35 changes: 24 additions & 11 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,12 +74,16 @@ const isHarEntry = (value: any): value is HarEntry =>
Array.isArray(value.log.entries);

export class HTTPSnippet {
initCalled = false;

entries: Entry[] = [];

requests: Request[] = [];

constructor(input: HarEntry | HarRequest, opts: HTTPSnippetOptions = {}) {
let entries: Entry[] = [];
options: HTTPSnippetOptions = {};

const options = {
constructor(input: HarEntry | HarRequest, opts: HTTPSnippetOptions = {}) {
this.options = {
harIsAlreadyEncoded: false,
...opts,
};
Expand All @@ -89,16 +93,19 @@ export class HTTPSnippet {

// is it har?
if (isHarEntry(input)) {
entries = input.log.entries;
this.entries = input.log.entries;
} else {
entries = [
this.entries = [
{
request: input,
},
];
}
}

entries.forEach(({ request }) => {
init() {
this.initCalled = true;
this.entries.forEach(({ request }) => {
// add optional properties to make validation successful
const req = {
bodySize: 0,
Expand All @@ -118,11 +125,13 @@ export class HTTPSnippet {
req.postData.mimeType = 'application/octet-stream';
}

this.requests.push(this.prepare(req as HarRequest, options));
this.requests.push(this.prepare(req as HarRequest, this.options));
});

return this;
}

prepare = (harRequest: HarRequest, options: HTTPSnippetOptions) => {
prepare(harRequest: HarRequest, options: HTTPSnippetOptions) {
const request: Request = {
...harRequest,
fullUrl: '',
Expand Down Expand Up @@ -334,9 +343,13 @@ export class HTTPSnippet {
url,
uriObj,
};
};
}

convert(targetId: TargetId, clientId?: ClientId, options?: any) {
if (!this.initCalled) {
throw new Error('The `.init()` method must be invoked before `.convert()`.');
}

convert = (targetId: TargetId, clientId?: ClientId, options?: any) => {
if (!options && clientId) {
options = clientId;
}
Expand All @@ -349,5 +362,5 @@ export class HTTPSnippet {
const { convert } = target.clientsById[clientId || target.info.default];
const results = this.requests.map(request => convert(request, options));
return results.length === 1 ? results[0] : results;
};
}
}
16 changes: 10 additions & 6 deletions src/targets/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ availableTargets()
describe(`${title} Request Validation`, () => {
clients.filter(testFilter('key', clientFilter)).forEach(({ key: clientId, extname: fixtureExtension }) => {
describe(`${clientId}`, () => {
fixtures.filter(testFilter(0, fixtureFilter)).forEach(([fixture, request]) => {
fixtures.filter(testFilter(0, fixtureFilter)).forEach(async ([fixture, request]) => {
const expectedPath = path.join(
'src',
'targets',
Expand All @@ -77,8 +77,10 @@ availableTargets()
}

const expected = readFileSync(expectedPath).toString();
const { convert } = new HTTPSnippet(request, options);
const result = convert(targetId, clientId); //?
const snippet = new HTTPSnippet(request, options);
await snippet.init();

const result = snippet.convert(targetId, clientId);

if (OVERWRITE_EVERYTHING && result) {
writeFileSync(expectedPath, String(result));
Expand Down Expand Up @@ -286,7 +288,7 @@ describe('addTargetClient', () => {
delete targets.node.clientsById.custom;
});

it('should add a new custom target', () => {
it('should add a new custom target', async () => {
const customClient: Client = {
info: {
key: 'custom',
Expand All @@ -302,8 +304,10 @@ describe('addTargetClient', () => {

addTargetClient('node', customClient);

const { convert } = new HTTPSnippet(short.log.entries[0].request as Request, {});
const result = convert('node', 'custom');
const snippet = new HTTPSnippet(short.log.entries[0].request as Request, {});
await snippet.init();

const result = snippet.convert('node', 'custom');

expect(result).toBe('This was generated from a custom client.');
});
Expand Down

0 comments on commit a080016

Please sign in to comment.