23dcfb393f
Implements the design in claude.md as a building skeleton: pure Go, no cgo, cross-compiles to a single Windows .exe from Linux. Architecture follows the spec's deliberate split. The fast path is native — tray icon, grouped submenus, a Win32 MessageBox showing a live before/after diff, then netsh. The slow path is an embedded web editor served on a random loopback port and opened in the default browser. Two decisions worth recording: Reads use GetAdaptersAddresses, writes use netsh. The spec left the enumeration mechanism open; parsing `netsh show config` breaks on a non-English Windows because the output is localised. DNS static-vs-DHCP origin is not exposed by that API, so it comes from one registry read. The netsh command plan is built in portable code. That puts the delete-every- existing-address step — the one that stops secondary addresses leaking across switches — under test without needing a Windows box. The editor requires the session token in a header for mutations, not just the cookie: any page in the browser can make it POST to 127.0.0.1 with the cookie attached, but it cannot set a header. The updater refuses to install a release that publishes no SHA256. Not yet done: no group picker for export (the API supports it), no single-instance guard, and internal/server/web/app.css is reconstructed from the description in claude.md rather than the canonical apointless.css. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
64 lines
1.4 KiB
Go
64 lines
1.4 KiB
Go
package updater
|
|
|
|
import "testing"
|
|
|
|
func TestParseVersion(t *testing.T) {
|
|
cases := []struct {
|
|
in string
|
|
want Version
|
|
}{
|
|
{"v1.2.0", Version{1, 2, 0, ""}},
|
|
{"1.2.0", Version{1, 2, 0, ""}},
|
|
{"1.2", Version{1, 2, 0, ""}},
|
|
{"1", Version{1, 0, 0, ""}},
|
|
{"v1.2.0-rc1", Version{1, 2, 0, "rc1"}},
|
|
{"1.2.0+build7", Version{1, 2, 0, ""}},
|
|
{" v0.1.0 ", Version{0, 1, 0, ""}},
|
|
}
|
|
for _, tc := range cases {
|
|
got, err := ParseVersion(tc.in)
|
|
if err != nil {
|
|
t.Errorf("ParseVersion(%q) errored: %v", tc.in, err)
|
|
continue
|
|
}
|
|
if got != tc.want {
|
|
t.Errorf("ParseVersion(%q) = %+v, want %+v", tc.in, got, tc.want)
|
|
}
|
|
}
|
|
|
|
for _, in := range []string{"", "v", "abc", "1.2.3.4", "1.x", "-1.0.0"} {
|
|
if got, err := ParseVersion(in); err == nil {
|
|
t.Errorf("ParseVersion(%q) = %+v, want error", in, got)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestIsNewer(t *testing.T) {
|
|
yes := [][2]string{
|
|
{"v1.2.1", "v1.2.0"},
|
|
{"v1.3.0", "v1.2.9"},
|
|
{"v2.0.0", "v1.99.99"},
|
|
{"v1.2.0", "v1.2.0-rc1"},
|
|
}
|
|
for _, tc := range yes {
|
|
if !IsNewer(tc[0], tc[1]) {
|
|
t.Errorf("IsNewer(%q, %q) = false, want true", tc[0], tc[1])
|
|
}
|
|
}
|
|
|
|
no := [][2]string{
|
|
{"v1.2.0", "v1.2.0"},
|
|
{"v1.2.0", "v1.2.1"},
|
|
{"v1.2.0-rc1", "v1.2.0"},
|
|
// An unreadable tag must never trigger an update offer.
|
|
{"garbage", "v1.2.0"},
|
|
{"v1.2.0", "garbage"},
|
|
{"", "v1.0.0"},
|
|
}
|
|
for _, tc := range no {
|
|
if IsNewer(tc[0], tc[1]) {
|
|
t.Errorf("IsNewer(%q, %q) = true, want false", tc[0], tc[1])
|
|
}
|
|
}
|
|
}
|