Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Fetch Adapter v2 #5

Draft
wants to merge 69 commits into
base: v1.x
Choose a base branch
from
Draft
Changes from 1 commit
Commits
Show all changes
69 commits
Select commit Hold shift + click to select a range
ab6a697
feat: add `fetch` adapter with cross-platform support
sgammon Nov 14, 2022
728dc27
fix: add polyfill for `AbortController` on Node 14
sgammon Oct 17, 2022
5cea891
chore: add web streams polyfill
sgammon Nov 14, 2022
c9427fc
chore: add `whatwg-fetch` polyfill
sgammon Dec 16, 2022
0f36532
feat(fetch): continue with implementation of Fetch adapter + API
sgammon Dec 17, 2022
3624b1f
chore: add karma-coverage for instrumentation
sgammon Dec 17, 2022
ab9b529
feat(fetch): implement mock `fetch` for testing
sgammon Dec 17, 2022
2173ebd
feat(fetch): add browser-side test suite
sgammon Dec 17, 2022
6a5c6c7
chore: ignore nyc-output / .husky
sgammon Dec 17, 2022
652ec27
chore: add `FetchAdapter` to ESM export
sgammon Dec 17, 2022
475b418
feat(fetch): update typescript typings with fetch types
sgammon Dec 17, 2022
157c653
feat(fetch): add new `generic` library target (ESM/pure JS)
sgammon Dec 17, 2022
4dfc7a9
feat(fetch): implement structure for abstract fetch testsuite
sgammon Dec 17, 2022
a968631
feat(fetch): add Deno benchmark and example
sgammon Dec 17, 2022
db1de9b
feat(fetch): add browser example
sgammon Dec 17, 2022
4e04f76
feat(fetch): add minimal browser samples
sgammon Dec 17, 2022
e8d6f3e
feat(fetch): add simple Node fetch sample
sgammon Dec 17, 2022
328220a
feat(fetch): add CloudFlare Workers sample
sgammon Dec 17, 2022
e1a042d
fix: build matrix, initial ci config for deno testing
sgammon Dec 17, 2022
6637938
fix: drop default fetch cache setting
sgammon Dec 17, 2022
2d6d78d
fix: browser example fixes
sgammon Dec 17, 2022
21f41e8
feat(fetch): platform-driven default adapters for each build
sgammon Dec 17, 2022
be92d9d
fix: copy new correct typings for generic bundle
sgammon Dec 17, 2022
41cd5e1
feat(fetch): tools to analyze browser/generic bundle sizes
sgammon Dec 17, 2022
d50c5a5
fix: tweaks to Node sample for fetch
sgammon Dec 17, 2022
3347dad
feat(fetch): construction on workers sample for axios/fetch
sgammon Dec 17, 2022
b348686
chore: test against Node 19
sgammon Dec 17, 2022
71749db
fix: labs CI matrix
sgammon Dec 17, 2022
dce9e02
feat(fetch): platform-specific defaults for `fetchOptions`
sgammon Dec 18, 2022
256195b
fix(fetch): fully mockable responses for fetch testing
sgammon Dec 18, 2022
b2d419c
fix: tweaks to fetch examples
sgammon Dec 18, 2022
1a6e074
feat(fetch): fetch example improvements + READMEs (browser, deno)
sgammon Dec 18, 2022
690be1c
feat(fetch): only ship .d.cts with non-modern targets
sgammon Dec 18, 2022
20b5633
feat(fetch): add explicit `axios/generic` bundle export
sgammon Dec 18, 2022
272f066
feat(fetch): variants and README for node fetch examples
sgammon Dec 18, 2022
58a2fc4
feat(fetch): better debug logging, assertions, support for `Blob`
sgammon Dec 18, 2022
503c724
feat(fetch): allow importing specific files for generic bundle
sgammon Dec 18, 2022
683469e
feat(fetch): document new fetch config in main README
sgammon Dec 18, 2022
100d31b
fix(fetch): refactor and cleanup response handler selection
sgammon Dec 18, 2022
3927308
fix(fetch): fixes for Fetch adapter in CloudFlare Workers env
sgammon Dec 18, 2022
0bc7dde
feat(fetch): sample for CloudFlare Workers
sgammon Dec 18, 2022
ae2ebe7
chore: add doc for workers sample
sgammon Dec 18, 2022
6e40e17
chore: updates to main README docs
sgammon Dec 18, 2022
9b8692c
feat(fetch): pure-ESM web testing
sgammon Dec 18, 2022
ce16244
chore: add bundle sizes for generic entrypoint
sgammon Dec 18, 2022
faede9b
chore: additional bundle size testing
sgammon Dec 18, 2022
da2308b
feat: export utils as individual symbols
sgammon Dec 18, 2022
2b9bed3
fix(fetch): avoid re-defining any variables named 'fetch'
sgammon Dec 18, 2022
f375b92
feat(fetch): treeshaking and terser tweaks, optimize license
sgammon Dec 18, 2022
4b1c994
fix: don't require Content-Type for body
sgammon Dec 18, 2022
181ebe9
feat(fetch): improvements to fetch typings
sgammon Dec 19, 2022
9bbd0b0
chore: cleanup abstract fetch testsuite
sgammon Dec 19, 2022
1ffc76e
fix: keep console debug statements during beta
sgammon Dec 19, 2022
e7d33a3
fix: 'modern' export profile types
sgammon Dec 19, 2022
eed4e5e
chore: smaller bundle size limits
sgammon Dec 19, 2022
b40c7c6
fix: handler selection with no `Content-Type` present
sgammon Dec 19, 2022
e0fddb0
fix: errors with previous typing updates
sgammon Dec 19, 2022
cdc1b3c
chore: drop unused webpack config
sgammon Dec 19, 2022
b1e4a3e
chore: package spec cleanup
sgammon Dec 19, 2022
fcb5351
chore: setup CI testing for Bun
sgammon Dec 19, 2022
de21e6d
fix: polynomial-capable regex in combineURLs helper
sgammon Dec 19, 2022
3fcd8cc
fix: pass relative URLs directly to the underlying fetcher
sgammon Dec 19, 2022
80d24e9
test: ESM API spec
sgammon Dec 19, 2022
a2d11ed
chore: cleanup abstract fetch testsuite
sgammon Dec 19, 2022
c6fa931
fix: disable protocol-relative URL testing for combineURLs (temp)
sgammon Dec 19, 2022
135495a
fix: drop missing test
sgammon Dec 19, 2022
ccfb365
fix: allow version tag in user-agent test
sgammon Sep 14, 2023
f7e700f
chore: version bump for `fetch` testing → `1.5.0-fetch`
sgammon Sep 14, 2023
9b315e1
chore: lock packages, rebuild dist
sgammon Sep 14, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
fix(fetch): fully mockable responses for fetch testing
sgammon committed Sep 14, 2023
commit 256195b735395f6c0d71c42e84183d02808686d4
6 changes: 6 additions & 0 deletions test/fetch/deno/fetch.deno.spec.js
Original file line number Diff line number Diff line change
@@ -11,6 +11,7 @@ import helpers, {
} from './helpers.js';

import {
assert,
assertEquals,
assertExists,
} from "https://deno.land/std@0.168.0/testing/asserts.ts";
@@ -37,6 +38,10 @@ async function testCase(ctx, test, label, operation) {
return await test.step(label, operation);
}

function doAssert(ctx, ...args) {
return assert(...args);
}

function doAssertEquals(ctx, ...args) {
return assertEquals(...args);
}
@@ -55,6 +60,7 @@ fetchTest.setupFetchTest(
)(
Deno.test,
testCase,
doAssert,
doAssertEquals,
doAssertExists,
(ctx, op) => {
40 changes: 40 additions & 0 deletions test/fetch/fetch_abstract.js
Original file line number Diff line number Diff line change
@@ -9,6 +9,7 @@ const setupFetchTest = (
) => (
describe,
testCase,
assert,
assertEquals,
assertExists,
testFactory = null,
@@ -88,6 +89,45 @@ const setupFetchTest = (
'https://stubbed_domain.local:1/foo'
);
}));

await testCase(this, t, "should support fetching JSON", withMockFetch(async function () {
const response = await axios(fetchConfigurator({
url: new URL('https://httpbin.org/json'),
responseType: 'json'
}));

assertExists(response, "should not get `null` response from axios fetch");

assertEquals(
this,
response.status,
200,
)
assertEquals(
this,
response.data.slideshow.title,
"Sample Slide Show",
);
}));

await testCase(this, t, "should support fetching text", withMockFetch(async function () {
const response = await axios(fetchConfigurator({
url: new URL('https://httpbin.org/html'),
responseType: 'text'
}));

assertExists(response, "should not get `null` response from axios fetch");

assertEquals(
this,
response.status,
200,
)
assert(
this,
response.data.includes("Herman Melville"),
);
}));
}));

if (typeof afterEach === 'function') {
121 changes: 113 additions & 8 deletions test/fetch/fetch_helpers.js
Original file line number Diff line number Diff line change
@@ -2,6 +2,15 @@
let fetchStubbed = false;
let fetchQueue = [];
let cachedGlobalFetch = null;
const fetchDebugLogging = true;

function createHeaders(fromObject) {
const h = new Headers();
Object.keys(fromObject).forEach((key) => {
h.append(key, fromObject[key]);
});
return h;
}

const defaultMockResponseHeaders = {
'Content-Type': 'test/mock',
@@ -10,26 +19,90 @@ const defaultMockResponseHeaders = {
const defaultMockResponseInfo = {
status: 200,
statusText: 'OK',
headers: new Headers(defaultMockResponseHeaders),
body: null,
};

const defaultFetchOptions = {
mock: defaultMockResponseInfo
};

function createResponse(config, statusCode, statusMessage, headers, body) {
return new Response(body || null, Object.assign({}, defaultMockResponseInfo,{
function createResponse(config, statusCode, statusMessage, headers, bodyGenerator, contentTypeIfKnown) {
const body = (bodyGenerator ? bodyGenerator() : null);
if (fetchDebugLogging) {
console.log("[axios:fetch.mock] mock has body: ", !!body);
}

const responseInfo = {
status: statusCode || 200,
statusText: statusMessage || 'OK',
headers: headers || new Headers(defaultMockResponseHeaders),
}));
headers: Object.assign({}, defaultMockResponseHeaders, {
'Content-Type': contentTypeIfKnown || 'test/mock',
'Content-Length': body ? body.length : undefined,
}, headers || {}),
};
if (contentTypeIfKnown) {
responseInfo.headers['Content-Type'] = contentTypeIfKnown;
}
if (fetchDebugLogging) {
console.log("[axios:fetch.mock] mock response info: ", JSON.stringify(responseInfo));
}

return new Response(body, Object.assign({}, defaultMockResponseInfo, responseInfo));
}

function fakeJsonResponse() {
return JSON.stringify({
slideshow: {
author: "Yours Truly",
date: "date of publication",
slides: [
{
title: "Wake up to WonderWidgets!",
type: "all"
},
{
items: [
"Why <em>WonderWidgets</em> are great",
"Who <em>buys</em> WonderWidgets"
],
title: "Overview",
type: "all"
}
],
title: "Sample Slide Show"
}
});
}

function fakeTextResponse() {
return (
"<!DOCTYPE html>\n" +
"<html>\n" +
" <head>\n" +
" </head>\n" +
" <body>\n" +
" <h1>Herman Melville - Moby-Dick</h1>\n" +
"\n" +
" <div>\n" +
" <p>\n" +
" Availing himself of the mild, summer-cool weather that now reigned in these latitudes...\n" +
" </p>\n" +
" </div>\n" +
" </body>\n" +
"</html>" +
""
);
}

function mockFetch(fetchTarget) {
function mockFetch(input, opts) {
if (fetchDebugLogging) {
console.log("[axios:fetch.mock] mock fetch call: ", input, JSON.stringify(opts || {}));
}
const options = opts || defaultFetchOptions;
if (!options.mock) {
if (fetchDebugLogging) {
console.log("[axios:fetch.mock] mocking is not active");
}
return fetchTarget(input, options);
}
fetchQueue.push({
@@ -38,14 +111,46 @@ function mockFetch(fetchTarget) {
})
return new Promise(function (resolve, reject) {
if (options.mock && options.mock.error) {
if (fetchDebugLogging) {
console.log("[axios:fetch.mock] mock injected error", options.mock.error);
}

// if the mock tells us to fake a rejection, do that, with the provided error.
reject(options.mock.error);
} else {
let resolvedUrl;
if (typeof input === 'string') {
resolvedUrl = input;
} else if (input instanceof URL) {
resolvedUrl = input.toString();
} else if (input instanceof Request) {
resolvedUrl = input.url;
}
let responseGenerator = null;
let knownContentType = null;
if (resolvedUrl === 'https://httpbin.org/json') {
// fake a JSON response
responseGenerator = fakeJsonResponse;
knownContentType = 'application/json';
} else if (resolvedUrl === 'https://httpbin.org/html') {
// fake a text response
responseGenerator = fakeTextResponse;
knownContentType = 'text/html';
}
if (fetchDebugLogging) {
console.log("[axios:fetch.mock] resolving mock URL: ", resolvedUrl);
console.log("[axios:fetch.mock] resolving with mock content-type: ", knownContentType);
}

resolve(createResponse(
options,
options.mock ? options.mock.statusCode : 200,
options.mock ? options.mock.statusMessage : 'OK',
options.mock ? options.mock.headers : defaultMockResponseHeaders,
options.mock ? options.mock.body : null,
options.mock?.response ? (options.mock.response.headers || null) : null,
options.mock?.response ? (
responseGenerator || (() => options.mock?.response.body)
) : responseGenerator,
knownContentType,
));
}
});
5 changes: 5 additions & 0 deletions test/specs/fetch.spec.js
Original file line number Diff line number Diff line change
@@ -33,6 +33,10 @@ function runFetchTestsuite() {
})
}

function doAssert(ctx, condition, message) {
expect.bind(ctx)(condition).toBeTruthy(message);
}

function expectEqual(ctx, actual, expected, message) {
expect.bind(ctx)(actual).toEqual(expected, message);
}
@@ -80,6 +84,7 @@ function runFetchTestsuite() {
)(
describer,
testCase,
doAssert,
expectEqual,
expectDefined,
testFactory,