diff --git a/frontend/__tests__/components/browser.test.tsx b/frontend/__tests__/components/browser.test.tsx
index c51519bf0ff4..f375e2dd50f9 100644
--- a/frontend/__tests__/components/browser.test.tsx
+++ b/frontend/__tests__/components/browser.test.tsx
@@ -1,11 +1,10 @@
import { describe, it, expect, afterEach, vi } from "vitest";
-import * as router from "react-router";
// Mock useParams before importing components
vi.mock("react-router", async () => {
const actual = await vi.importActual("react-router");
return {
- ...actual as object,
+ ...(actual as object),
useParams: () => ({ conversationId: "test-conversation-id" }),
};
});
@@ -14,7 +13,7 @@ vi.mock("react-router", async () => {
vi.mock("react-i18next", async () => {
const actual = await vi.importActual("react-i18next");
return {
- ...actual as object,
+ ...(actual as object),
useTranslation: () => ({
t: (key: string) => key,
i18n: {
@@ -28,7 +27,6 @@ import { screen } from "@testing-library/react";
import { renderWithProviders } from "../../test-utils";
import { BrowserPanel } from "#/components/features/browser/browser";
-
describe("Browser", () => {
afterEach(() => {
vi.clearAllMocks();
diff --git a/frontend/__tests__/components/chat/expandable-message.test.tsx b/frontend/__tests__/components/chat/expandable-message.test.tsx
index ec0f7c2b6549..7587d6c1c2c3 100644
--- a/frontend/__tests__/components/chat/expandable-message.test.tsx
+++ b/frontend/__tests__/components/chat/expandable-message.test.tsx
@@ -2,36 +2,42 @@ import { describe, expect, it } from "vitest";
import { screen } from "@testing-library/react";
import { renderWithProviders } from "test-utils";
import { ExpandableMessage } from "#/components/features/chat/expandable-message";
-import { vi } from 'vitest';
+import { vi } from "vitest";
-vi.mock('react-i18next', async () => {
- const actual = await vi.importActual('react-i18next');
+vi.mock("react-i18next", async () => {
+ const actual = await vi.importActual("react-i18next");
return {
...actual,
useTranslation: () => ({
- t: (key:string) => key,
+ t: (key: string) => key,
i18n: {
changeLanguage: () => new Promise(() => {}),
- language: 'en',
+ language: "en",
exists: () => true,
},
}),
- }
+ };
});
describe("ExpandableMessage", () => {
it("should render with neutral border for non-action messages", () => {
renderWithProviders();
const element = screen.getByText("Hello");
- const container = element.closest("div.flex.gap-2.items-center.justify-start");
+ const container = element.closest(
+ "div.flex.gap-2.items-center.justify-start",
+ );
expect(container).toHaveClass("border-neutral-300");
expect(screen.queryByTestId("status-icon")).not.toBeInTheDocument();
});
it("should render with neutral border for error messages", () => {
- renderWithProviders();
+ renderWithProviders(
+ ,
+ );
const element = screen.getByText("Error occurred");
- const container = element.closest("div.flex.gap-2.items-center.justify-start");
+ const container = element.closest(
+ "div.flex.gap-2.items-center.justify-start",
+ );
expect(container).toHaveClass("border-danger");
expect(screen.queryByTestId("status-icon")).not.toBeInTheDocument();
});
@@ -43,10 +49,12 @@ describe("ExpandableMessage", () => {
message="Command executed successfully"
type="action"
success={true}
- />
+ />,
);
const element = screen.getByText("OBSERVATION_MESSAGE$RUN");
- const container = element.closest("div.flex.gap-2.items-center.justify-start");
+ const container = element.closest(
+ "div.flex.gap-2.items-center.justify-start",
+ );
expect(container).toHaveClass("border-neutral-300");
const icon = screen.getByTestId("status-icon");
expect(icon).toHaveClass("fill-success");
@@ -59,10 +67,12 @@ describe("ExpandableMessage", () => {
message="Command failed"
type="action"
success={false}
- />
+ />,
);
const element = screen.getByText("OBSERVATION_MESSAGE$RUN");
- const container = element.closest("div.flex.gap-2.items-center.justify-start");
+ const container = element.closest(
+ "div.flex.gap-2.items-center.justify-start",
+ );
expect(container).toHaveClass("border-neutral-300");
const icon = screen.getByTestId("status-icon");
expect(icon).toHaveClass("fill-danger");
@@ -74,10 +84,12 @@ describe("ExpandableMessage", () => {
id="OBSERVATION_MESSAGE$RUN"
message="Running command"
type="action"
- />
+ />,
);
const element = screen.getByText("OBSERVATION_MESSAGE$RUN");
- const container = element.closest("div.flex.gap-2.items-center.justify-start");
+ const container = element.closest(
+ "div.flex.gap-2.items-center.justify-start",
+ );
expect(container).toHaveClass("border-neutral-300");
expect(screen.queryByTestId("status-icon")).not.toBeInTheDocument();
});
diff --git a/frontend/__tests__/components/feedback-form.test.tsx b/frontend/__tests__/components/feedback-form.test.tsx
index e3cad75d45c1..7a20780c7dbf 100644
--- a/frontend/__tests__/components/feedback-form.test.tsx
+++ b/frontend/__tests__/components/feedback-form.test.tsx
@@ -1,11 +1,10 @@
-import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
-import * as router from "react-router";
+import { afterEach, describe, expect, it, vi } from "vitest";
// Mock useParams before importing components
vi.mock("react-router", async () => {
const actual = await vi.importActual("react-router");
return {
- ...actual as object,
+ ...(actual as object),
useParams: () => ({ conversationId: "test-conversation-id" }),
};
});
@@ -60,7 +59,9 @@ describe("FeedbackForm", () => {
renderWithProviders(
,
);
- await user.click(screen.getByRole("button", { name: I18nKey.FEEDBACK$CANCEL_LABEL }));
+ await user.click(
+ screen.getByRole("button", { name: I18nKey.FEEDBACK$CANCEL_LABEL }),
+ );
expect(onCloseMock).toHaveBeenCalled();
});
diff --git a/frontend/__tests__/routes/_oh.test.tsx b/frontend/__tests__/routes/_oh.test.tsx
index 1ce376e8d133..1470a88abca3 100644
--- a/frontend/__tests__/routes/_oh.test.tsx
+++ b/frontend/__tests__/routes/_oh.test.tsx
@@ -1,5 +1,4 @@
import { afterEach, beforeAll, describe, expect, it, vi } from "vitest";
-import * as router from "react-router";
import { createRoutesStub } from "react-router";
import { screen, waitFor, within } from "@testing-library/react";
import { renderWithProviders } from "test-utils";
diff --git a/frontend/__tests__/utils/test-config.tsx b/frontend/__tests__/utils/test-config.tsx
new file mode 100644
index 000000000000..f6a0971697a9
--- /dev/null
+++ b/frontend/__tests__/utils/test-config.tsx
@@ -0,0 +1,20 @@
+import { vi } from "vitest";
+import OpenHands from "#/api/open-hands";
+
+export const setupTestConfig = () => {
+ const getConfigSpy = vi.spyOn(OpenHands, "getConfig");
+ getConfigSpy.mockResolvedValue({
+ APP_MODE: "oss",
+ GITHUB_CLIENT_ID: "test-id",
+ POSTHOG_CLIENT_KEY: "test-key",
+ });
+};
+
+export const setupSaasTestConfig = () => {
+ const getConfigSpy = vi.spyOn(OpenHands, "getConfig");
+ getConfigSpy.mockResolvedValue({
+ APP_MODE: "saas",
+ GITHUB_CLIENT_ID: "test-id",
+ POSTHOG_CLIENT_KEY: "test-key",
+ });
+};
diff --git a/frontend/src/api/github-axios-instance.ts b/frontend/src/api/github-axios-instance.ts
index 21ad5ea90651..dace5e3461c4 100644
--- a/frontend/src/api/github-axios-instance.ts
+++ b/frontend/src/api/github-axios-instance.ts
@@ -41,6 +41,7 @@ export const isGitHubErrorReponse = >(
// Axios interceptor to handle token refresh
const setupAxiosInterceptors = (
+ appMode: string,
refreshToken: () => Promise,
logout: () => void,
) => {
@@ -74,18 +75,21 @@ const setupAxiosInterceptors = (
!originalRequest._retry // Prevent infinite retry loops
) {
originalRequest._retry = true;
- try {
- const refreshed = await refreshToken();
- if (refreshed) {
- return await github(originalRequest);
- }
- logout();
- return await Promise.reject(new Error("Failed to refresh token"));
- } catch (refreshError) {
- // If token refresh fails, evict the user
- logout();
- return Promise.reject(refreshError);
+ if (appMode === "saas") {
+ try {
+ const refreshed = await refreshToken();
+ if (refreshed) {
+ return await github(originalRequest);
+ }
+
+ logout();
+ return await Promise.reject(new Error("Failed to refresh token"));
+ } catch (refreshError) {
+ // If token refresh fails, evict the user
+ logout();
+ return Promise.reject(refreshError);
+ }
}
}
diff --git a/frontend/src/context/auth-context.tsx b/frontend/src/context/auth-context.tsx
index 14e45f503ed4..f04f4c79a237 100644
--- a/frontend/src/context/auth-context.tsx
+++ b/frontend/src/context/auth-context.tsx
@@ -87,7 +87,12 @@ function AuthProvider({ children }: React.PropsWithChildren) {
setGitHubToken(storedGitHubToken);
setUserId(userId);
- setupGithubAxiosInterceptors(refreshToken, logout);
+ const setupIntercepter = async () => {
+ const config = await OpenHands.getConfig();
+ setupGithubAxiosInterceptors(config.APP_MODE, refreshToken, logout);
+ };
+
+ setupIntercepter();
}, []);
const value = React.useMemo(