feat(integration): improve integration test connection (#3005)

This commit is contained in:
Meier Lukas
2025-05-16 20:59:12 +02:00
committed by GitHub
parent 3daf1c8341
commit ef9a5e9895
111 changed files with 7168 additions and 976 deletions

View File

@@ -0,0 +1,43 @@
import { AxiosError } from "axios";
import { logger } from "@homarr/log";
import type { AnyRequestError } from "../request-error";
import { RequestError } from "../request-error";
import { ResponseError } from "../response-error";
import { matchErrorCode } from "./fetch-http-error-handler";
import { HttpErrorHandler } from "./http-error-handler";
export class AxiosHttpErrorHandler extends HttpErrorHandler {
handleRequestError(error: unknown): AnyRequestError | undefined {
if (!(error instanceof AxiosError)) return undefined;
if (error.code === undefined) return undefined;
logger.debug("Received Axios request error", {
code: error.code,
message: error.message,
});
const requestErrorInput = matchErrorCode(error.code);
if (!requestErrorInput) return undefined;
return new RequestError(requestErrorInput, {
cause: error,
});
}
handleResponseError(error: unknown): ResponseError | undefined {
if (!(error instanceof AxiosError)) return undefined;
if (error.response === undefined) return undefined;
logger.debug("Received Axios response error", {
status: error.response.status,
url: error.response.config.url,
message: error.message,
});
return new ResponseError({
status: error.response.status,
url: error.response.config.url ?? "?",
});
}
}

View File

@@ -0,0 +1,68 @@
import { logger } from "@homarr/log";
import { objectEntries } from "../../../object";
import type { Modify } from "../../../types";
import type { AnyRequestError, AnyRequestErrorInput, RequestErrorCode, RequestErrorReason } from "../request-error";
import { RequestError, requestErrorMap } from "../request-error";
import type { ResponseError } from "../response-error";
import { HttpErrorHandler } from "./http-error-handler";
export class FetchHttpErrorHandler extends HttpErrorHandler {
constructor(private type = "undici") {
super();
}
handleRequestError(error: unknown): AnyRequestError | undefined {
if (!isTypeErrorWithCode(error)) return undefined;
logger.debug(`Received ${this.type} request error`, {
code: error.cause.code,
});
const result = matchErrorCode(error.cause.code);
if (!result) return undefined;
return new RequestError(result, { cause: error });
}
/**
* Response errors do not exist for fetch as it does not throw errors for non successful responses.
*/
handleResponseError(_: unknown): ResponseError | undefined {
return undefined;
}
}
type TypeErrorWithCode = Modify<
TypeError,
{
cause: Error & { code: string };
}
>;
const isTypeErrorWithCode = (error: unknown): error is TypeErrorWithCode => {
return (
error instanceof TypeError &&
error.cause instanceof Error &&
"code" in error.cause &&
typeof error.cause.code === "string"
);
};
export const matchErrorCode = (code: string): AnyRequestErrorInput | undefined => {
for (const [key, value] of objectEntries(requestErrorMap)) {
const entries = Object.entries(value) as [string, string | string[]][];
const found = entries.find(([_, entryCode]) =>
typeof entryCode === "string" ? entryCode === code : entryCode.includes(code),
);
if (!found) continue;
return {
type: key,
reason: found[0] as RequestErrorReason<typeof key>,
code: code as RequestErrorCode,
};
}
return undefined;
};

View File

@@ -0,0 +1,7 @@
import type { AnyRequestError } from "../request-error";
import type { ResponseError } from "../response-error";
export abstract class HttpErrorHandler {
abstract handleRequestError(error: unknown): AnyRequestError | undefined;
abstract handleResponseError(error: unknown): ResponseError | undefined;
}

View File

@@ -0,0 +1,5 @@
export * from "./http-error-handler";
export * from "./fetch-http-error-handler";
export * from "./ofetch-http-error-handler";
export * from "./axios-http-error-handler";
export * from "./tsdav-http-error-handler";

View File

@@ -0,0 +1,38 @@
import { FetchError } from "ofetch";
import { logger } from "@homarr/log";
import type { AnyRequestError } from "../request-error";
import { ResponseError } from "../response-error";
import { FetchHttpErrorHandler } from "./fetch-http-error-handler";
import { HttpErrorHandler } from "./http-error-handler";
/**
* Ofetch is a wrapper around the native fetch API
* which will always throw the FetchError (also for non successful responses).
*
* It is for example used within the ctrl packages like qbittorrent, deluge, transmission, etc.
*/
export class OFetchHttpErrorHandler extends HttpErrorHandler {
handleRequestError(error: unknown): AnyRequestError | undefined {
if (!(error instanceof FetchError)) return undefined;
if (!(error.cause instanceof TypeError)) return undefined;
const result = new FetchHttpErrorHandler("ofetch").handleRequestError(error.cause);
if (!result) return undefined;
return result;
}
handleResponseError(error: unknown): ResponseError | undefined {
if (!(error instanceof FetchError)) return undefined;
if (error.response === undefined) return undefined;
logger.debug("Received ofetch response error", {
status: error.response.status,
url: error.response.url,
});
return new ResponseError(error.response);
}
}

View File

@@ -0,0 +1,25 @@
import { logger } from "@homarr/log";
import type { AnyRequestError } from "../request-error";
import { ResponseError } from "../response-error";
import { FetchHttpErrorHandler } from "./fetch-http-error-handler";
import { HttpErrorHandler } from "./http-error-handler";
export class TsdavHttpErrorHandler extends HttpErrorHandler {
handleRequestError(error: unknown): AnyRequestError | undefined {
return new FetchHttpErrorHandler("tsdav").handleRequestError(error);
}
handleResponseError(error: unknown): ResponseError | undefined {
if (!(error instanceof Error)) return undefined;
// Tsdav sadly does not throw a custom error and rather just uses "Error"
// https://github.com/natelindev/tsdav/blob/bf33f04b1884694d685ee6f2b43fe9354b12d167/src/account.ts#L86
if (error.message !== "Invalid credentials") return undefined;
logger.debug("Received Tsdav response error", {
status: 401,
});
return new ResponseError({ status: 401, url: "?" });
}
}