Files
DenozordecandCursor f45b2bed90
CI / changes (push) Successful in 6s
CI / commitlint (push) Skipped
CI / openapi (push) Failing after 40s
CI / web (push) Successful in 56s
CI / go (push) Successful in 1m9s
CI / bird2 (push) Successful in 14s
CI / release (push) Skipped
feat(lookup): enhance lookup functionality to support CIDR queries
Updated the lookup system to allow for CIDR queries in addition to IP and domain searches. This includes modifications to the API, frontend components, and documentation to reflect the new capabilities. The LookupSearchForm and LookupComponent were adjusted to accommodate CIDR input, and relevant tests were added to ensure functionality.

Co-authored-by: Cursor <[email protected]>
2026-07-31 13:20:18 +07:00

317 lines
8.0 KiB
Go

package lookup
import (
"context"
"errors"
"net/netip"
"testing"
"evobgp/internal/store"
)
func TestLookupIPEntryAndSnapshot(t *testing.T) {
m := store.NewMemory()
m.SeedDemo()
tenant, _, modIP, _, _ := m.DemoIDs()
cid := ""
comms, err := m.ListCommunities(tenant)
if err != nil || len(comms) == 0 {
t.Fatal("expected demo community")
}
cid = comms[0].ID
def := cid
if _, err := m.UpdateModule(tenant, modIP, &store.ModulePatch{DefaultCommunityID: &def}); err != nil {
t.Fatal(err)
}
entryComm := cid
e, err := m.CreateIPRangeEntry(tenant, modIP, &store.IPRangeEntry{
Prefix: "203.0.113.0/24",
CommunityID: &entryComm,
})
if err != nil {
t.Fatal(err)
}
if err := m.SetModulePrefixSnapshot(tenant, modIP, "hash1", []store.PrefixRow{
{Prefix: "203.0.113.0/24", CommunityID: &cid, Source: "ip_range"},
}); err != nil {
t.Fatal(err)
}
res, err := Lookup(context.Background(), m, tenant, "203.0.113.10")
if err != nil {
t.Fatal(err)
}
if res.QueryKind != KindIP || res.Normalized != "203.0.113.10" {
t.Fatalf("kind/normalized: %+v", res)
}
if !res.Matched || res.MatchCount < 2 {
t.Fatalf("expected entry+snapshot matches, got %+v", res)
}
var entryHit, snapHit bool
for _, hit := range res.Matches {
if hit.Layer == LayerEntry && hit.EntryID == e.ID {
entryHit = true
if hit.Community != "demo-comm" || hit.CommunityTitle != "Demo" {
t.Fatalf("entry community: %+v", hit)
}
}
if hit.Layer == LayerSnapshot && hit.MatchedValue == "203.0.113.0/24" {
snapHit = true
}
}
if !entryHit || !snapHit {
t.Fatalf("missing layers entry=%v snap=%v matches=%+v", entryHit, snapHit, res.Matches)
}
}
func TestLookupIPCommunityFallback(t *testing.T) {
m := store.NewMemory()
m.SeedDemo()
tenant, _, modIP, _, _ := m.DemoIDs()
comms, _ := m.ListCommunities(tenant)
cid := comms[0].ID
if _, err := m.UpdateModule(tenant, modIP, &store.ModulePatch{DefaultCommunityID: &cid}); err != nil {
t.Fatal(err)
}
if _, err := m.CreateIPRangeEntry(tenant, modIP, &store.IPRangeEntry{Prefix: "10.0.0.0/8"}); err != nil {
t.Fatal(err)
}
res, err := Lookup(context.Background(), m, tenant, "10.1.2.3")
if err != nil {
t.Fatal(err)
}
if !res.Matched {
t.Fatal("expected match")
}
found := false
for _, hit := range res.Matches {
if hit.Layer == LayerEntry {
found = true
if hit.CommunityID == nil || *hit.CommunityID != cid {
t.Fatalf("expected default community, got %+v", hit)
}
if hit.Community != "demo-comm" {
t.Fatalf("community value: %+v", hit)
}
}
}
if !found {
t.Fatal("no entry match")
}
}
func TestLookupDomainEntryAndSnapshot(t *testing.T) {
m := store.NewMemory()
m.SeedDemo()
tenant, _, _, _, _ := m.DemoIDs()
mod, err := m.CreateModule(tenant, &store.Module{
Type: "DOMAINS",
Name: "demo-domains",
Enabled: true,
})
if err != nil {
t.Fatal(err)
}
comms, _ := m.ListCommunities(tenant)
cid := comms[0].ID
e, err := m.CreateDomainEntry(tenant, mod.ID, &store.DomainEntry{
FQDN: "Example.COM.",
CommunityID: &cid,
})
if err != nil {
t.Fatal(err)
}
if err := m.SetModulePrefixSnapshot(tenant, mod.ID, "hash-d", []store.PrefixRow{
{Prefix: "198.51.100.1/32", CommunityID: &cid, Source: "domain"},
{Prefix: "203.0.113.9/32", CommunityID: &cid, Source: "other"},
}); err != nil {
t.Fatal(err)
}
noDNS := func(context.Context, string) ([]netip.Addr, error) { return nil, nil }
res, err := LookupWithResolver(context.Background(), m, tenant, "example.com", noDNS)
if err != nil {
t.Fatal(err)
}
if res.QueryKind != KindDomain || res.Normalized != "example.com" {
t.Fatalf("kind/normalized: %+v", res)
}
if !res.Matched {
t.Fatal("expected match")
}
var entryHit, snapHit, otherSnap bool
for _, hit := range res.Matches {
if hit.Layer == LayerEntry && hit.EntryID == e.ID {
entryHit = true
}
if hit.Layer == LayerSnapshot && hit.MatchedValue == "198.51.100.1/32" {
snapHit = true
}
if hit.MatchedValue == "203.0.113.9/32" {
otherSnap = true
}
}
if !entryHit || !snapHit {
t.Fatalf("entry=%v snap=%v matches=%+v", entryHit, snapHit, res.Matches)
}
if otherSnap {
t.Fatal("non-domain snapshot source should be excluded")
}
}
func TestLookupDomainResolvedIPAgainstRanges(t *testing.T) {
m := store.NewMemory()
m.SeedDemo()
tenant, _, modIP, _, _ := m.DemoIDs()
comms, _ := m.ListCommunities(tenant)
cid := comms[0].ID
if _, err := m.UpdateModule(tenant, modIP, &store.ModulePatch{DefaultCommunityID: &cid}); err != nil {
t.Fatal(err)
}
if _, err := m.CreateIPRangeEntry(tenant, modIP, &store.IPRangeEntry{
Prefix: "203.0.113.0/24",
CommunityID: &cid,
}); err != nil {
t.Fatal(err)
}
if err := m.SetModulePrefixSnapshot(tenant, modIP, "hash-r", []store.PrefixRow{
{Prefix: "203.0.113.0/24", CommunityID: &cid, Source: "ip_range"},
}); err != nil {
t.Fatal(err)
}
fake := func(_ context.Context, host string) ([]netip.Addr, error) {
if host != "google.com" {
t.Fatalf("unexpected host %q", host)
}
return []netip.Addr{netip.MustParseAddr("203.0.113.50")}, nil
}
res, err := LookupWithResolver(context.Background(), m, tenant, "google.com", fake)
if err != nil {
t.Fatal(err)
}
if res.QueryKind != KindDomain {
t.Fatalf("kind: %+v", res)
}
if len(res.ResolvedIPs) != 1 || res.ResolvedIPs[0] != "203.0.113.50" {
t.Fatalf("resolved_ips: %+v", res.ResolvedIPs)
}
if !res.Matched {
t.Fatalf("expected IP membership via resolve, got %+v", res)
}
var viaResolve bool
for _, hit := range res.Matches {
if hit.ResolvedIP == "203.0.113.50" && hit.MatchedValue == "203.0.113.0/24" {
viaResolve = true
}
}
if !viaResolve {
t.Fatalf("missing resolved-ip match: %+v", res.Matches)
}
}
func TestLookupCIDREntryAndSnapshot(t *testing.T) {
m := store.NewMemory()
m.SeedDemo()
tenant, _, modIP, _, _ := m.DemoIDs()
comms, _ := m.ListCommunities(tenant)
cid := comms[0].ID
e, err := m.CreateIPRangeEntry(tenant, modIP, &store.IPRangeEntry{
Prefix: "203.0.113.0/24",
CommunityID: &cid,
})
if err != nil {
t.Fatal(err)
}
if err := m.SetModulePrefixSnapshot(tenant, modIP, "hash-cidr", []store.PrefixRow{
{Prefix: "203.0.113.0/24", CommunityID: &cid, Source: "ip_range"},
}); err != nil {
t.Fatal(err)
}
res, err := Lookup(context.Background(), m, tenant, "203.0.113.0/24")
if err != nil {
t.Fatal(err)
}
if res.QueryKind != KindCIDR || res.Normalized != "203.0.113.0/24" {
t.Fatalf("kind/normalized: %+v", res)
}
if !res.Matched || res.MatchCount < 2 {
t.Fatalf("expected entry+snapshot, got %+v", res)
}
var entryHit, snapHit bool
for _, hit := range res.Matches {
if hit.Layer == LayerEntry && hit.EntryID == e.ID {
entryHit = true
}
if hit.Layer == LayerSnapshot && hit.MatchedValue == "203.0.113.0/24" {
snapHit = true
}
}
if !entryHit || !snapHit {
t.Fatalf("entry=%v snap=%v matches=%+v", entryHit, snapHit, res.Matches)
}
// Narrower query covered by wider entry.
res2, err := Lookup(context.Background(), m, tenant, "203.0.113.128/25")
if err != nil {
t.Fatal(err)
}
if res2.QueryKind != KindCIDR || !res2.Matched {
t.Fatalf("expected cover match: %+v", res2)
}
}
func TestLookupNoMatch(t *testing.T) {
m := store.NewMemory()
m.SeedDemo()
tenant, _, _, _, _ := m.DemoIDs()
res, err := Lookup(context.Background(), m, tenant, "192.0.2.1")
if err != nil {
t.Fatal(err)
}
if res.Matched || res.MatchCount != 0 || len(res.Matches) != 0 {
t.Fatalf("expected empty: %+v", res)
}
}
func TestLookupInvalid(t *testing.T) {
m := store.NewMemory()
m.SeedDemo()
tenant, _, _, _, _ := m.DemoIDs()
ctx := context.Background()
_, err := Lookup(ctx, m, tenant, "")
if !errors.Is(err, store.ErrInvalidInput) {
t.Fatalf("empty: %v", err)
}
_, err = Lookup(ctx, m, tenant, "not a host")
if !errors.Is(err, store.ErrInvalidInput) {
t.Fatalf("spaces: %v", err)
}
_, err = Lookup(ctx, m, tenant, "localhost")
if !errors.Is(err, store.ErrInvalidInput) {
t.Fatalf("single label: %v", err)
}
}
func TestNormalizeFQDN(t *testing.T) {
got, ok := normalizeFQDN(" Example.COM. ")
if !ok || got != "example.com" {
t.Fatalf("got %q ok=%v", got, ok)
}
}