1package provider
2
3import (
4 "context"
5 "net/http"
6 "net/url"
7 "testing"
8 "time"
9)
10
11type mockRoundTripper struct {
12 lastReq *http.Request
13}
14
15func (m *mockRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
16 m.lastReq = req
17 return &http.Response{
18 StatusCode: http.StatusOK,
19 Body: http.NoBody,
20 }, nil
21}
22
23func TestCopilotHostRewrite(t *testing.T) {
24 // Seed the cache with a mock token that carries a non-default proxy endpoint
25 // Ensure the expiresAt time is far in the future so that it bypasses the exchange check
26 const pat = "ghp_mock_token_for_test"
27 copilotCache.mu.Lock()
28 copilotCache.tokens[pat] = copilotToken{
29 value: "mock-jwt-with;proxy-ep=enterprise.copilot.proxy",
30 expiresAt: time.Now().Add(1 * time.Hour),
31 baseURL: "https://api.enterprise.copilot.proxy",
32 }
33 copilotCache.mu.Unlock()
34
35 mockRT := &mockRoundTripper{}
36 transport := &copilotRefreshTransport{
37 inner: mockRT,
38 pat: pat,
39 }
40
41 reqUrl, err := url.Parse("https://api.individual.githubcopilot.com/chat/completions")
42 if err != nil {
43 t.Fatalf("failed to parse test URL: %v", err)
44 }
45
46 req := &http.Request{
47 Method: "POST",
48 URL: reqUrl,
49 Header: make(http.Header),
50 }
51 req = req.WithContext(context.Background())
52
53 _, err = transport.RoundTrip(req)
54 if err != nil {
55 t.Fatalf("RoundTrip failed: %v", err)
56 }
57
58 if mockRT.lastReq == nil {
59 t.Fatal("RoundTrip did not forward the request")
60 }
61
62 // Verify both URL host and Host header are rewritten correctly to prevent 421 Misdirected Request
63 wantHost := "api.enterprise.copilot.proxy"
64 if mockRT.lastReq.URL.Host != wantHost {
65 t.Errorf("expected URL.Host to be %q, got %q", wantHost, mockRT.lastReq.URL.Host)
66 }
67 if mockRT.lastReq.Host != wantHost {
68 t.Errorf("expected Host header (Request.Host) to be %q, got %q", wantHost, mockRT.lastReq.Host)
69 }
70}
71
72func TestCopilotRoutesGPT56ToResponses(t *testing.T) {
73 router, ok := NewGithubCopilotClient("pat").(*modelRouter)
74 if !ok {
75 t.Fatal("NewGithubCopilotClient did not return a modelRouter")
76 }
77 responses, ok := router.byAPI[APIResponses].(*renamedClient)
78 if !ok {
79 t.Fatal("github-copilot router has no Responses client")
80 }
81 codex, ok := responses.inner.(*codexClient)
82 if !ok {
83 t.Fatal("github-copilot Responses client is not a codexClient")
84 }
85 const want = "https://api.individual.githubcopilot.com/responses"
86 if codex.baseURL != want {
87 t.Errorf("github-copilot Responses baseURL = %q, want %q", codex.baseURL, want)
88 }
89 for _, id := range []string{"gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna"} {
90 m, err := FindModel("github-copilot", id)
91 if err != nil {
92 t.Fatalf("FindModel(%q): %v", id, err)
93 }
94 if m.API != APIResponses {
95 t.Errorf("model %q API = %q, want %q", id, m.API, APIResponses)
96 }
97 }
98}