Refactor and improve external URL checks
- Move external URL checks to its own module under `tests/`. This separates them from integration test, addressing long runs and frequent failures that led to ignoring test results. - Move `check-desktop-runtime-errors` to `tests/checks` to keep all test-related checks into one directory. - Replace `ts-node` with `vite` for running `check-desktop-runtime-errors` to maintain a consistent execution environment across checks. - Implement a timeout for each fetch call. - Be nice to external sources, wait 5 seconds before sending another request to an URL under same domain. This solves rate-limiting issues. - Instead of running test on every push/pull request, run them only weekly. - Do not run tests on each commit/PR but only scheduled (weekly) to minimize noise. - Fix URLs are not captured correctly inside backticks or parenthesis.
This commit is contained in:
107
tests/checks/external-urls/StatusChecker/FetchFollow.ts
Normal file
107
tests/checks/external-urls/StatusChecker/FetchFollow.ts
Normal file
@@ -0,0 +1,107 @@
|
||||
import { fetchWithTimeout } from './FetchWithTimeout';
|
||||
|
||||
export function fetchFollow(
|
||||
url: string,
|
||||
timeoutInMs: number,
|
||||
fetchOptions: RequestInit,
|
||||
followOptions: IFollowOptions,
|
||||
): Promise<Response> {
|
||||
followOptions = { ...DefaultOptions, ...followOptions };
|
||||
if (followRedirects(followOptions)) {
|
||||
return fetchWithTimeout(url, timeoutInMs, fetchOptions);
|
||||
}
|
||||
fetchOptions = { ...fetchOptions, redirect: 'manual' /* handled manually */ };
|
||||
const cookies = new CookieStorage(followOptions.enableCookies);
|
||||
return followRecursivelyWithCookies(
|
||||
url,
|
||||
timeoutInMs,
|
||||
fetchOptions,
|
||||
followOptions.maximumRedirectFollowDepth,
|
||||
cookies,
|
||||
);
|
||||
}
|
||||
|
||||
export interface IFollowOptions {
|
||||
followRedirects?: boolean;
|
||||
maximumRedirectFollowDepth?: number;
|
||||
enableCookies?: boolean;
|
||||
}
|
||||
|
||||
const DefaultOptions: IFollowOptions = {
|
||||
followRedirects: true,
|
||||
maximumRedirectFollowDepth: 20,
|
||||
enableCookies: true,
|
||||
};
|
||||
|
||||
async function followRecursivelyWithCookies(
|
||||
url: string,
|
||||
timeoutInMs: number,
|
||||
options: RequestInit,
|
||||
followDepth: number,
|
||||
cookies: CookieStorage,
|
||||
): Promise<Response> {
|
||||
options = updateCookieHeader(cookies, options);
|
||||
const response = await fetchWithTimeout(
|
||||
url,
|
||||
timeoutInMs,
|
||||
options,
|
||||
);
|
||||
if (!isRedirect(response.status)) {
|
||||
return response;
|
||||
}
|
||||
const newFollowDepth = followDepth - 1;
|
||||
if (newFollowDepth < 0) {
|
||||
throw new Error(`[max-redirect] maximum redirect reached at: ${url}`);
|
||||
}
|
||||
const cookieHeader = response.headers.get('set-cookie');
|
||||
cookies.addHeader(cookieHeader);
|
||||
const nextUrl = response.headers.get('location');
|
||||
return followRecursivelyWithCookies(nextUrl, timeoutInMs, options, newFollowDepth, cookies);
|
||||
}
|
||||
|
||||
function isRedirect(code: number): boolean {
|
||||
return code === 301 || code === 302 || code === 303 || code === 307 || code === 308;
|
||||
}
|
||||
|
||||
class CookieStorage {
|
||||
public cookies = new Array<string>();
|
||||
|
||||
constructor(private readonly enabled: boolean) {
|
||||
}
|
||||
|
||||
public hasAny() {
|
||||
return this.enabled && this.cookies.length > 0;
|
||||
}
|
||||
|
||||
public addHeader(header: string) {
|
||||
if (!this.enabled || !header) {
|
||||
return;
|
||||
}
|
||||
this.cookies.push(header);
|
||||
}
|
||||
|
||||
public getHeader() {
|
||||
return this.cookies.join(' ; ');
|
||||
}
|
||||
}
|
||||
|
||||
function followRedirects(options: IFollowOptions) {
|
||||
if (!options.followRedirects) {
|
||||
return false;
|
||||
}
|
||||
if (options.maximumRedirectFollowDepth === 0) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function updateCookieHeader(
|
||||
cookies: CookieStorage,
|
||||
options: RequestInit,
|
||||
): RequestInit {
|
||||
if (!cookies.hasAny()) {
|
||||
return options;
|
||||
}
|
||||
const newOptions = { ...options, headers: { ...options.headers, cookie: cookies.getHeader() } };
|
||||
return newOptions;
|
||||
}
|
||||
Reference in New Issue
Block a user