v0.3.70 5bef29b

discover_copilot_test.go RawHistoryFolder

1package provider
2
3import (
4	"context"
5	"fmt"
6	"net/http"
7	"net/http/httptest"
8	"reflect"
9	"strings"
10	"testing"
11)
12
13func TestParseCopilotAvailableModels(t *testing.T) {
14	for _, tc := range []struct {
15		name, body string
16		fallback   bool
17		want       []string
18		invalid    bool
19	}{
20		{"picker", `{"data":[{"id":"yes","model_picker_enabled":true},{"id":"disabled","model_picker_enabled":true,"policy":{"state":"disabled"}},{"id":"hidden","policy":{"state":"enabled"}},{"id":"no-tools","model_picker_enabled":true,"capabilities":{"supports":{"tool_calls":false}}}]}`, true, []string{"yes"}, false},
21		{"individual fallback", `{"data":[{"id":"enabled","policy":{"state":"enabled"}},{"id":"unconfigured","policy":{"state":"unconfigured"}},{"id":"disabled","policy":{"state":"disabled"}}]}`, true, []string{"enabled"}, false},
22		{"enterprise no fallback", `{"data":[{"id":"enabled","policy":{"state":"enabled"}}]}`, false, []string{}, false},
23		{"empty", `{"data":[]}`, true, []string{}, false},
24		{"duplicate", `{"data":[{"id":"yes","model_picker_enabled":true},{"id":"yes","model_picker_enabled":true}]}`, false, []string{"yes"}, false},
25		{"missing", `{}`, false, nil, true},
26		{"null", `{"data":null}`, false, nil, true},
27		{"invalid", `{"data":{}}`, false, nil, true},
28	} {
29		t.Run(tc.name, func(t *testing.T) {
30			got, err := parseCopilotAvailableModels([]byte(tc.body), tc.fallback)
31			if (err != nil) != tc.invalid {
32				t.Fatalf("error = %v", err)
33			}
34			if !tc.invalid && !reflect.DeepEqual(got, tc.want) {
35				t.Fatalf("got %v, want %v", got, tc.want)
36			}
37		})
38	}
39}
40
41func TestCopilotDiscoveryHTTP(t *testing.T) {
42	for _, status := range []int{200, 403, 429} {
43		t.Run(fmt.Sprint(status), func(t *testing.T) {
44			srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
45				if r.Method != "GET" || r.URL.Path != "/models" {
46					t.Error("incorrect discovery request")
47				}
48				if r.Header.Get("Authorization") != "Bearer synthetic-session-token" || r.Header.Get("Copilot-Integration-Id") != "vscode-chat" || r.Header.Get("X-GitHub-Api-Version") != "2026-06-01" {
49					t.Error("incorrect discovery headers")
50				}
51				w.WriteHeader(status)
52				if status != 200 {
53					fmt.Fprint(w, "sensitive-response-body")
54					return
55				}
56				fmt.Fprint(w, `{"data":[{"id":"claude-sonnet-5","model_picker_enabled":true}]}`)
57			}))
58			defer srv.Close()
59			pat := seedCopilotTestToken(t, srv.URL)
60			got, err := DiscoverCopilotAvailableModels(context.Background(), pat)
61			if status == 200 {
62				if err != nil || !reflect.DeepEqual(got, []string{"claude-sonnet-5"}) {
63					t.Fatalf("got %v, %v", got, err)
64				}
65			} else if err == nil || strings.Contains(err.Error(), "sensitive-response-body") {
66				t.Fatalf("unexpected error %v", err)
67			}
68			ctx, cancel := context.WithCancel(context.Background())
69			cancel()
70			if _, err := DiscoverCopilotAvailableModels(ctx, pat); err == nil {
71				t.Error("cancellation ignored")
72			}
73		})
74	}
75}
76
77func TestModelAvailabilityFiltersWithoutDeletingCatalog(t *testing.T) {
78	t.Cleanup(func() { SetModelAvailability("github-copilot", nil) })
79	before := len(ModelsForProvider("anthropic"))
80	ids := []string{"claude-sonnet-5"}
81	SetModelAvailability("github-copilot", ids)
82	ids[0] = "mutated"
83	models := ModelsForProvider("github-copilot")
84	if len(models) != 1 || models[0].ID != "claude-sonnet-5" {
85		t.Fatalf("models = %v", models)
86	}
87	if len(ModelsForProvider("anthropic")) != before {
88		t.Error("changed another provider")
89	}
90	SetModelAvailability("github-copilot", []string{})
91	// Hiding a model must not change wire metadata for a session already using it.
92	model, err := FindModel("github-copilot", "gpt-5.4")
93	if err != nil || model.API != APIResponses {
94		t.Fatalf("availability refresh lost routing metadata: %+v, %v", model, err)
95	}
96	if len(ModelsForProvider("github-copilot")) != 0 {
97		t.Error("empty availability did not hide models")
98	}
99	SetModelAvailability("github-copilot", nil)
100	if len(ModelsForProvider("github-copilot")) != 27 {
101		t.Error("clearing restriction did not restore catalog")
102	}
103}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 476

↑↓ moveenter openesc close