| 1 | package config |
| 2 | |
| 3 | import ( |
| 4 | "os" |
| 5 | "path/filepath" |
| 6 | "strings" |
| 7 | "testing" |
| 8 | ) |
| 9 | |
| 10 | // Windows-edited configurations keep CRLF. Every schema 7-9 file takes the |
| 11 | // lexical migration, so line endings must survive both the version-only fast |
| 12 | // path and the provider-splitting rewrite. |
| 13 | func TestOpenCodeGoV10MigrationPreservesCRLF(t *testing.T) { |
| 14 | plain := `# intro |
| 15 | config_version = 9 # c |
| 16 | default_model = "custom/text" |
| 17 | |
| 18 | [[providers]] |
| 19 | name = "custom" |
| 20 | kind = "openai" |
| 21 | base_url = "https://example.invalid/v1" |
| 22 | api_key_env = "CUSTOM_KEY" |
| 23 | model = "text" |
| 24 | ` |
| 25 | versionLater := `default_model = "custom/text" |
| 26 | config_version = 9 |
| 27 | |
| 28 | [[providers]] |
| 29 | name = "custom" |
| 30 | kind = "openai" |
| 31 | base_url = "https://example.invalid/v1" |
| 32 | api_key_env = "CUSTOM_KEY" |
| 33 | model = "text" |
| 34 | ` |
| 35 | for name, fixture := range map[string]struct { |
| 36 | body string |
| 37 | providers int |
| 38 | aliases bool |
| 39 | }{ |
| 40 | "version-only": {plain, 1, false}, |
| 41 | "version-not-first": {versionLater, 1, false}, |
| 42 | "official-deepseek": {strings.Replace(plain, "config_version = 9 # c", "config_version = 8", 1), 1, false}, |
| 43 | "opencode-go-split": {openCodeGoUpgradeFixture, 5, true}, |
| 44 | } { |
| 45 | t.Run(name, func(t *testing.T) { |
| 46 | path := filepath.Join(t.TempDir(), "config.toml") |
| 47 | crlf := strings.ReplaceAll(fixture.body, "\n", "\r\n") |
| 48 | if err := os.WriteFile(path, []byte(crlf), 0o600); err != nil { |
| 49 | t.Fatal(err) |
| 50 | } |
| 51 | changed, err := ApplyUserConfigUpgradesOnStartup(path) |
| 52 | if err != nil || !changed { |
| 53 | t.Fatalf("changed=%v err=%v", changed, err) |
| 54 | } |
| 55 | raw, _ := os.ReadFile(path) |
| 56 | text := string(raw) |
| 57 | if strings.Contains(strings.ReplaceAll(text, "\r\n", ""), "\n") || !strings.Contains(text, "\r\n") { |
| 58 | t.Fatalf("line endings not preserved:\n%q", text) |
| 59 | } |
| 60 | var cfg Config |
| 61 | if _, err := decodeTOMLFile(path, &cfg); err != nil { |
| 62 | t.Fatalf("migrated config no longer parses: %v\n%q", err, text) |
| 63 | } |
| 64 | if cfg.ConfigVersion != openCodeGoUpgradeVersion || len(cfg.Providers) != fixture.providers { |
| 65 | t.Fatalf("version=%d providers=%d\n%q", cfg.ConfigVersion, len(cfg.Providers), text) |
| 66 | } |
| 67 | if strings.Count(text, "config_version") != 1 { |
| 68 | t.Fatalf("duplicate config_version:\n%q", text) |
| 69 | } |
| 70 | if j := readOpenCodeGoJournal(path, raw); fixture.aliases && (j == nil || !j.Committed || len(j.Aliases) == 0) { |
| 71 | t.Fatalf("journal not activated for CRLF split: %+v", j) |
| 72 | } |
| 73 | if _, err := ApplyUserConfigUpgradesOnStartup(path); err != nil { |
| 74 | t.Fatalf("second startup: %v", err) |
| 75 | } |
| 76 | }) |
| 77 | } |
| 78 | } |
| 79 |