Scaffold ipswap: tray-driven Windows IP preset switcher
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>
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
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])
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user