1package provider
2
3import (
4 "context"
5 "strings"
6 "testing"
7)
8
9func TestCopilotCatalogCoversPublicChatModels(t *testing.T) {
10 groups := map[string][]string{
11 APIAnthropicMessages: {"claude-fable-5", "claude-fable-5.1", "claude-haiku-4.5", "claude-opus-4.7", "claude-opus-4.8", "claude-opus-4.8-fast", "claude-opus-5", "claude-sonnet-5"},
12 APIResponses: {"gpt-5-mini", "gpt-5.3-codex", "gpt-5.4", "gpt-5.4-mini", "gpt-5.5", "gpt-5.6-luna", "gpt-5.6-sol", "gpt-5.6-terra", "gpt-6-astra", "grok-4.5", "grok-4.6", "mai-code-1.1-flash"},
13 APICompletions: {"gemini-3.5-flash", "gemini-3.6-flash", "gemini-3.7-flash", "gemini-3.8-flash", "kimi-k2.7-code", "kimi-k3"},
14 }
15 for api, ids := range groups {
16 for _, id := range ids {
17 t.Run(id, func(t *testing.T) {
18 m, err := FindModel("github-copilot", id)
19 if err != nil {
20 t.Fatal(err)
21 }
22 if m.API != api {
23 t.Fatalf("API = %q, want %q", m.API, api)
24 }
25 if m.ContextWindow <= 0 || m.MaxOutput <= 0 || m.MaxOutput >= m.ContextWindow {
26 t.Fatalf("invalid context/output limits: %d/%d", m.ContextWindow, m.MaxOutput)
27 }
28 if m.BaseURL != copilotDefaultBaseURL || !m.Reasoning {
29 t.Fatal("invalid endpoint or reasoning metadata")
30 }
31 client := NewGithubCopilotClient("synthetic-token").(*copilotClient)
32 captures := make(map[string]*routeCaptureClient)
33 for _, key := range []string{APIAnthropicMessages, APIResponses, APICompletions} {
34 capture := &routeCaptureClient{name: "github-copilot"}
35 captures[key] = capture
36 client.router.byAPI[key] = capture
37 }
38 stream, err := client.Stream(context.Background(), Request{Model: id})
39 if err != nil {
40 t.Fatal(err)
41 }
42 for range stream {
43 }
44 for key, capture := range captures {
45 want := 0
46 if key == api {
47 want = 1
48 }
49 if len(capture.models) != want {
50 t.Errorf("%s routed to %s incorrectly", id, key)
51 }
52 }
53 })
54 }
55 }
56}
57
58func TestCopilotCatalogKeepsPreviouslySupportedIDsAndExcludesUtilityModels(t *testing.T) {
59 // Rows that shipped before the catalog refresh stay selectable until
60 // retirement is verified; account availability hides unavailable ones.
61 kept := map[string]string{
62 "claude-opus-4.5": APIAnthropicMessages, "claude-opus-4.6": APIAnthropicMessages,
63 "claude-sonnet-4.5": APIAnthropicMessages, "claude-sonnet-4.6": APIAnthropicMessages,
64 "gemini-2.5-pro": APICompletions, "gemini-3-flash-preview": APICompletions, "gemini-3.1-pro-preview": APICompletions,
65 "gpt-4.1": APICompletions, "gpt-4o": APICompletions, "grok-code-fast-1": APICompletions,
66 "gpt-5.2": APIResponses, "gpt-5.2-codex": APIResponses,
67 }
68 for id, api := range kept {
69 m, err := FindModel("github-copilot", id)
70 if err != nil {
71 t.Errorf("previously supported Copilot model %q dropped: %v", id, err)
72 continue
73 }
74 if m.API != api {
75 t.Errorf("%s API = %q, want %q", id, m.API, api)
76 }
77 if (strings.HasSuffix(id, "-4.5") || strings.HasSuffix(id, "-4.6")) && (m.AdaptiveThinking || usesAdaptiveThinking(m)) {
78 t.Errorf("%s must not use adaptive thinking", id)
79 }
80 }
81 // GPT-5.4 nano is a Codex VS Code model, not a Copilot Chat choice.
82 if _, err := FindModel("github-copilot", "gpt-5.4-nano"); err == nil {
83 t.Error("Copilot catalog includes gpt-5.4-nano")
84 }
85}
86
87func TestConfigureCopilotModelAdaptiveDefaultsFollowKnownFamilies(t *testing.T) {
88 for id, want := range map[string]bool{
89 "claude-opus-4.6": false, "claude-sonnet-4.6": false, "claude-sonnet-4.5": false, "claude-haiku-4.5": false,
90 "claude-opus-4.7": true, "claude-opus-4.8": true, "claude-opus-5": true, "claude-sonnet-5": true, "claude-fable-5.1": true,
91 } {
92 m := Model{Provider: ProviderGitHubCopilot, ID: id}
93 configureCopilotModel(&m)
94 if m.API != APIAnthropicMessages || m.AdaptiveThinking != want {
95 t.Errorf("%s API=%q adaptive=%v, want adaptive=%v", id, m.API, m.AdaptiveThinking, want)
96 }
97 }
98 // Explicit metadata for a 4.6 row is preserved.
99 m := Model{Provider: ProviderGitHubCopilot, ID: "claude-opus-4.6", AdaptiveThinking: true}
100 configureCopilotModel(&m)
101 if !m.AdaptiveThinking {
102 t.Error("explicit adaptive metadata was cleared")
103 }
104}
105
106func TestCopilotCatalogOmitsRetiredMAIPickerRow(t *testing.T) {
107 if _, err := FindModel("github-copilot", "mai-code-1-flash-picker"); err == nil {
108 t.Error("retired mai-code-1-flash-picker row is still in the catalog")
109 }
110}