# frozen_string_literal: true require "net/http" require "uri" RSpec.describe Dispatch::Adapter::Claude::OAuth::CallbackServer do # The CallbackServer specs exercise a real local HTTP server and therefore # need genuine loopback traffic. The global spec_helper locks all network # access (including localhost) — relax that just for this file. before do WebMock.disable_net_connect!(allow_localhost: true) end after do WebMock.disable_net_connect!(allow_localhost: false) end # Use a random high port to avoid conflicts in CI let(:port) { 54_545 } let(:server) { described_class.new(port: port, timeout: 5) } after { server.stop } describe "#callback_url" do it "returns the localhost callback URL" do expect(server.callback_url).to eq("http://localhost:#{port}/callback") end end describe "#start + #await_code" do it "returns [code, state] when the browser hits /callback?code=X&state=Y" do server.start # Give the server a moment to bind sleep(0.1) # Simulate browser redirect Thread.new do sleep(0.05) Net::HTTP.get(URI("http://127.0.0.1:#{port}/callback?code=auth_code_123&state=csrf_state_abc")) rescue StandardError nil end code, state = server.await_code expect(code).to eq("auth_code_123") expect(state).to eq("csrf_state_abc") end it "raises AuthenticationError on timeout" do short_timeout_server = described_class.new(port: port, timeout: 1) short_timeout_server.start expect { short_timeout_server.await_code }.to raise_error(Dispatch::Adapter::AuthenticationError, /timed out/) ensure short_timeout_server.stop end it "serves a 200 success page on /callback with code" do server.start sleep(0.1) response = nil Thread.new do response = Net::HTTP.get_response(URI("http://127.0.0.1:#{port}/callback?code=abc&state=xyz")) rescue StandardError nil end.join(2) # Drain the queue so await_code doesn't hang the after block Thread.new do server.await_code rescue StandardError nil end expect(response&.code).to eq("200") end it "serves 400 when code param is missing" do server.start sleep(0.1) response = Net::HTTP.get_response(URI("http://127.0.0.1:#{port}/callback?state=xyz")) expect(response.code).to eq("400") end it "serves 404 for unknown paths" do server.start sleep(0.1) response = Net::HTTP.get_response(URI("http://127.0.0.1:#{port}/unknown")) expect(response.code).to eq("404") end end describe "#stop" do it "is idempotent — calling stop twice does not raise" do server.start sleep(0.1) server.stop expect { server.stop }.not_to raise_error end it "can be called before start without raising" do expect { server.stop }.not_to raise_error end end end