| 1 | package main |
| 2 | |
| 3 | import ( |
| 4 | "testing" |
| 5 | |
| 6 | "reasonix/internal/config" |
| 7 | "reasonix/internal/control" |
| 8 | ) |
| 9 | |
| 10 | func TestRemoveProviderAccessRejectsCustomProviderThatBecameOfficialBeforeCommit(t *testing.T) { |
| 11 | isolateDesktopUserDirs(t) |
| 12 | setDesktopTestCredential(t, "DEEPSEEK_API_KEY", "sk-test") |
| 13 | setDesktopTestCredential(t, "MIMO_API_KEY", "sk-test") |
| 14 | |
| 15 | cfg := config.Default() |
| 16 | cfg.DefaultModel = "deepseek-flash/deepseek-v4-flash" |
| 17 | cfg.Desktop.ProviderAccess = []string{"deepseek-flash", "mimo-pro"} |
| 18 | cfg.Providers = []config.ProviderEntry{ |
| 19 | {Name: "deepseek-flash", Kind: "openai", BaseURL: "https://proxy.example/v1", Model: "deepseek-v4-flash", APIKeyEnv: "DEEPSEEK_API_KEY"}, |
| 20 | {Name: "mimo-pro", Kind: "openai", BaseURL: "https://token-plan-cn.xiaomimimo.com/v1", Model: "mimo-v2.5-pro", APIKeyEnv: "MIMO_API_KEY"}, |
| 21 | } |
| 22 | if err := cfg.SaveTo(config.UserConfigPath()); err != nil { |
| 23 | t.Fatalf("save config: %v", err) |
| 24 | } |
| 25 | |
| 26 | app := NewApp() |
| 27 | ctrl := newBlockingSnapshotCtrl(control.New(control.Options{Label: "custom-deepseek"})) |
| 28 | tab := &WorkspaceTab{ID: "custom-deepseek", Scope: "global", model: cfg.DefaultModel, Ctrl: ctrl} |
| 29 | app.tabs = map[string]*WorkspaceTab{tab.ID: tab} |
| 30 | app.tabOrder = []string{tab.ID} |
| 31 | app.activeTabID = tab.ID |
| 32 | |
| 33 | fingerprint := app.Settings().ModelSettingsFingerprint |
| 34 | |
| 35 | unlock := config.LockUserConfigEdits() |
| 36 | changed := config.LoadForEdit(config.UserConfigPath()) |
| 37 | provider, ok := changed.Provider("deepseek-flash") |
| 38 | if !ok { |
| 39 | unlock() |
| 40 | t.Fatal("deepseek-flash provider missing") |
| 41 | } |
| 42 | provider.BaseURL = "https://api.deepseek.com/anthropic" |
| 43 | provider.Kind = "anthropic" |
| 44 | if err := changed.SaveTo(config.UserConfigPath()); err != nil { |
| 45 | unlock() |
| 46 | t.Fatalf("save overlapping config edit: %v", err) |
| 47 | } |
| 48 | unlock() |
| 49 | result := app.ApplyModelSettings(ModelSettingsChange{Kind: "provider_remove", Names: []string{"deepseek-flash"}, RequestID: "remove", ExpectedFingerprint: fingerprint}) |
| 50 | if result.Persisted || len(result.Issues) == 0 { |
| 51 | t.Fatal("stale removal deleted a custom provider that became official before commit") |
| 52 | } |
| 53 | got := config.LoadForEdit(config.UserConfigPath()) |
| 54 | provider, ok = got.Provider("deepseek-flash") |
| 55 | if !ok || officialProviderKindFromEntry(*provider) != "deepseek" { |
| 56 | t.Fatalf("updated official provider was not preserved: %+v/%v", provider, ok) |
| 57 | } |
| 58 | access := providerAccessSet(got.Desktop.ProviderAccess) |
| 59 | if !access["deepseek"] && !access["deepseek-flash"] { |
| 60 | t.Fatalf("provider access changed after rejected overlap: %+v", got.Desktop.ProviderAccess) |
| 61 | } |
| 62 | if ctrl.closeCount.Load() != 0 || tab.Ctrl != ctrl || tab.model != cfg.DefaultModel { |
| 63 | t.Fatalf("runtime mutated after rejected overlap: closes=%d ctrl=%T model=%q", ctrl.closeCount.Load(), tab.Ctrl, tab.model) |
| 64 | } |
| 65 | } |
| 66 |