package server import ( "strings" "testing" "housecarl/internal/api" ) // TestRename: renaming a zone, set or service changes what refers to it, // in the same change, which undoes as one. func TestRename(t *testing.T) { e := newEnv(t) e.srv.probationFor = 0 post := func(path string, body map[string]any) { t.Helper() if code := e.call(e.token, "POST", path, body, nil); code != 201 { t.Fatalf("POST %s: %d", path, code) } } post("/zones", map[string]any{"name": "wan", "interfaces": []string{"wan"}, "external": true}) post("/zones", map[string]any{"name": "dhcp", "interfaces": []string{"eth1"}}) post("/sets", map[string]any{"name": "trusted_ips", "entries": []string{"10.0.10.0/24"}}) post("/sets", map[string]any{"name": "guests", "dynamic": true}) post("/services", map[string]any{"name": "jarl", "entries": []string{"tcp/8053"}}) post("/rules", map[string]any{"chain": "forward", "in_zones": []string{"dhcp"}, "out_zones": []string{"wan"}, "sources": []string{"trusted_ips"}}) post("/rules", map[string]any{"chain": "input", "in_zones": []string{"dhcp"}, "services": []string{"jarl"}}) post("/snats", map[string]any{"out_zones": []string{"wan"}, "sources": []string{"trusted_ips"}}) post("/forwards", map[string]any{"name": "jf", "ports": "8096", "to": "10.0.10.57", "public_addresses": []string{"217.155.8.194"}, "sources": []string{"trusted_ips"}}) var res api.MutationResponse if code := e.call(e.token, "PATCH", "/zones/dhcp", map[string]any{"name": "lan"}, &res); code != 200 { t.Fatalf("renaming the zone: %d %+v", code, res) } zoneRename := res.HistoryID e.call(e.token, "PATCH", "/sets/trusted_ips", map[string]any{"name": "trusted"}, &res) setRename := res.HistoryID e.call(e.token, "PATCH", "/services/jarl", map[string]any{"name": "jarl-api"}, &res) serviceRename := res.HistoryID st := e.eng.State() if st.Zone("dhcp") != nil || st.Zone("lan") == nil { t.Fatalf("zones %+v", st.Zones) } for _, r := range st.Rules { if strings.Join(r.InZones, ",") != "lan" { t.Errorf("rule %d in zones %v", r.ID, r.InZones) } if r.Chain == "forward" && strings.Join(r.Sources, ",") != "trusted" { t.Errorf("rule %d sources %v", r.ID, r.Sources) } if r.Chain == "input" && strings.Join(r.Services, ",") != "jarl-api" { t.Errorf("rule %d services %v", r.ID, r.Services) } } if s := st.SNATs[0].Sources; strings.Join(s, ",") != "trusted" { t.Errorf("snat sources %v", s) } if s := st.Forwards[0].Sources; strings.Join(s, ",") != "trusted" { t.Errorf("forward sources %v", s) } if ps := e.eng.Problems(); len(ps) != 0 { for _, p := range ps { if p.Severity == "error" { t.Errorf("problem after renaming: %+v", p) } } } // Undoing the renames (newest first) puts the references back too. for _, id := range []int64{serviceRename, setRename, zoneRename} { if code := e.call(e.token, "POST", "/history/"+itoa(id)+"/revert", nil, nil); code != 200 { t.Fatalf("undo %d: %d", id, code) } } st = e.eng.State() for _, r := range st.Rules { if strings.Join(r.InZones, ",") != "dhcp" || r.Chain == "forward" && r.Sources[0] != "trusted_ips" || r.Chain == "input" && r.Services[0] != "jarl" { t.Errorf("after undo: rule %+v", r.Match) } } // A dynamic set keeps its name: tokens and programs go by it. if code := e.call(e.token, "PATCH", "/sets/guests", map[string]any{"name": "visitors"}, nil); code != 409 { t.Errorf("renaming a dynamic set: %d", code) } }