Files
mc-router/internal/automc/pgsync_test.go
T
claude-timemachine 657fca325e automc: pg LISTEN/NOTIFY route source + HTTP waker
Adds opt-in extension package internal/automc/ that:
- Subscribes to Postgres notifications on a 'servers' table and pushes
  route changes into server.Routes (no file I/O, no fsnotify).
- Provides a WakerFunc that POSTs to a configurable HTTP control plane
  (server-manager) and polls until state=running.

When AUTOMC_DSN is unset, Wire() is a no-op and the binary behaves
exactly like upstream itzg/mc-router. Single patch site in main.go
(import + 4-line call) keeps upstream rebases trivial.

See docs/AUTOMC.md for env vars and the expected DB schema/trigger.
2026-05-27 11:10:02 +02:00

104 lines
3.0 KiB
Go

package automc
import (
"sort"
"testing"
)
func TestDiff(t *testing.T) {
cases := []struct {
name string
prev, next map[string]route
wantAddHost []string
wantDel []string
}{
{
name: "empty to empty",
prev: map[string]route{},
next: map[string]route{},
wantAddHost: nil,
wantDel: nil,
},
{
name: "add one",
prev: map[string]route{},
next: map[string]route{"a.example.com": {name: "a", domain: "a.example.com", address: "10.0.0.1:25565"}},
wantAddHost: []string{"a.example.com"},
wantDel: nil,
},
{
name: "delete one",
prev: map[string]route{"a.example.com": {name: "a", domain: "a.example.com", address: "10.0.0.1:25565"}},
next: map[string]route{},
wantAddHost: nil,
wantDel: []string{"a.example.com"},
},
{
name: "address change",
prev: map[string]route{"a.example.com": {name: "a", domain: "a.example.com", address: "10.0.0.1:25565"}},
next: map[string]route{"a.example.com": {name: "a", domain: "a.example.com", address: "10.0.0.2:25565"}},
wantAddHost: []string{"a.example.com"},
wantDel: nil,
},
{
name: "name change with same address triggers re-register (waker rebind)",
prev: map[string]route{"a.example.com": {name: "a", domain: "a.example.com", address: "10.0.0.1:25565"}},
next: map[string]route{"a.example.com": {name: "b", domain: "a.example.com", address: "10.0.0.1:25565"}},
wantAddHost: []string{"a.example.com"},
wantDel: nil,
},
{
name: "no change",
prev: map[string]route{"a.example.com": {name: "a", domain: "a.example.com", address: "10.0.0.1:25565"}},
next: map[string]route{"a.example.com": {name: "a", domain: "a.example.com", address: "10.0.0.1:25565"}},
wantAddHost: nil,
wantDel: nil,
},
{
name: "mixed add + delete",
prev: map[string]route{
"a.example.com": {name: "a", domain: "a.example.com", address: "10.0.0.1:25565"},
"b.example.com": {name: "b", domain: "b.example.com", address: "10.0.0.2:25565"},
},
next: map[string]route{
"a.example.com": {name: "a", domain: "a.example.com", address: "10.0.0.1:25565"},
"c.example.com": {name: "c", domain: "c.example.com", address: "10.0.0.3:25565"},
},
wantAddHost: []string{"c.example.com"},
wantDel: []string{"b.example.com"},
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
add, del := diff(tc.prev, tc.next)
gotAdd := make([]string, 0, len(add))
for _, r := range add {
gotAdd = append(gotAdd, r.domain)
}
sort.Strings(gotAdd)
sort.Strings(del)
sort.Strings(tc.wantAddHost)
sort.Strings(tc.wantDel)
if !equalSlice(gotAdd, tc.wantAddHost) {
t.Errorf("add: got %v want %v", gotAdd, tc.wantAddHost)
}
if !equalSlice(del, tc.wantDel) {
t.Errorf("del: got %v want %v", del, tc.wantDel)
}
})
}
}
func equalSlice(a, b []string) bool {
if len(a) != len(b) {
return false
}
for i := range a {
if a[i] != b[i] {
return false
}
}
return true
}