1 parent
289a8cc2d8
commit
66f36cfac6
19 files changed
+1905
-817
No files matched your search
@@ -0,0 +1,181 @@
|
||||
package console
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
dynfake "k8s.io/client-go/dynamic/fake"
|
||||
|
||||
"git.k8s-git-k6n.net/mats/fancy-dns/internal/config"
|
||||
"git.k8s-git-k6n.net/mats/fancy-dns/internal/kubeedit"
|
||||
"git.k8s-git-k6n.net/mats/fancy-dns/internal/kubesource"
|
||||
)
|
||||
|
||||
// validator builds a console whose cluster holds the given groups and whose
|
||||
// resolver reports one group of its own, defined in its config file.
|
||||
func validator(t *testing.T, saved ...config.Group) *Console {
|
||||
t.Helper()
|
||||
cl := dynfake.NewSimpleDynamicClientWithCustomListKinds(runtime.NewScheme(),
|
||||
map[schema.GroupVersionResource]string{
|
||||
kubesource.ZoneGVR: "DNSZoneList", kubesource.GroupGVR: "ClientGroupList",
|
||||
kubesource.ListGVR: "BlockListList", kubesource.OverrideGVR: "AddressOverrideList",
|
||||
kubesource.RuleGVR: "DomainRuleList",
|
||||
})
|
||||
ed := kubeedit.New(cl, "dns")
|
||||
if len(saved) > 0 {
|
||||
if _, err := ed.SyncGroups(context.Background(), saved, nil, "test"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
resolver := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.URL.Path {
|
||||
case "/api/clients":
|
||||
groups := []map[string]any{{
|
||||
"name": "from-file", "policy": "allow", "source": "",
|
||||
"match": map[string]any{"cidrs": []string{"172.16.0.0/12"}},
|
||||
}}
|
||||
for _, g := range saved {
|
||||
groups = append(groups, map[string]any{"name": g.Name, "policy": g.Policy, "source": "kubernetes", "match": g.Match})
|
||||
}
|
||||
json.NewEncoder(w).Encode(map[string]any{"groups": groups, "default_policy": "allow", "default_rate_limit": 50})
|
||||
case "/api/upstreams":
|
||||
json.NewEncoder(w).Encode(map[string]any{"servers": []map[string]any{{"addr": "1.1.1.1:53"}}})
|
||||
default:
|
||||
http.NotFound(w, r)
|
||||
}
|
||||
}))
|
||||
t.Cleanup(resolver.Close)
|
||||
rt, err := NewRuntime(resolver.URL, "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return &Console{opt: Options{Runtime: rt, Editor: ed}, log: slog.Default()}
|
||||
}
|
||||
|
||||
func validate(t *testing.T, c *Console, body string) validateResponse {
|
||||
t.Helper()
|
||||
rec := httptest.NewRecorder()
|
||||
c.handleClientsValidate(rec, httptest.NewRequest(http.MethodPost, "/api/clients/validate", strings.NewReader(body)))
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("status %d: %s", rec.Code, rec.Body)
|
||||
}
|
||||
var out validateResponse
|
||||
if err := json.Unmarshal(rec.Body.Bytes(), &out); err != nil {
|
||||
t.Fatalf("%v: %s", err, rec.Body)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func hasField(list []string, f string) bool {
|
||||
for _, s := range list {
|
||||
if s == f {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func errFields(r validateResponse) []string {
|
||||
var out []string
|
||||
for _, e := range r.Errors {
|
||||
out = append(out, e.Field)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func TestValidateReportsEveryErrorAtOnce(t *testing.T) {
|
||||
c := validator(t)
|
||||
r := validate(t, c, `{"group":{"name":"Bad Name","policy":"sometimes","match":{"cidrs":["10.0.0.0/33"]},"rate_limit":-1}}`)
|
||||
if r.OK {
|
||||
t.Fatal("a group with four problems validated")
|
||||
}
|
||||
for _, want := range []string{"name", "policy", "match.cidrs", "rate_limit"} {
|
||||
if !hasField(errFields(r), want) {
|
||||
t.Errorf("no error for %q; got %+v", want, r.Errors)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateAcceptsAGoodGroupAndExplainsIt(t *testing.T) {
|
||||
c := validator(t)
|
||||
r := validate(t, c, `{"group":{"name":"kids","policy":"allow","match":{"cidrs":["10.0.5.0/24"]},"allow_domains":["*.school.example"]}}`)
|
||||
if !r.OK || len(r.Errors) != 0 {
|
||||
t.Fatalf("errors: %+v", r.Errors)
|
||||
}
|
||||
if r.Position != 2 || r.Total != 2 {
|
||||
t.Errorf("a new group goes last: position %d of %d", r.Position, r.Total)
|
||||
}
|
||||
if len(r.Summary) < 3 {
|
||||
t.Errorf("summary = %v", r.Summary)
|
||||
}
|
||||
// 0 rate limit under a default of 50 is the surprise worth flagging.
|
||||
found := false
|
||||
for _, w := range r.Warnings {
|
||||
found = found || w.Field == "rate_limit"
|
||||
}
|
||||
if !found {
|
||||
t.Errorf("warnings = %+v, want the rate-limit exemption", r.Warnings)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateEditingKeepsThePosition(t *testing.T) {
|
||||
a := config.Group{Name: "a", Policy: "allow", Match: config.Match{CIDRs: []string{"10.0.0.0/8"}}}
|
||||
b := config.Group{Name: "b", Policy: "allow", Match: config.Match{CIDRs: []string{"192.168.0.0/16"}}}
|
||||
c := validator(t, a, b)
|
||||
r := validate(t, c, `{"group":{"name":"a","policy":"deny","match":{"cidrs":["10.0.0.0/8"]}}}`)
|
||||
if r.Position != 2 || r.Total != 3 { // from-file, a, b
|
||||
t.Errorf("position %d of %d", r.Position, r.Total)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateRefusesAFileGroupName(t *testing.T) {
|
||||
c := validator(t)
|
||||
r := validate(t, c, `{"group":{"name":"from-file","policy":"deny","match":{"cidrs":["10.0.0.0/8"]}}}`)
|
||||
if r.OK || !hasField(errFields(r), "name") {
|
||||
t.Errorf("a group the resolver's file owns validated: %+v", r)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateFlagsAGroupBuriedUnderAWiderOne(t *testing.T) {
|
||||
wide := config.Group{Name: "wide", Policy: "allow", Match: config.Match{CIDRs: []string{"10.0.0.0/8"}}}
|
||||
c := validator(t, wide)
|
||||
r := validate(t, c, `{"group":{"name":"narrow","policy":"deny","match":{"cidrs":["10.1.0.0/16"]}}}`)
|
||||
if !r.OK || !r.Unreachable {
|
||||
t.Errorf("ok=%v unreachable=%v warnings=%+v", r.OK, r.Unreachable, r.Warnings)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateTestIP(t *testing.T) {
|
||||
g := config.Group{Name: "lan", Policy: "deny", Match: config.Match{CIDRs: []string{"10.0.0.0/8"}}}
|
||||
c := validator(t, g)
|
||||
r := validate(t, c, `{"group":{"name":"lan","policy":"deny","match":{"cidrs":["10.0.0.0/8"]}},"test_ip":"10.1.2.3"}`)
|
||||
if r.Test == nil || r.Test.Group != "lan" || r.Test.Default {
|
||||
t.Errorf("test = %+v", r.Test)
|
||||
}
|
||||
r = validate(t, c, `{"group":{"name":"lan","policy":"deny","match":{"cidrs":["10.0.0.0/8"]}},"test_ip":"8.8.8.8"}`)
|
||||
if r.Test == nil || !r.Test.Default {
|
||||
t.Errorf("test = %+v", r.Test)
|
||||
}
|
||||
r = validate(t, c, `{"group":{"name":"lan","policy":"deny","match":{"cidrs":["10.0.0.0/8"]}},"test_ip":"nope"}`)
|
||||
if r.Test == nil || r.Test.Valid {
|
||||
t.Errorf("test = %+v", r.Test)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateUnreachableResolverIsABadGateway(t *testing.T) {
|
||||
c := validator(t)
|
||||
c.opt.Runtime, _ = NewRuntime("http://127.0.0.1:1", "")
|
||||
rec := httptest.NewRecorder()
|
||||
c.handleClientsValidate(rec, httptest.NewRequest(http.MethodPost, "/", strings.NewReader(`{"group":{"name":"x"}}`)))
|
||||
if rec.Code != http.StatusBadGateway {
|
||||
t.Errorf("status %d", rec.Code)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user