If we take the getNextLocation
function that we created previously, and put this into our implementation, does this solve the Microsoft redirect issue?
Well, rather than write a test for that, let’s just check it out by hacking it in to the current code. If it does work, we can think of a better way to solve the problem. And if it doesn’t, then we didn’t waste too much time trying it out.
try {
const result = await fetcher(href);
const guessedNextLocation = getNextLocation(href, result.headers);
console.log(`guessedNextLocation`, guessedNextLocation);
if (guessedNextLocation) {
const updatedRequests = [...requests, { ...result, redirected: true }];
return await visit(guessedNextLocation, updatedRequests);
}
return [
...requests,
{
...result,
redirected: false,
},
];
} catch (e) {
Code language: TypeScript (typescript)
Which does seem to work:
I’m still not thrilled about needing to figure out the next location inside the visit
function. I think I would prefer to move that inside the fetcher
.
This should be fairly easy to do, as we have all the information in the fetcher
already. Is it easy to update the tests though?
import { fetcher } from "./fetcher";
const fakeUrl = "http://some.fake.url";
describe("fetcher", () => {
afterEach(() => {
jest.resetAllMocks();
});
[
{
description: "empty headers",
givenHeaders: new Headers(),
expectedHeaders: {},
},
{
description: "with headers",
givenHeaders: new Headers({ some: "data" }),
expectedHeaders: { some: "data" },
},
].forEach(({ description, givenHeaders, expectedHeaders }) => {
test(`should call the native fetch function in the expected manner: ${description}`, async () => {
const fetchSpy = jest.spyOn(global, "fetch").mockResolvedValueOnce({
url: fakeUrl,
status: 200,
statusText: "something",
ok: true,
headers: givenHeaders,
} as unknown as Response);
const result = await fetcher(fakeUrl);
expect(fetchSpy).toHaveBeenCalledTimes(1);
expect(fetchSpy).toHaveBeenCalledWith(fakeUrl, {
redirect: "manual",
});
expect(result).toEqual({
ok: true,
status: 200,
statusText: "something",
url: "http://some.fake.url",
headers: expectedHeaders,
});
});
});
});
Code language: TypeScript (typescript)
As best I can think, all we need to do here is to add the nextLocation
key / value pair on to the expected result.
Those two tests don’t take a location
header into account. I guess a new test is the best option:
test("should guess the next location if applicable", async () => {
const fakeLocationPath = "/a/b/c";
jest.spyOn(global, "fetch").mockResolvedValueOnce({
url: fakeUrl,
status: 200,
statusText: "something",
ok: true,
headers: new Headers({ location: fakeLocationPath }),
} as unknown as Response);
const result = await fetcher(fakeUrl);
expect(result).toEqual({
ok: true,
status: 200,
statusText: "something",
url: "http://some.fake.url",
headers: { location: fakeLocationPath },
nextLocation: `${fakeUrl}${fakeLocationPath}`,
});
});
Code language: TypeScript (typescript)
And the implementation:
import { FetcherResponse } from "./types";
import { getNextLocation } from "../get-next-location";
export const fetcher = async (href: string): Promise<FetcherResponse> => {
const { url, status, statusText, ok, headers } = await fetch(href, {
redirect: "manual",
});
const headersObject = Object.fromEntries(headers);
const nextLocation = getNextLocation(url, headersObject);
return { url, status, statusText, ok, headers: headersObject, nextLocation };
};
Code language: TypeScript (typescript)
The nextLocation
also needs adding to the FetcherResponse
type:
export type FetcherResponse = {
url: string;
status: number;
statusText: string;
ok: boolean;
headers: Record<string, string>;
nextLocation: string | null;
};
Code language: TypeScript (typescript)
This type addition causes some chaos in the tests for visit
and fetcher
. I’d suggest checking out the code repo over on GitHub if following along and unable to fix for yourself.
No behaviour changes here, just a little internal tidy up.
There is still the outstanding issue of our function occasionally hanging when called. Let’s fix that next.