Compare commits

..

1 Commits

Author SHA1 Message Date
openhands cfa1151780 Fix GitHub authentication headers to use Bearer token consistently 2025-03-14 14:17:58 +00:00
6 changed files with 80 additions and 56 deletions
@@ -4,10 +4,8 @@ import userEvent from "@testing-library/user-event";
import { afterEach, beforeEach, describe, expect, it, test, vi } from "vitest";
import OpenHands from "#/api/open-hands";
import { PaymentForm } from "#/components/features/payment/payment-form";
import * as featureFlags from "#/utils/feature-flags";
describe("PaymentForm", () => {
const billingSettingsSpy = vi.spyOn(featureFlags, "BILLING_SETTINGS");
const getBalanceSpy = vi.spyOn(OpenHands, "getBalance");
const createCheckoutSessionSpy = vi.spyOn(OpenHands, "createCheckoutSession");
const getConfigSpy = vi.spyOn(OpenHands, "getConfig");
@@ -28,7 +26,6 @@ describe("PaymentForm", () => {
GITHUB_CLIENT_ID: "123",
POSTHOG_CLIENT_KEY: "456",
});
billingSettingsSpy.mockReturnValue(true);
});
afterEach(() => {
+8 -28
View File
@@ -72,10 +72,8 @@ describe("useTerminal", () => {
wrapper: Wrapper,
});
// Input commands should be displayed
expect(mockTerminal.writeln).toHaveBeenCalledWith("echo hello");
// Output commands should be displayed
expect(mockTerminal.writeln).toHaveBeenCalledWith("hello");
expect(mockTerminal.writeln).toHaveBeenNthCalledWith(1, "echo hello");
expect(mockTerminal.writeln).toHaveBeenNthCalledWith(2, "hello");
});
it("should hide secrets in the terminal", () => {
@@ -99,31 +97,13 @@ describe("useTerminal", () => {
},
);
// Input command should be displayed with secrets masked
expect(mockTerminal.writeln).toHaveBeenCalledWith(
// BUG: `vi.clearAllMocks()` does not clear the number of calls
// therefore, we need to assume the order of the calls based
// on the test order
expect(mockTerminal.writeln).toHaveBeenNthCalledWith(
3,
`export GITHUB_TOKEN=${"*".repeat(10)},${"*".repeat(10)},${"*".repeat(10)}`,
);
// Output command should be displayed with secrets masked
expect(mockTerminal.writeln).toHaveBeenCalledWith("*".repeat(10));
});
it("should prevent duplicate command display", () => {
const inputCommand = "ls -la";
const commands: Command[] = [
{ content: inputCommand, type: "input" },
{ content: `${inputCommand}\nfile1.txt\nfile2.txt`, type: "output" },
];
render(<TestTerminalComponent commands={commands} secrets={[]} />, {
wrapper: Wrapper,
});
// Input command should be displayed
expect(mockTerminal.writeln).toHaveBeenCalledWith(inputCommand);
// Output should not be displayed since it starts with the input command
// This prevents the duplicate display of the command
expect(mockTerminal.writeln).not.toHaveBeenCalledWith(`${inputCommand}\nfile1.txt\nfile2.txt`);
expect(mockTerminal.writeln).toHaveBeenNthCalledWith(4, "*".repeat(10));
});
});
+1 -2
View File
@@ -1,7 +1,6 @@
import { useQuery } from "@tanstack/react-query";
import { useConfig } from "./use-config";
import OpenHands from "#/api/open-hands";
import { BILLING_SETTINGS } from "#/utils/feature-flags";
export const useBalance = () => {
const { data: config } = useConfig();
@@ -9,6 +8,6 @@ export const useBalance = () => {
return useQuery({
queryKey: ["user", "balance"],
queryFn: OpenHands.getBalance,
enabled: config?.APP_MODE === "saas" && BILLING_SETTINGS(),
enabled: config?.APP_MODE === "saas",
});
};
+3 -21
View File
@@ -86,8 +86,6 @@ export const useTerminal = ({
const handleEnter = (command: string) => {
terminal.current?.write("\r\n");
// Send the command to the backend but don't echo it back in the terminal
// The backend will include the command in its response
send(getTerminalCommand(command));
};
@@ -133,26 +131,10 @@ export const useTerminal = ({
content = content.replaceAll(secret, "*".repeat(10));
});
// Check if this is an output that starts with the previous input command
// This happens when the backend echoes back the command in the output
let shouldDisplayContent = true;
if (type === "output" && i > 0 && commands[i - 1].type === "input") {
const prevInputCommand = commands[i - 1].content.trim();
// If the output starts with the input command, remove it to avoid duplication
if (content.trim().startsWith(prevInputCommand)) {
// Skip displaying this part as it's a duplicate of the user's input
// that's already shown in the terminal
shouldDisplayContent = false;
}
}
terminal.current?.writeln(
parseTerminalOutput(content.replaceAll("\n", "\r\n").trim()),
);
if (shouldDisplayContent) {
terminal.current?.writeln(
parseTerminalOutput(content.replaceAll("\n", "\r\n").trim()),
);
}
// Add a new prompt after the output
if (type === "output") {
terminal.current.write(`\n$ `);
}
+2 -2
View File
@@ -27,7 +27,7 @@ class GithubIssueHandler(IssueHandlerInterface):
def get_headers(self) -> dict[str, str]:
return {
'Authorization': f'token {self.token}',
'Authorization': f'Bearer {self.token}',
'Accept': 'application/vnd.github.v3+json',
}
@@ -450,7 +450,7 @@ class GithubPRHandler(GithubIssueHandler):
"""Download comments for a specific pull request from Github."""
url = f'https://api.github.com/repos/{self.owner}/{self.repo}/issues/{pr_number}/comments'
headers = {
'Authorization': f'token {self.token}',
'Authorization': f'Bearer {self.token}',
'Accept': 'application/vnd.github.v3+json',
}
params = {'per_page': 100, 'page': 1}
+66
View File
@@ -0,0 +1,66 @@
import pytest
from unittest.mock import patch, MagicMock
from openhands.resolver.interfaces.github import GithubIssueHandler, GithubPRHandler
@pytest.mark.asyncio
async def test_github_issue_handler_auth_headers():
"""Test that GithubIssueHandler uses Bearer token in authorization headers."""
# Create a GithubIssueHandler instance
handler = GithubIssueHandler(
owner="test-owner",
repo="test-repo",
token="test-token",
username="test-username"
)
# Check that the headers use Bearer token
headers = handler.get_headers()
assert headers["Authorization"] == "Bearer test-token"
# Mock requests.get to check headers in API calls
with patch("requests.get") as mock_get:
mock_response = MagicMock()
mock_response.status_code = 200
mock_response.json.return_value = []
mock_get.return_value = mock_response
# Call a method that uses requests.get
handler.download_issues()
# Check that the call to requests.get used Bearer token
call_args = mock_get.call_args
headers_used = call_args[1]["headers"]
assert headers_used["Authorization"] == "Bearer test-token"
@pytest.mark.asyncio
async def test_github_pr_handler_auth_headers():
"""Test that GithubPRHandler uses Bearer token in authorization headers."""
# Create a GithubPRHandler instance
handler = GithubPRHandler(
owner="test-owner",
repo="test-repo",
token="test-token",
username="test-username"
)
# Check that the headers use Bearer token
headers = handler.get_headers()
assert headers["Authorization"] == "Bearer test-token"
# Mock requests.post to check headers in GraphQL API calls
with patch("requests.post") as mock_post:
mock_response = MagicMock()
mock_response.status_code = 200
mock_response.json.return_value = {"data": {"repository": {"pullRequest": {}}}}
mock_post.return_value = mock_response
# Call a method that uses requests.post with GraphQL
handler.download_pr_metadata(1)
# Check that the call to requests.post used Bearer token
call_args = mock_post.call_args
headers_used = call_args[1]["headers"]
assert headers_used["Authorization"] == "Bearer test-token"