| 1 | package boot |
| 2 | |
| 3 | import ( |
| 4 | "context" |
| 5 | "os" |
| 6 | "path/filepath" |
| 7 | "reasonix/internal/config" |
| 8 | "reasonix/internal/event" |
| 9 | "strings" |
| 10 | "testing" |
| 11 | ) |
| 12 | |
| 13 | func TestBuildRestoresDeepSeekChatDefaultWithOneNotice(t *testing.T) { |
| 14 | home := isolateConfigHome(t) |
| 15 | t.Setenv("REASONIX_HOME", filepath.Join(home, "reasonix-home")) |
| 16 | userPath := config.UserConfigPath() |
| 17 | if err := os.MkdirAll(filepath.Dir(userPath), 0o700); err != nil { |
| 18 | t.Fatal(err) |
| 19 | } |
| 20 | if err := os.WriteFile(userPath, []byte(`config_version = 7 |
| 21 | default_model = "deepseek-flash/deepseek-v4-flash" |
| 22 | |
| 23 | [[providers]] |
| 24 | name = "deepseek-flash" |
| 25 | kind = "anthropic" |
| 26 | base_url = "https://api.deepseek.com/anthropic" |
| 27 | model = "deepseek-v4-flash" |
| 28 | api_key_env = "DEEPSEEK_API_KEY" |
| 29 | `), 0o600); err != nil { |
| 30 | t.Fatal(err) |
| 31 | } |
| 32 | |
| 33 | var notices []event.Event |
| 34 | sink := event.FuncSink(func(e event.Event) { |
| 35 | if e.Kind == event.Notice { |
| 36 | notices = append(notices, e) |
| 37 | } |
| 38 | }) |
| 39 | build := func() { |
| 40 | t.Helper() |
| 41 | ctrl, err := Build(context.Background(), Options{Sink: sink, WorkspaceRoot: t.TempDir()}) |
| 42 | if err != nil { |
| 43 | t.Fatalf("Build: %v", err) |
| 44 | } |
| 45 | ctrl.Close() |
| 46 | } |
| 47 | |
| 48 | build() |
| 49 | migrationNotices := 0 |
| 50 | for _, notice := range notices { |
| 51 | if notice.Text != "User configuration was upgraded." { |
| 52 | continue |
| 53 | } |
| 54 | migrationNotices++ |
| 55 | if notice.Level != event.LevelInfo || !strings.Contains(notice.Detail, "prefix-cache") { |
| 56 | t.Fatalf("migration notice = %+v", notice) |
| 57 | } |
| 58 | } |
| 59 | if migrationNotices != 1 { |
| 60 | t.Fatalf("migration notices = %d, want 1; got %+v", migrationNotices, notices) |
| 61 | } |
| 62 | raw, err := os.ReadFile(userPath) |
| 63 | if err != nil { |
| 64 | t.Fatal(err) |
| 65 | } |
| 66 | if !strings.Contains(string(raw), `kind = "openai"`) || |
| 67 | !strings.Contains(string(raw), `base_url = "https://api.deepseek.com"`) { |
| 68 | t.Fatalf("legacy DeepSeek protocol remained on disk:\n%s", raw) |
| 69 | } |
| 70 | |
| 71 | notices = nil |
| 72 | build() |
| 73 | for _, notice := range notices { |
| 74 | if strings.Contains(notice.Text, "User configuration was upgraded") { |
| 75 | t.Fatalf("second boot repeated migration notice: %+v", notice) |
| 76 | } |
| 77 | } |
| 78 | } |
| 79 | |
| 80 | func TestProviderEndpointRepairNoticeIsActionableAndContentFree(t *testing.T) { |
| 81 | var notices []event.Event |
| 82 | sink := event.FuncSink(func(e event.Event) { notices = append(notices, e) }) |
| 83 | emitUserConfigUpgradeNotice(sink, config.Default(), true, nil, []config.ProviderEndpointRepair{{ |
| 84 | ProviderName: "Deepseek2", |
| 85 | RequestURL: "https://api.deepseek.com/anthropic/v1/messages", |
| 86 | FromProtocol: "responses", |
| 87 | ToProtocol: "anthropic", |
| 88 | }}) |
| 89 | if len(notices) != 1 { |
| 90 | t.Fatalf("notices = %+v", notices) |
| 91 | } |
| 92 | notice := notices[0] |
| 93 | if notice.Text != "Provider connection settings were repaired." || |
| 94 | !strings.Contains(notice.Detail, "Deepseek2") || |
| 95 | !strings.Contains(notice.Detail, "Responses → Anthropic Messages") || |
| 96 | !strings.Contains(notice.Detail, "https://api.deepseek.com/anthropic/v1/messages") || |
| 97 | !strings.Contains(notice.Detail, "prefix-cache") { |
| 98 | t.Fatalf("repair notice = %+v", notice) |
| 99 | } |
| 100 | if strings.Contains(strings.ToLower(notice.Detail), "api_key") || |
| 101 | strings.Contains(strings.ToLower(notice.Detail), "authorization") { |
| 102 | t.Fatalf("repair notice leaked credential metadata: %+v", notice) |
| 103 | } |
| 104 | } |
| 105 | |
| 106 | func TestProviderEndpointRepairNoticeUsesConfiguredChinese(t *testing.T) { |
| 107 | cfg := config.Default() |
| 108 | cfg.Desktop.Language = "zh" |
| 109 | var notice event.Event |
| 110 | emitUserConfigUpgradeNotice(event.FuncSink(func(e event.Event) { notice = e }), cfg, false, nil, []config.ProviderEndpointRepair{{ |
| 111 | ProviderName: "Deepseek2", |
| 112 | RequestURL: "https://api.deepseek.com/anthropic/v1/messages", |
| 113 | FromProtocol: "responses", |
| 114 | ToProtocol: "anthropic", |
| 115 | }}) |
| 116 | if notice.Text != "已自动修复供应商连接设置。" || |
| 117 | !strings.Contains(notice.Detail, "供应商连接“Deepseek2”") || |
| 118 | !strings.Contains(notice.Detail, "协议由 Responses 调整为 Anthropic Messages") || |
| 119 | !strings.Contains(notice.Detail, "请求地址为 https://api.deepseek.com/anthropic/v1/messages") { |
| 120 | t.Fatalf("Chinese repair notice = %+v", notice) |
| 121 | } |
| 122 | } |
| 123 |