Files
feedc0de fd5f5c454f
Validate and publish / Test image and chart (push) Successful in 2m33s
Validate and publish / Publish image and OCI chart (push) Successful in 22s
Support multiple service port forwardings
2026-08-31 14:13:56 +02:00

107 lines
3.5 KiB
Go

package routeros
import (
"context"
"encoding/json"
"io"
"net/http"
"slices"
"strings"
"testing"
)
func TestSyncOwnsOnlyPrefixedRules(t *testing.T) {
requests := make([]string, 0)
transport := roundTripFunc(func(request *http.Request) (*http.Response, error) {
username, password, ok := request.BasicAuth()
if !ok || username != "operator" || password != "secret" {
t.Errorf("wrong Basic Auth credentials")
}
requests = append(requests, request.Method+" "+request.URL.Path)
body := `{}`
if request.Method == http.MethodGet {
encoded, err := json.Marshal([]map[string]string{
{".id": "*1", "comment": "manual rule", "chain": "dstnat"},
{".id": "*2", "comment": "k8s-mikrotik/games/factorio", "chain": "dstnat", "action": "dst-nat", "protocol": "udp", "dst-port": "34197", "to-addresses": "192.168.0.99", "to-ports": "34197"},
{".id": "*3", "comment": "k8s-mikrotik/old/gone", "chain": "dstnat", "action": "dst-nat"},
{".id": "*4", "comment": "k8s-mikrotik/pending/service", "chain": "dstnat", "action": "dst-nat"},
})
if err != nil {
return nil, err
}
body = string(encoded)
}
return &http.Response{
StatusCode: http.StatusOK,
Status: "200 OK",
Header: make(http.Header),
Body: io.NopCloser(strings.NewReader(body)),
Request: request,
}, nil
})
client, err := NewClient("https://router.test", "operator", "secret", TLSConfig{})
if err != nil {
t.Fatal(err)
}
client.http.Transport = transport
desired := []Rule{
{Comment: "k8s-mikrotik/games/factorio", Protocol: "udp", DestinationPort: "34197", ToAddress: "192.168.0.17", ToPort: "34197"},
{Comment: "k8s-mikrotik/new/service", Protocol: "tcp", DestinationPort: "443", ToAddress: "192.168.0.18", ToPort: "8443"},
}
count, err := client.Sync(context.Background(), desired, map[string]struct{}{"k8s-mikrotik/pending/service": {}})
if err != nil {
t.Fatal(err)
}
if count != 3 {
t.Fatalf("managed count = %d, want 3", count)
}
got := slices.Clone(requests)
want := []string{
"GET /rest/ip/firewall/nat",
"PATCH /rest/ip/firewall/nat/*2",
"PUT /rest/ip/firewall/nat",
"DELETE /rest/ip/firewall/nat/*3",
}
if !slices.Equal(got, want) {
t.Fatalf("requests = %#v, want %#v", got, want)
}
for _, request := range got {
if strings.Contains(request, "*1") || strings.Contains(request, "*4") {
t.Fatalf("operator touched a manual or preserved rule: %s", request)
}
}
}
func TestCommentPreservedByServicePrefix(t *testing.T) {
preserve := map[string]struct{}{"k8s-mikrotik/default/traefik/": {}}
if !commentPreserved("k8s-mikrotik/default/traefik/http", preserve) {
t.Fatal("named rule was not preserved")
}
if commentPreserved("k8s-mikrotik/default/other/http", preserve) {
t.Fatal("unrelated rule was preserved")
}
}
type roundTripFunc func(*http.Request) (*http.Response, error)
func (function roundTripFunc) RoundTrip(request *http.Request) (*http.Response, error) {
return function(request)
}
func TestNewClientRejectsInvalidEndpointAndCA(t *testing.T) {
if _, err := NewClient("router.local", "u", "p", TLSConfig{}); err == nil {
t.Fatal("expected missing scheme to fail")
}
if _, err := NewClient("https://user:password@router.local", "u", "p", TLSConfig{}); err == nil {
t.Fatal("expected credentials in endpoint to fail")
}
if _, err := NewClient("https://router.local/rest", "u", "p", TLSConfig{}); err == nil {
t.Fatal("expected endpoint path to fail")
}
if _, err := NewClient("https://router.local", "u", "p", TLSConfig{CABundle: []byte("not PEM")}); err == nil {
t.Fatal("expected invalid CA to fail")
}
}