338 lines
9.1 KiB
Go
338 lines
9.1 KiB
Go
package store
|
|
|
|
import (
|
|
"testing"
|
|
|
|
pb "git.ipng.ch/ipng/nginx-logtail/proto/logtailpb"
|
|
)
|
|
|
|
// --- ParseStatusExpr ---
|
|
|
|
func TestParseStatusExprEQ(t *testing.T) {
|
|
n, op, ok := ParseStatusExpr("200")
|
|
if !ok || n != 200 || op != pb.StatusOp_EQ {
|
|
t.Fatalf("got (%d,%v,%v)", n, op, ok)
|
|
}
|
|
}
|
|
|
|
func TestParseStatusExprExplicitEQ(t *testing.T) {
|
|
for _, expr := range []string{"=200", "==200"} {
|
|
n, op, ok := ParseStatusExpr(expr)
|
|
if !ok || n != 200 || op != pb.StatusOp_EQ {
|
|
t.Fatalf("expr %q: got (%d,%v,%v)", expr, n, op, ok)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestParseStatusExprNE(t *testing.T) {
|
|
n, op, ok := ParseStatusExpr("!=200")
|
|
if !ok || n != 200 || op != pb.StatusOp_NE {
|
|
t.Fatalf("got (%d,%v,%v)", n, op, ok)
|
|
}
|
|
}
|
|
|
|
func TestParseStatusExprGE(t *testing.T) {
|
|
n, op, ok := ParseStatusExpr(">=400")
|
|
if !ok || n != 400 || op != pb.StatusOp_GE {
|
|
t.Fatalf("got (%d,%v,%v)", n, op, ok)
|
|
}
|
|
}
|
|
|
|
func TestParseStatusExprGT(t *testing.T) {
|
|
n, op, ok := ParseStatusExpr(">400")
|
|
if !ok || n != 400 || op != pb.StatusOp_GT {
|
|
t.Fatalf("got (%d,%v,%v)", n, op, ok)
|
|
}
|
|
}
|
|
|
|
func TestParseStatusExprLE(t *testing.T) {
|
|
n, op, ok := ParseStatusExpr("<=500")
|
|
if !ok || n != 500 || op != pb.StatusOp_LE {
|
|
t.Fatalf("got (%d,%v,%v)", n, op, ok)
|
|
}
|
|
}
|
|
|
|
func TestParseStatusExprLT(t *testing.T) {
|
|
n, op, ok := ParseStatusExpr("<500")
|
|
if !ok || n != 500 || op != pb.StatusOp_LT {
|
|
t.Fatalf("got (%d,%v,%v)", n, op, ok)
|
|
}
|
|
}
|
|
|
|
func TestParseStatusExprEmpty(t *testing.T) {
|
|
_, _, ok := ParseStatusExpr("")
|
|
if ok {
|
|
t.Fatal("expected ok=false for empty string")
|
|
}
|
|
}
|
|
|
|
func TestParseStatusExprInvalid(t *testing.T) {
|
|
for _, expr := range []string{"abc", "!=", ">=", "2xx"} {
|
|
_, _, ok := ParseStatusExpr(expr)
|
|
if ok {
|
|
t.Fatalf("expr %q: expected ok=false", expr)
|
|
}
|
|
}
|
|
}
|
|
|
|
// --- MatchesFilter ---
|
|
|
|
func compiledEQ(status int32) *CompiledFilter {
|
|
v := status
|
|
return CompileFilter(&pb.Filter{HttpResponse: &v, StatusOp: pb.StatusOp_EQ})
|
|
}
|
|
|
|
func TestMatchesFilterNil(t *testing.T) {
|
|
if !MatchesFilter(Tuple6{Website: "x"}, nil) {
|
|
t.Fatal("nil filter should match everything")
|
|
}
|
|
if !MatchesFilter(Tuple6{Website: "x"}, &CompiledFilter{}) {
|
|
t.Fatal("empty compiled filter should match everything")
|
|
}
|
|
}
|
|
|
|
func TestMatchesFilterExactWebsite(t *testing.T) {
|
|
w := "example.com"
|
|
cf := CompileFilter(&pb.Filter{Website: &w})
|
|
if !MatchesFilter(Tuple6{Website: "example.com"}, cf) {
|
|
t.Fatal("expected match")
|
|
}
|
|
if MatchesFilter(Tuple6{Website: "other.com"}, cf) {
|
|
t.Fatal("expected no match")
|
|
}
|
|
}
|
|
|
|
func TestMatchesFilterWebsiteRegex(t *testing.T) {
|
|
re := "gouda.*"
|
|
cf := CompileFilter(&pb.Filter{WebsiteRegex: &re})
|
|
if !MatchesFilter(Tuple6{Website: "gouda.example.com"}, cf) {
|
|
t.Fatal("expected match")
|
|
}
|
|
if MatchesFilter(Tuple6{Website: "edam.example.com"}, cf) {
|
|
t.Fatal("expected no match")
|
|
}
|
|
}
|
|
|
|
func TestMatchesFilterURIRegex(t *testing.T) {
|
|
re := "^/api/.*"
|
|
cf := CompileFilter(&pb.Filter{UriRegex: &re})
|
|
if !MatchesFilter(Tuple6{URI: "/api/users"}, cf) {
|
|
t.Fatal("expected match")
|
|
}
|
|
if MatchesFilter(Tuple6{URI: "/health"}, cf) {
|
|
t.Fatal("expected no match")
|
|
}
|
|
}
|
|
|
|
func TestMatchesFilterInvalidRegexMatchesNothing(t *testing.T) {
|
|
re := "[invalid"
|
|
cf := CompileFilter(&pb.Filter{WebsiteRegex: &re})
|
|
if MatchesFilter(Tuple6{Website: "anything"}, cf) {
|
|
t.Fatal("invalid regex should match nothing")
|
|
}
|
|
}
|
|
|
|
func TestMatchesFilterStatusEQ(t *testing.T) {
|
|
cf := compiledEQ(200)
|
|
if !MatchesFilter(Tuple6{Status: "200"}, cf) {
|
|
t.Fatal("expected match")
|
|
}
|
|
if MatchesFilter(Tuple6{Status: "404"}, cf) {
|
|
t.Fatal("expected no match")
|
|
}
|
|
}
|
|
|
|
func TestMatchesFilterStatusNE(t *testing.T) {
|
|
v := int32(200)
|
|
cf := CompileFilter(&pb.Filter{HttpResponse: &v, StatusOp: pb.StatusOp_NE})
|
|
if MatchesFilter(Tuple6{Status: "200"}, cf) {
|
|
t.Fatal("expected no match for 200 != 200")
|
|
}
|
|
if !MatchesFilter(Tuple6{Status: "404"}, cf) {
|
|
t.Fatal("expected match for 404 != 200")
|
|
}
|
|
}
|
|
|
|
func TestMatchesFilterStatusGE(t *testing.T) {
|
|
v := int32(400)
|
|
cf := CompileFilter(&pb.Filter{HttpResponse: &v, StatusOp: pb.StatusOp_GE})
|
|
if !MatchesFilter(Tuple6{Status: "400"}, cf) {
|
|
t.Fatal("expected match: 400 >= 400")
|
|
}
|
|
if !MatchesFilter(Tuple6{Status: "500"}, cf) {
|
|
t.Fatal("expected match: 500 >= 400")
|
|
}
|
|
if MatchesFilter(Tuple6{Status: "200"}, cf) {
|
|
t.Fatal("expected no match: 200 >= 400")
|
|
}
|
|
}
|
|
|
|
func TestMatchesFilterStatusLT(t *testing.T) {
|
|
v := int32(400)
|
|
cf := CompileFilter(&pb.Filter{HttpResponse: &v, StatusOp: pb.StatusOp_LT})
|
|
if !MatchesFilter(Tuple6{Status: "200"}, cf) {
|
|
t.Fatal("expected match: 200 < 400")
|
|
}
|
|
if MatchesFilter(Tuple6{Status: "400"}, cf) {
|
|
t.Fatal("expected no match: 400 < 400")
|
|
}
|
|
}
|
|
|
|
func TestMatchesFilterStatusNonNumeric(t *testing.T) {
|
|
cf := compiledEQ(200)
|
|
if MatchesFilter(Tuple6{Status: "ok"}, cf) {
|
|
t.Fatal("non-numeric status should not match")
|
|
}
|
|
}
|
|
|
|
func TestMatchesFilterCombined(t *testing.T) {
|
|
w := "example.com"
|
|
v := int32(200)
|
|
cf := CompileFilter(&pb.Filter{
|
|
Website: &w,
|
|
HttpResponse: &v,
|
|
StatusOp: pb.StatusOp_EQ,
|
|
})
|
|
if !MatchesFilter(Tuple6{Website: "example.com", Status: "200"}, cf) {
|
|
t.Fatal("expected match")
|
|
}
|
|
if MatchesFilter(Tuple6{Website: "other.com", Status: "200"}, cf) {
|
|
t.Fatal("expected no match: wrong website")
|
|
}
|
|
if MatchesFilter(Tuple6{Website: "example.com", Status: "404"}, cf) {
|
|
t.Fatal("expected no match: wrong status")
|
|
}
|
|
}
|
|
|
|
// --- IsTor label encoding and filtering ---
|
|
|
|
func TestEncodeLabelTupleRoundtripWithTor(t *testing.T) {
|
|
for _, isTor := range []bool{false, true} {
|
|
orig := Tuple6{Website: "a.com", Prefix: "1.2.3.0/24", URI: "/x", Status: "200", IsTor: isTor}
|
|
got := LabelTuple(EncodeTuple(orig))
|
|
if got != orig {
|
|
t.Errorf("roundtrip mismatch: got %+v, want %+v", got, orig)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestLabelTupleBackwardCompat(t *testing.T) {
|
|
// Old 4-field label (no is_tor field) should decode with IsTor=false.
|
|
label := "a.com\x001.2.3.0/24\x00/x\x00200"
|
|
got := LabelTuple(label)
|
|
if got.IsTor {
|
|
t.Errorf("expected IsTor=false for old label, got true")
|
|
}
|
|
if got.Website != "a.com" || got.Status != "200" {
|
|
t.Errorf("unexpected tuple: %+v", got)
|
|
}
|
|
}
|
|
|
|
func TestMatchesFilterTorYes(t *testing.T) {
|
|
cf := CompileFilter(&pb.Filter{Tor: pb.TorFilter_TOR_YES})
|
|
if !MatchesFilter(Tuple6{IsTor: true}, cf) {
|
|
t.Fatal("TOR_YES should match TOR tuple")
|
|
}
|
|
if MatchesFilter(Tuple6{IsTor: false}, cf) {
|
|
t.Fatal("TOR_YES should not match non-TOR tuple")
|
|
}
|
|
}
|
|
|
|
func TestMatchesFilterTorNo(t *testing.T) {
|
|
cf := CompileFilter(&pb.Filter{Tor: pb.TorFilter_TOR_NO})
|
|
if !MatchesFilter(Tuple6{IsTor: false}, cf) {
|
|
t.Fatal("TOR_NO should match non-TOR tuple")
|
|
}
|
|
if MatchesFilter(Tuple6{IsTor: true}, cf) {
|
|
t.Fatal("TOR_NO should not match TOR tuple")
|
|
}
|
|
}
|
|
|
|
func TestMatchesFilterTorAny(t *testing.T) {
|
|
cf := CompileFilter(&pb.Filter{Tor: pb.TorFilter_TOR_ANY})
|
|
if !MatchesFilter(Tuple6{IsTor: true}, cf) {
|
|
t.Fatal("TOR_ANY should match TOR tuple")
|
|
}
|
|
if !MatchesFilter(Tuple6{IsTor: false}, cf) {
|
|
t.Fatal("TOR_ANY should match non-TOR tuple")
|
|
}
|
|
}
|
|
|
|
// --- ASN label encoding, filtering, and DimensionLabel ---
|
|
|
|
func TestEncodeLabelTupleRoundtripWithASN(t *testing.T) {
|
|
for _, asn := range []int32{0, 1, 12345, 65535} {
|
|
orig := Tuple6{Website: "a.com", Prefix: "1.2.3.0/24", URI: "/x", Status: "200", ASN: asn}
|
|
got := LabelTuple(EncodeTuple(orig))
|
|
if got != orig {
|
|
t.Errorf("roundtrip mismatch for ASN=%d: got %+v, want %+v", asn, got, orig)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestLabelTupleBackwardCompatNoASN(t *testing.T) {
|
|
// 5-field label (no asn field) should decode with ASN=0.
|
|
label := "a.com\x001.2.3.0/24\x00/x\x00200\x000"
|
|
got := LabelTuple(label)
|
|
if got.ASN != 0 {
|
|
t.Errorf("expected ASN=0 for 5-field label, got %d", got.ASN)
|
|
}
|
|
}
|
|
|
|
func TestMatchesFilterAsnEQ(t *testing.T) {
|
|
n := int32(12345)
|
|
cf := CompileFilter(&pb.Filter{AsnNumber: &n})
|
|
if !MatchesFilter(Tuple6{ASN: 12345}, cf) {
|
|
t.Fatal("EQ should match equal ASN")
|
|
}
|
|
if MatchesFilter(Tuple6{ASN: 99999}, cf) {
|
|
t.Fatal("EQ should not match different ASN")
|
|
}
|
|
}
|
|
|
|
func TestMatchesFilterAsnNE(t *testing.T) {
|
|
n := int32(12345)
|
|
cf := CompileFilter(&pb.Filter{AsnNumber: &n, AsnOp: pb.StatusOp_NE})
|
|
if MatchesFilter(Tuple6{ASN: 12345}, cf) {
|
|
t.Fatal("NE should not match equal ASN")
|
|
}
|
|
if !MatchesFilter(Tuple6{ASN: 99999}, cf) {
|
|
t.Fatal("NE should match different ASN")
|
|
}
|
|
}
|
|
|
|
func TestMatchesFilterAsnGE(t *testing.T) {
|
|
n := int32(1000)
|
|
cf := CompileFilter(&pb.Filter{AsnNumber: &n, AsnOp: pb.StatusOp_GE})
|
|
if !MatchesFilter(Tuple6{ASN: 1000}, cf) {
|
|
t.Fatal("GE should match equal ASN")
|
|
}
|
|
if !MatchesFilter(Tuple6{ASN: 2000}, cf) {
|
|
t.Fatal("GE should match larger ASN")
|
|
}
|
|
if MatchesFilter(Tuple6{ASN: 500}, cf) {
|
|
t.Fatal("GE should not match smaller ASN")
|
|
}
|
|
}
|
|
|
|
func TestMatchesFilterAsnLT(t *testing.T) {
|
|
n := int32(64512)
|
|
cf := CompileFilter(&pb.Filter{AsnNumber: &n, AsnOp: pb.StatusOp_LT})
|
|
if !MatchesFilter(Tuple6{ASN: 1000}, cf) {
|
|
t.Fatal("LT should match smaller ASN")
|
|
}
|
|
if MatchesFilter(Tuple6{ASN: 64512}, cf) {
|
|
t.Fatal("LT should not match equal ASN")
|
|
}
|
|
if MatchesFilter(Tuple6{ASN: 65535}, cf) {
|
|
t.Fatal("LT should not match larger ASN")
|
|
}
|
|
}
|
|
|
|
func TestDimensionLabelASN(t *testing.T) {
|
|
got := DimensionLabel(Tuple6{ASN: 12345}, pb.GroupBy_ASN_NUMBER)
|
|
if got != "12345" {
|
|
t.Errorf("DimensionLabel ASN: got %q, want %q", got, "12345")
|
|
}
|
|
}
|