Default the update repo to the Gitea host

Settles the first open question in claude.md: the update endpoint is
gitea.apointless.space/bsncubed/ipswap. It was left empty — which disables the
checker — only because the host was unconfirmed.

Load unmarshals over the defaults, so an absent update_repo takes the new
default while an explicitly empty one stays empty. That distinction is the off
switch, and forks depend on it, so it is now pinned by a test.

Adds the config package's first tests while here: the defaulting rules above,
the repair path for a nonsensical mask style or poll interval, and the
guarantee that a corrupt config.json still yields usable defaults rather than
stopping the app from starting.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-20 13:37:38 +10:00
parent 23dcfb393f
commit d93ae63156
3 changed files with 112 additions and 6 deletions
+6 -2
View File
@@ -64,7 +64,7 @@ type Settings struct {
// UpdateRepo is the Gitea repo polled for releases, as
// "https://host/owner/repo". Empty disables the update checker entirely,
// which is the default until the host is confirmed.
// which is what a fork or a private build should set it to.
UpdateRepo string `json:"update_repo"`
// ActivePollSeconds is how often the live adapter state is re-read to work
@@ -73,12 +73,16 @@ type Settings struct {
}
// Default is the settings a fresh install starts with.
// DefaultUpdateRepo is where release builds look for updates. Set it to "" in
// config.json (or clear the field in Settings) to turn the check off entirely.
const DefaultUpdateRepo = "https://gitea.apointless.space/bsncubed/ipswap"
func Default() Settings {
return Settings{
MaskStyle: MaskPrefix,
StartWithWindows: false,
CheckUpdates: true,
UpdateRepo: "",
UpdateRepo: DefaultUpdateRepo,
ActivePollSeconds: 30,
}
}
+98
View File
@@ -0,0 +1,98 @@
package config
import (
"os"
"path/filepath"
"testing"
)
func TestLoadMissingFileReturnsDefaults(t *testing.T) {
s, err := Load(filepath.Join(t.TempDir(), "config.json"))
if err != nil {
t.Fatalf("a missing config must not be an error: %v", err)
}
if s != Default() {
t.Errorf("expected defaults, got %+v", s)
}
if s.UpdateRepo != DefaultUpdateRepo {
t.Errorf("update repo default = %q, want %q", s.UpdateRepo, DefaultUpdateRepo)
}
}
// An absent field takes the default, but an explicitly empty one must stay
// empty — that is how the update check is turned off.
func TestExplicitEmptyUpdateRepoDisablesTheCheck(t *testing.T) {
path := filepath.Join(t.TempDir(), "config.json")
if err := os.WriteFile(path, []byte(`{"update_repo": ""}`), 0o644); err != nil {
t.Fatal(err)
}
s, err := Load(path)
if err != nil {
t.Fatal(err)
}
if s.UpdateRepo != "" {
t.Errorf("an explicit empty update_repo must be preserved, got %q", s.UpdateRepo)
}
// The rest of the file was absent, so it should still be defaulted.
if s.ActivePollSeconds != Default().ActivePollSeconds {
t.Errorf("unset fields should keep their defaults, got %+v", s)
}
}
func TestLoadRepairsNonsense(t *testing.T) {
path := filepath.Join(t.TempDir(), "config.json")
if err := os.WriteFile(path, []byte(`{"mask_style":"wat","active_poll_seconds":0}`), 0o644); err != nil {
t.Fatal(err)
}
s, err := Load(path)
if err != nil {
t.Fatal(err)
}
if s.MaskStyle != MaskPrefix {
t.Errorf("an unknown mask style should fall back to prefix, got %q", s.MaskStyle)
}
if s.ActivePollSeconds != 30 {
t.Errorf("a zero poll interval should be repaired, got %d", s.ActivePollSeconds)
}
}
// A corrupt config must not stop the app from starting.
func TestLoadCorruptFileReturnsDefaultsAndError(t *testing.T) {
path := filepath.Join(t.TempDir(), "config.json")
if err := os.WriteFile(path, []byte(`{not json`), 0o644); err != nil {
t.Fatal(err)
}
s, err := Load(path)
if err == nil {
t.Error("expected an error for a corrupt config")
}
if s != Default() {
t.Errorf("a corrupt config should still yield usable defaults, got %+v", s)
}
}
func TestSaveRoundTrip(t *testing.T) {
path := filepath.Join(t.TempDir(), "nested", "config.json")
want := Default()
want.MaskStyle = MaskDotted
want.StartWithWindows = true
want.UpdateRepo = "https://example.com/o/r"
if err := want.Save(path); err != nil {
t.Fatal(err)
}
got, err := Load(path)
if err != nil {
t.Fatal(err)
}
if got != want {
t.Errorf("round trip changed the settings:\n got %+v\nwant %+v", got, want)
}
if !got.DottedMasks() {
t.Error("DottedMasks should be true for MaskDotted")
}
}