add simpleProxyFetcher and add fetcher unit tests
This commit is contained in:
parent
a31e05adfb
commit
aa24946d6f
|
@ -25,11 +25,8 @@ Todos:
|
||||||
- does it return as expected?
|
- does it return as expected?
|
||||||
- does it error when invalid id?
|
- does it error when invalid id?
|
||||||
- makeStandardFetcher()
|
- makeStandardFetcher()
|
||||||
- do all parameters get passed to real fetch as expected?
|
|
||||||
- does serialisation work as expected? (formdata + json + string)
|
- does serialisation work as expected? (formdata + json + string)
|
||||||
- does json responses get automatically parsed?
|
|
||||||
- add all real providers
|
- add all real providers
|
||||||
- fetcher for MW's simple-proxy
|
|
||||||
- make default fetcher maker thing work with both undici and node-fetch
|
- make default fetcher maker thing work with both undici and node-fetch
|
||||||
|
|
||||||
Future todos:
|
Future todos:
|
||||||
|
|
|
@ -0,0 +1,125 @@
|
||||||
|
import { makeSimpleProxyFetcher } from "@/fetchers/simpleProxy";
|
||||||
|
import { DefaultedFetcherOptions, FetcherOptions } from "@/fetchers/types";
|
||||||
|
import { Headers } from "node-fetch";
|
||||||
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
|
describe("makeSimpleProxyFetcher()", () => {
|
||||||
|
const fetch = vi.fn();
|
||||||
|
const fetcher = makeSimpleProxyFetcher("https://example.com/proxy", fetch);
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vi.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
function setResult(type: "text" | "json", value: any) {
|
||||||
|
if (type === 'text') return fetch.mockResolvedValueOnce({
|
||||||
|
headers: new Headers({
|
||||||
|
"content-type": "text/plain",
|
||||||
|
}),
|
||||||
|
text() {
|
||||||
|
return Promise.resolve(value);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (type === 'json') return fetch.mockResolvedValueOnce({
|
||||||
|
headers: new Headers({
|
||||||
|
"content-type": "application/json",
|
||||||
|
}),
|
||||||
|
json() {
|
||||||
|
return Promise.resolve(value);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function expectFetchCall(ops: { inputUrl: string, input: DefaultedFetcherOptions, outputUrl?: string, output: any, outputBody: any }) {
|
||||||
|
expect(fetcher(ops.inputUrl, ops.input)).resolves.toEqual(ops.outputBody);
|
||||||
|
expect(fetch).toBeCalledWith(ops.outputUrl ?? ops.inputUrl, ops.output);
|
||||||
|
vi.clearAllMocks();
|
||||||
|
}
|
||||||
|
|
||||||
|
it('should pass options through', () => {
|
||||||
|
setResult("text", "hello world");
|
||||||
|
expectFetchCall({
|
||||||
|
inputUrl: "https://google.com",
|
||||||
|
input: {
|
||||||
|
method: "GET",
|
||||||
|
query: {},
|
||||||
|
headers: {
|
||||||
|
"X-Hello": "world",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
outputUrl: `https://example.com/proxy?destination=${encodeURIComponent('https://google.com/')}`,
|
||||||
|
output: {
|
||||||
|
method: "GET",
|
||||||
|
headers: {
|
||||||
|
"X-Hello": "world",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
outputBody: "hello world"
|
||||||
|
})
|
||||||
|
setResult("text", "hello world");
|
||||||
|
expectFetchCall({
|
||||||
|
inputUrl: "https://google.com",
|
||||||
|
input: {
|
||||||
|
method: "GET",
|
||||||
|
headers: {},
|
||||||
|
query: {
|
||||||
|
"a": 'b',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
outputUrl: `https://example.com/proxy?destination=${encodeURIComponent('https://google.com/?a=b')}`,
|
||||||
|
output: {
|
||||||
|
method: "GET",
|
||||||
|
headers: {},
|
||||||
|
},
|
||||||
|
outputBody: "hello world"
|
||||||
|
})
|
||||||
|
setResult("text", "hello world");
|
||||||
|
expectFetchCall({
|
||||||
|
inputUrl: "https://google.com",
|
||||||
|
input: {
|
||||||
|
method: "GET",
|
||||||
|
query: {},
|
||||||
|
headers: {},
|
||||||
|
},
|
||||||
|
outputUrl: `https://example.com/proxy?destination=${encodeURIComponent('https://google.com/')}`,
|
||||||
|
output: {
|
||||||
|
method: "GET",
|
||||||
|
headers: {},
|
||||||
|
},
|
||||||
|
outputBody: "hello world"
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse response correctly', () => {
|
||||||
|
setResult("text", "hello world");
|
||||||
|
expectFetchCall({
|
||||||
|
inputUrl: "https://google.com/",
|
||||||
|
input: {
|
||||||
|
method: "POST",
|
||||||
|
query: {},
|
||||||
|
headers: {},
|
||||||
|
},
|
||||||
|
outputUrl: `https://example.com/proxy?destination=${encodeURIComponent('https://google.com/')}`,
|
||||||
|
output: {
|
||||||
|
method: "POST",
|
||||||
|
headers: {},
|
||||||
|
},
|
||||||
|
outputBody: "hello world"
|
||||||
|
})
|
||||||
|
setResult("json", { hello: 42 });
|
||||||
|
expectFetchCall({
|
||||||
|
inputUrl: "https://google.com/",
|
||||||
|
input: {
|
||||||
|
method: "POST",
|
||||||
|
query: {},
|
||||||
|
headers: {},
|
||||||
|
},
|
||||||
|
outputUrl: `https://example.com/proxy?destination=${encodeURIComponent('https://google.com/')}`,
|
||||||
|
output: {
|
||||||
|
method: "POST",
|
||||||
|
headers: {},
|
||||||
|
},
|
||||||
|
outputBody: { hello: 42 }
|
||||||
|
})
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,125 @@
|
||||||
|
import { makeStandardFetcher } from "@/fetchers/standardFetch";
|
||||||
|
import { DefaultedFetcherOptions } from "@/fetchers/types";
|
||||||
|
import { Headers } from "node-fetch";
|
||||||
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
|
describe("makeStandardFetcher()", () => {
|
||||||
|
const fetch = vi.fn();
|
||||||
|
const fetcher = makeStandardFetcher(fetch);
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vi.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
function setResult(type: "text" | "json", value: any) {
|
||||||
|
if (type === 'text') return fetch.mockResolvedValueOnce({
|
||||||
|
headers: new Headers({
|
||||||
|
"content-type": "text/plain",
|
||||||
|
}),
|
||||||
|
text() {
|
||||||
|
return Promise.resolve(value);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (type === 'json') return fetch.mockResolvedValueOnce({
|
||||||
|
headers: new Headers({
|
||||||
|
"content-type": "application/json",
|
||||||
|
}),
|
||||||
|
json() {
|
||||||
|
return Promise.resolve(value);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function expectFetchCall(ops: { inputUrl: string, input: DefaultedFetcherOptions, outputUrl?: string, output: any, outputBody: any }) {
|
||||||
|
expect(fetcher(ops.inputUrl, ops.input)).resolves.toEqual(ops.outputBody);
|
||||||
|
expect(fetch).toBeCalledWith(ops.outputUrl ?? ops.inputUrl, ops.output);
|
||||||
|
vi.clearAllMocks();
|
||||||
|
}
|
||||||
|
|
||||||
|
it('should pass options through', () => {
|
||||||
|
setResult("text", "hello world");
|
||||||
|
expectFetchCall({
|
||||||
|
inputUrl: "https://google.com",
|
||||||
|
input: {
|
||||||
|
method: "GET",
|
||||||
|
query: {},
|
||||||
|
headers: {
|
||||||
|
"X-Hello": "world",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
outputUrl: "https://google.com/",
|
||||||
|
output: {
|
||||||
|
method: "GET",
|
||||||
|
headers: {
|
||||||
|
"X-Hello": "world",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
outputBody: "hello world"
|
||||||
|
})
|
||||||
|
setResult("text", "hello world");
|
||||||
|
expectFetchCall({
|
||||||
|
inputUrl: "https://google.com",
|
||||||
|
input: {
|
||||||
|
method: "GET",
|
||||||
|
headers: {},
|
||||||
|
query: {
|
||||||
|
"a": 'b',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
outputUrl: "https://google.com/?a=b",
|
||||||
|
output: {
|
||||||
|
method: "GET",
|
||||||
|
headers: {},
|
||||||
|
},
|
||||||
|
outputBody: "hello world"
|
||||||
|
})
|
||||||
|
setResult("text", "hello world");
|
||||||
|
expectFetchCall({
|
||||||
|
inputUrl: "https://google.com",
|
||||||
|
input: {
|
||||||
|
query: {},
|
||||||
|
headers: {},
|
||||||
|
method: "GET"
|
||||||
|
},
|
||||||
|
outputUrl: "https://google.com/",
|
||||||
|
output: {
|
||||||
|
method: "GET",
|
||||||
|
headers: {},
|
||||||
|
},
|
||||||
|
outputBody: "hello world"
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse response correctly', () => {
|
||||||
|
setResult("text", "hello world");
|
||||||
|
expectFetchCall({
|
||||||
|
inputUrl: "https://google.com/",
|
||||||
|
input: {
|
||||||
|
query: {},
|
||||||
|
headers: {},
|
||||||
|
method: "POST"
|
||||||
|
},
|
||||||
|
outputUrl: "https://google.com/",
|
||||||
|
output: {
|
||||||
|
method: "POST",
|
||||||
|
headers: {},
|
||||||
|
},
|
||||||
|
outputBody: "hello world"
|
||||||
|
})
|
||||||
|
setResult("json", { hello: 42 });
|
||||||
|
expectFetchCall({
|
||||||
|
inputUrl: "https://google.com/",
|
||||||
|
input: {
|
||||||
|
query: {},
|
||||||
|
headers: {},
|
||||||
|
method: "POST"
|
||||||
|
},
|
||||||
|
outputUrl: "https://google.com/",
|
||||||
|
output: {
|
||||||
|
method: "POST",
|
||||||
|
headers: {},
|
||||||
|
},
|
||||||
|
outputBody: { hello: 42 }
|
||||||
|
})
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,35 @@
|
||||||
|
import fetch from 'node-fetch';
|
||||||
|
|
||||||
|
import { makeFullUrl } from '@/fetchers/common';
|
||||||
|
import { makeStandardFetcher } from '@/fetchers/standardFetch';
|
||||||
|
import { Fetcher } from '@/fetchers/types';
|
||||||
|
|
||||||
|
const headerMap: Record<string, string> = {
|
||||||
|
cookie: 'X-Cookie',
|
||||||
|
referer: 'X-Referer',
|
||||||
|
origin: 'X-Origin',
|
||||||
|
};
|
||||||
|
|
||||||
|
export function makeSimpleProxyFetcher(proxyUrl: string, f: typeof fetch): Fetcher {
|
||||||
|
const fetcher = makeStandardFetcher(f);
|
||||||
|
const proxiedFetch: Fetcher = async (url, ops) => {
|
||||||
|
const fullUrl = makeFullUrl(url, ops);
|
||||||
|
|
||||||
|
const headerEntries = Object.entries(ops.headers).map((entry) => {
|
||||||
|
const key = entry[0].toLowerCase();
|
||||||
|
if (headerMap[key]) return [headerMap[key], entry[1]];
|
||||||
|
return entry;
|
||||||
|
});
|
||||||
|
|
||||||
|
return fetcher(proxyUrl, {
|
||||||
|
...ops,
|
||||||
|
query: {
|
||||||
|
destination: fullUrl,
|
||||||
|
},
|
||||||
|
headers: Object.fromEntries(headerEntries),
|
||||||
|
baseUrl: undefined,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return proxiedFetch;
|
||||||
|
}
|
|
@ -14,3 +14,4 @@ export type {
|
||||||
export { NotFoundError } from '@/utils/errors';
|
export { NotFoundError } from '@/utils/errors';
|
||||||
export { makeProviders } from '@/main/builder';
|
export { makeProviders } from '@/main/builder';
|
||||||
export { makeStandardFetcher } from '@/fetchers/standardFetch';
|
export { makeStandardFetcher } from '@/fetchers/standardFetch';
|
||||||
|
export { makeSimpleProxyFetcher } from '@/fetchers/simpleProxy';
|
||||||
|
|
Loading…
Reference in New Issue