Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 4 additions & 0 deletions app/client/packages/utils/src/compose/compose.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export const compose =
<T>(...fns: Array<(arg: T) => T>) =>
(x: T) =>
fns.reduce((acc, fn) => fn(acc), x);
1 change: 1 addition & 0 deletions app/client/packages/utils/src/compose/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { compose } from "./compose";
1 change: 1 addition & 0 deletions app/client/packages/utils/src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from "./object";
export * from "./compose";
23 changes: 6 additions & 17 deletions app/client/src/api/Api.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
import type { AxiosInstance, AxiosRequestConfig } from "axios";
import axios from "axios";
import { REQUEST_TIMEOUT_MS } from "ee/constants/ApiConstants";
import { convertObjectToQueryParams } from "utils/URLUtils";
import type { AxiosInstance, AxiosRequestConfig } from "axios";
import {
apiFailureResponseInterceptor,
apiRequestInterceptor,
apiFailureResponseInterceptor,
apiSuccessResponseInterceptor,
blockedApiRoutesForAirgapInterceptor,
} from "ee/api/ApiUtils";
} from "./interceptors";
import { REQUEST_TIMEOUT_MS } from "ee/constants/ApiConstants";
import { convertObjectToQueryParams } from "utils/URLUtils";

//TODO(abhinav): Refactor this to make more composable.
export const apiRequestConfig = {
baseURL: "/api/",
timeout: REQUEST_TIMEOUT_MS,
Expand All @@ -21,16 +19,7 @@ export const apiRequestConfig = {

const axiosInstance: AxiosInstance = axios.create();

const requestInterceptors = [
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reusing the refactored interceptors.

blockedApiRoutesForAirgapInterceptor,
apiRequestInterceptor,
];

requestInterceptors.forEach((interceptor) => {
// TODO: Fix this the next time the file is edited
// eslint-disable-next-line @typescript-eslint/no-explicit-any
axiosInstance.interceptors.request.use(interceptor as any);
});
axiosInstance.interceptors.request.use(apiRequestInterceptor);

axiosInstance.interceptors.response.use(
apiSuccessResponseInterceptor,
Expand Down
26 changes: 0 additions & 26 deletions app/client/src/api/ConsolidatedPageLoadApi.tsx

This file was deleted.

225 changes: 225 additions & 0 deletions app/client/src/api/__tests__/apiFailureResponseInterceptors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
import axios from "axios";
import type { AxiosError } from "axios";

import {
apiFailureResponseInterceptor,
apiSuccessResponseInterceptor,
} from "api/interceptors";
import type { ApiResponse } from "api/types";
import {
createMessage,
ERROR_0,
ERROR_413,
ERROR_500,
SERVER_API_TIMEOUT_ERROR,
} from "ee/constants/messages";
import { ERROR_CODES } from "ee/constants/ApiConstants";
import { UserCancelledActionExecutionError } from "sagas/ActionExecution/errorUtils";

describe("Api success response interceptors", () => {
beforeAll(() => {
axios.interceptors.response.use(
apiSuccessResponseInterceptor,
apiFailureResponseInterceptor,
);
});

it("checks 413 error", async () => {
axios.defaults.adapter = async () => {
return Promise.reject({
response: {
status: 413,
},
} as AxiosError);
};

try {
await axios.get("https://example.com");
} catch (error) {
expect((error as AxiosError<ApiResponse>).response?.status).toBe(413);
expect((error as AxiosError<ApiResponse>).message).toBe(
createMessage(ERROR_413, 100),
);
expect(
(error as AxiosError<ApiResponse> & { statusCode?: string }).statusCode,
).toBe("AE-APP-4013");
}

axios.defaults.adapter = undefined;
});

it("checks the response message when request is made when user is offline", async () => {
const onlineGetter: jest.SpyInstance = jest.spyOn(
window.navigator,
"onLine",
"get",
);

onlineGetter.mockReturnValue(false);
axios.defaults.adapter = async () => {
return new Promise((resolve, reject) => {
reject({
message: "Network Error",
} as AxiosError);
});
};

try {
await axios.get("https://example.com");
} catch (error) {
expect((error as AxiosError<ApiResponse>).message).toBe(
createMessage(ERROR_0),
);
}

onlineGetter.mockRestore();
axios.defaults.adapter = undefined;
});

it("checks if it throws UserCancelledActionExecutionError user cancels the request ", async () => {
const cancelToken = axios.CancelToken.source();

axios.defaults.adapter = async () => {
return new Promise((resolve, reject) => {
cancelToken.cancel("User cancelled the request");

reject({
message: "User cancelled the request",
} as AxiosError);
});
};

try {
await axios.get("https://example.com", {
cancelToken: cancelToken.token,
});
} catch (error) {
expect(error).toBeInstanceOf(UserCancelledActionExecutionError);
}

axios.defaults.adapter = undefined;
});

it("checks the response message when request fails for exeuction action urls", async () => {
axios.defaults.adapter = async () => {
return Promise.reject({
response: {
status: 500,
statusText: "Internal Server Error",
headers: {
"content-length": 1,
},
config: {
headers: {
timer: "1000",
},
},
},
config: {
url: "/v1/actions/execute",
},
});
};

const url = "/v1/actions/execute";
const response = await axios.get(url);

expect(response).toHaveProperty("clientMeta");

axios.defaults.adapter = undefined;
});

it("checks the error response in case of timeout", async () => {
axios.defaults.adapter = async () => {
return Promise.reject({
code: "ECONNABORTED",
message: "timeout of 1000ms exceeded",
});
};

try {
await axios.get("https://example.com");
} catch (error) {
expect((error as AxiosError<ApiResponse>).message).toBe(
createMessage(SERVER_API_TIMEOUT_ERROR),
);
expect((error as AxiosError<ApiResponse>).code).toBe(
ERROR_CODES.REQUEST_TIMEOUT,
);
}

axios.defaults.adapter = undefined;
});

it("checks the error response in case of server error", async () => {
axios.defaults.adapter = async () => {
return Promise.reject({
response: {
status: 502,
},
});
};

try {
await axios.get("https://example.com");
} catch (error) {
expect((error as AxiosError<ApiResponse>).message).toBe(
createMessage(ERROR_500),
);
expect((error as AxiosError<ApiResponse>).code).toBe(
ERROR_CODES.SERVER_ERROR,
);
}

axios.defaults.adapter = undefined;
});

it("checks error response in case of unauthorized error", async () => {
axios.defaults.adapter = async () => {
return Promise.reject({
response: {
status: 401,
},
});
};

try {
await axios.get("https://example.com");
} catch (error) {
expect((error as AxiosError<ApiResponse>).message).toBe(
"Unauthorized. Redirecting to login page...",
);
expect((error as AxiosError<ApiResponse>).code).toBe(
ERROR_CODES.REQUEST_NOT_AUTHORISED,
);
}
});

it("checks error response in case of not found error", async () => {
axios.defaults.adapter = async () => {
return Promise.reject({
response: {
data: {
responseMeta: {
status: 404,
error: {
code: "AE-ACL-4004",
},
},
},
},
});
};

try {
await axios.get("https://example.com");
} catch (error) {
expect((error as AxiosError<ApiResponse>).message).toBe(
"Resource Not Found",
);
expect((error as AxiosError<ApiResponse>).code).toBe(
ERROR_CODES.PAGE_NOT_FOUND,
);
}
});
});
Loading