package settings import ( "io" "log" "os" "path/filepath" "strings" "testing" ) // The bootstrap rule (R-558): a box starts in the language the hub says, until the household picks. func newSettings(t *testing.T) *Settings { t.Helper() s, err := Load(filepath.Join(t.TempDir(), "settings.json"), log.New(io.Discard, "", 0)) if err != nil { t.Fatal(err) } return s } func TestLanguageBootstrapFromConfig(t *testing.T) { // 3. Nothing anywhere → Hungarian. s := newSettings(t) if got := s.GetLanguage(); got != "hu" { t.Errorf("no choice, no config = %q, want hu", got) } // 2. The config's language, for a box nobody has told yet. s.SetConfigLanguage("en") if got := s.GetLanguage(); got != "en" { t.Errorf("config en, no choice = %q, want en — a box created as English starts Hungarian", got) } // 1. An explicit choice WINS, in both directions. if err := s.SetLanguage("hu"); err != nil { t.Fatal(err) } if got := s.GetLanguage(); got != "hu" { t.Errorf("chose hu with config en = %q, want hu — the household's choice lost", got) } s2 := newSettings(t) s2.SetConfigLanguage("hu") if err := s2.SetLanguage("en"); err != nil { t.Fatal(err) } if got := s2.GetLanguage(); got != "en" { t.Errorf("chose en with config hu = %q, want en", got) } // An unsupported config value is ignored, not stored and not rendered. s3 := newSettings(t) s3.SetConfigLanguage("klingon") if got := s3.GetLanguage(); got != "hu" { t.Errorf("unsupported config language = %q, want hu", got) } } // The config default must NEVER be written to settings.json. If it were, a box would record a // choice the household never made, and the next config pull could no longer be distinguished from // a deliberate switch. func TestConfigLanguageIsNotPersisted(t *testing.T) { path := filepath.Join(t.TempDir(), "settings.json") s, err := Load(path, log.New(io.Discard, "", 0)) if err != nil { t.Fatal(err) } s.SetConfigLanguage("en") // Force a save through an unrelated setter, the way any ordinary use would. if err := s.SetBackupWindowStart("01:00"); err != nil { t.Fatal(err) } raw, err := os.ReadFile(path) if err != nil { t.Fatal(err) } if strings.Contains(string(raw), `"language"`) { t.Errorf("the config default was persisted into settings.json — it is now indistinguishable "+ "from a household's choice:\n%s", raw) } // And a REAL choice IS persisted, or the control proves nothing. if err := s.SetLanguage("en"); err != nil { t.Fatal(err) } raw, _ = os.ReadFile(path) if !strings.Contains(string(raw), `"language"`) { t.Error("a real choice was NOT persisted — the negative control above is meaningless") } } // SEAM-WIRING PIN (the class with four instances in this project): the setter exists, but is it // CALLED? `cmd/` is gitignored, so ripgrep skips main.go and a reviewer's search finds nothing — // which is exactly how a seam gets built and never wired here. func TestConfigLanguageIsWiredInMain(t *testing.T) { raw, err := os.ReadFile(filepath.Join("..", "..", "cmd", "controller", "main.go")) if err != nil { t.Fatalf("cannot read main.go to check the wiring: %v", err) } src := string(raw) if !strings.Contains(src, "sett.SetConfigLanguage(cfg.Customer.Language)") { t.Error("main.go never calls SetConfigLanguage — the bootstrap default is an unwired seam, " + "and every box would start Hungarian whatever the hub said") } // It must come from the CONFIG, not from a literal someone pasted to make this pass. if strings.Contains(src, `SetConfigLanguage("`) { t.Error("main.go passes a literal to SetConfigLanguage — it must pass cfg.Customer.Language") } }