Files
anubis-mirror/lib/checker/remoteaddress/remoteaddress_test.go
2025-07-01 23:58:42 +00:00

239 lines
4.3 KiB
Go

package remoteaddress
import (
_ "embed"
"encoding/json"
"errors"
"net/http"
"net/netip"
"strings"
"testing"
"github.com/TecharoHQ/anubis/internal"
"github.com/TecharoHQ/anubis/lib/checker"
"github.com/TecharoHQ/anubis/lib/policy"
"github.com/TecharoHQ/anubis/lib/policy/config"
"github.com/gaissmai/bart"
)
func TestFactoryIsCheckerFactory(t *testing.T) {
if _, ok := (any(Factory{})).(checker.Factory); !ok {
t.Fatal("Factory is not an instance of checker.Factory")
}
}
func TestFactoryValidateConfig(t *testing.T) {
f := Factory{}
for _, tt := range []struct {
name string
data []byte
err error
}{
{
name: "basic valid",
data: []byte(`{
"remote_addresses": [
"1.1.1.1/32"
]
}`),
},
{
name: "not json",
data: []byte(`]`),
err: config.ErrUnparseableConfig,
},
{
name: "no cidr",
data: []byte(`{
"remote_addresses": []
}`),
err: ErrNoRemoteAddresses,
},
{
name: "bad cidr",
data: []byte(`{
"remote_addresses": [
"according to all laws of aviation"
]
}`),
err: config.ErrInvalidCIDR,
},
} {
t.Run(tt.name, func(t *testing.T) {
data := json.RawMessage(tt.data)
if err := f.ValidateConfig(data); !errors.Is(err, tt.err) {
t.Logf("want: %v", tt.err)
t.Logf("got: %v", err)
t.Fatal("validation didn't do what was expected")
}
})
}
}
func TestFactoryCreate(t *testing.T) {
f := Factory{}
for _, tt := range []struct {
name string
data []byte
err error
ip string
match bool
}{
{
name: "basic valid",
data: []byte(`{
"remote_addresses": [
"1.1.1.1/32"
]
}`),
ip: "1.1.1.1",
match: true,
},
{
name: "bad cidr",
data: []byte(`{
"remote_addresses": [
"according to all laws of aviation"
]
}`),
err: config.ErrUnparseableConfig,
},
} {
t.Run(tt.name, func(t *testing.T) {
data := json.RawMessage(tt.data)
impl, err := f.Create(data)
if !errors.Is(err, tt.err) {
t.Logf("want: %v", tt.err)
t.Logf("got: %v", err)
t.Fatal("creation didn't do what was expected")
}
if tt.err != nil {
return
}
r, err := http.NewRequest(http.MethodGet, "/", nil)
if err != nil {
t.Fatalf("can't make request: %v", err)
}
if tt.ip != "" {
r.Header.Add("X-Real-Ip", tt.ip)
}
match, err := impl.Check(r)
if tt.match != match {
t.Errorf("match: %v, wanted: %v", match, tt.match)
}
if err != nil && tt.err != nil && !errors.Is(err, tt.err) {
t.Errorf("err: %v, wanted: %v", err, tt.err)
}
if impl.Hash() == "" {
t.Error("hash method returns empty string")
}
})
}
}
func racFromCidrs(t *testing.T, inp []string) *Impl {
t.Helper()
var result Impl
result.prefixTable = new(bart.Lite)
result.hash = internal.FastHash(strings.Join(inp, ","))
for _, cidr := range inp {
pfx, err := netip.ParsePrefix(cidr)
if err != nil {
t.Errorf("prefix %q is invalid: %v", cidr, err)
continue
}
result.prefixTable.Insert(pfx)
}
return &result
}
func TestRemoteAddrChecker(t *testing.T) {
for _, tt := range []struct {
err error
name string
ip string
cidrs []string
ok bool
}{
{
name: "match_ipv4",
cidrs: []string{"0.0.0.0/0"},
ip: "1.1.1.1",
ok: true,
err: nil,
},
{
name: "match_ipv6",
cidrs: []string{"::/0"},
ip: "cafe:babe::",
ok: true,
err: nil,
},
{
name: "not_match_ipv4",
cidrs: []string{"1.1.1.1/32"},
ip: "1.1.1.2",
ok: false,
err: nil,
},
{
name: "not_match_ipv6",
cidrs: []string{"cafe:babe::/128"},
ip: "cafe:babe:4::/128",
ok: false,
err: nil,
},
{
name: "no_ip_set",
cidrs: []string{"::/0"},
ok: false,
err: policy.ErrMisconfiguration,
},
{
name: "invalid_ip",
cidrs: []string{"::/0"},
ip: "According to all natural laws of aviation",
ok: false,
err: policy.ErrMisconfiguration,
},
} {
t.Run(tt.name, func(t *testing.T) {
rac := racFromCidrs(t, tt.cidrs)
r, err := http.NewRequest(http.MethodGet, "/", nil)
if err != nil {
t.Fatalf("can't make request: %v", err)
}
if tt.ip != "" {
r.Header.Add("X-Real-Ip", tt.ip)
}
ok, err := rac.Check(r)
if tt.ok != ok {
t.Errorf("ok: %v, wanted: %v", ok, tt.ok)
}
if err != nil && tt.err != nil && !errors.Is(err, tt.err) {
t.Errorf("err: %v, wanted: %v", err, tt.err)
}
})
}
}