diff --git a/frontend/src/App.test.tsx b/frontend/src/App.test.tsx
index 22a1aea8..07f94fc7 100644
--- a/frontend/src/App.test.tsx
+++ b/frontend/src/App.test.tsx
@@ -66,7 +66,12 @@ describe("App", () => {
mockedApi.getAnthropicApiKeyExists.mockResolvedValue(false);
mockedApi.getAnthropicModels.mockResolvedValue([]);
mockedApi.getModelPreference.mockResolvedValue(null);
- mockedApi.getOAuthStatus.mockResolvedValue({ authenticated: false, expired: false, expires_at: 0, has_refresh_token: false });
+ mockedApi.getOAuthStatus.mockResolvedValue({
+ authenticated: false,
+ expired: false,
+ expires_at: 0,
+ has_refresh_token: false,
+ });
});
async function renderApp() {
diff --git a/frontend/src/components/Chat.tsx b/frontend/src/components/Chat.tsx
index 1e73e51a..bad98dc3 100644
--- a/frontend/src/components/Chat.tsx
+++ b/frontend/src/components/Chat.tsx
@@ -168,7 +168,11 @@ interface ChatProps {
oauthStatus?: OAuthStatus | null;
}
-export function Chat({ projectPath, onCloseProject, oauthStatus = null }: ChatProps) {
+export function Chat({
+ projectPath,
+ onCloseProject,
+ oauthStatus = null,
+}: ChatProps) {
const { messages, setMessages, clearMessages } = useChatHistory(projectPath);
const [loading, setLoading] = useState(false);
const [model, setModel] = useState("claude-code-pty");
diff --git a/frontend/src/components/ChatHeader.tsx b/frontend/src/components/ChatHeader.tsx
index 83b5b25c..e8b925b0 100644
--- a/frontend/src/components/ChatHeader.tsx
+++ b/frontend/src/components/ChatHeader.tsx
@@ -349,7 +349,11 @@ export function ChatHeader({
type="button"
title="Authenticate with Claude via OAuth"
onClick={() => {
- window.open("/oauth/authorize", "_blank", "noopener,noreferrer");
+ window.open(
+ "/oauth/authorize",
+ "_blank",
+ "noopener,noreferrer",
+ );
}}
style={{
padding: "6px 12px",
diff --git a/frontend/src/components/selection/SelectionScreen.tsx b/frontend/src/components/selection/SelectionScreen.tsx
index 9b3a637e..5274f760 100644
--- a/frontend/src/components/selection/SelectionScreen.tsx
+++ b/frontend/src/components/selection/SelectionScreen.tsx
@@ -66,7 +66,11 @@ export function SelectionScreen({
) : (
>,
slack_ctx: Option>,
+ port: u16,
) -> impl poem::Endpoint {
let ctx_arc = std::sync::Arc::new(ctx);
let (api_service, docs_service) = build_openapi_service(ctx_arc.clone());
- let oauth_state = Arc::new(oauth::OAuthState::new(resolve_port()));
+ let oauth_state = Arc::new(oauth::OAuthState::new(port));
let mut route = Route::new()
.nest("/api", api_service)
@@ -236,6 +237,15 @@ mod tests {
fn build_routes_constructs_without_panic() {
let tmp = tempfile::tempdir().unwrap();
let ctx = context::AppContext::new_test(tmp.path().to_path_buf());
- let _endpoint = build_routes(ctx, None, None);
+ let _endpoint = build_routes(ctx, None, None, 3001);
+ }
+
+ #[test]
+ fn build_routes_accepts_custom_port() {
+ // Verify build_routes compiles and runs with a non-default port,
+ // ensuring the port parameter flows through to OAuthState.
+ let tmp = tempfile::tempdir().unwrap();
+ let ctx = context::AppContext::new_test(tmp.path().to_path_buf());
+ let _endpoint = build_routes(ctx, None, None, 9999);
}
}
diff --git a/server/src/http/oauth.rs b/server/src/http/oauth.rs
index 6d83d52b..8932e1ae 100644
--- a/server/src/http/oauth.rs
+++ b/server/src/http/oauth.rs
@@ -423,6 +423,13 @@ mod tests {
assert_eq!(state.callback_url(), "http://localhost:3001/callback");
}
+ #[test]
+ fn oauth_state_callback_url_uses_given_port() {
+ // Ensure OAuthState::new uses the port passed to it, not a hardcoded value.
+ let state = OAuthState::new(9876);
+ assert_eq!(state.callback_url(), "http://localhost:9876/callback");
+ }
+
#[tokio::test]
async fn html_response_contains_title_and_message() {
let resp = html_response(StatusCode::OK, "Test Title", "Test message");
diff --git a/server/src/main.rs b/server/src/main.rs
index 4bd05f27..d957c081 100644
--- a/server/src/main.rs
+++ b/server/src/main.rs
@@ -495,7 +495,7 @@ async fn main() -> Result<(), std::io::Error> {
matrix_shutdown_tx: Some(Arc::clone(&matrix_shutdown_tx)),
};
- let app = build_routes(ctx, whatsapp_ctx.clone(), slack_ctx.clone());
+ let app = build_routes(ctx, whatsapp_ctx.clone(), slack_ctx.clone(), port);
// Optional Matrix bot: connect to the homeserver and start listening for
// messages if `.storkit/bot.toml` is present and enabled.