You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
397 lines
8.4 KiB
397 lines
8.4 KiB
package graph |
|
|
|
import ( |
|
"testing" |
|
|
|
"git.mleku.dev/mleku/nostr/encoders/filter" |
|
) |
|
|
|
func TestQueryValidate(t *testing.T) { |
|
validSeed := "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" |
|
|
|
tests := []struct { |
|
name string |
|
query Query |
|
wantErr error |
|
}{ |
|
{ |
|
name: "valid follows query", |
|
query: Query{ |
|
Method: "follows", |
|
Seed: validSeed, |
|
Depth: 2, |
|
}, |
|
wantErr: nil, |
|
}, |
|
{ |
|
name: "valid followers query", |
|
query: Query{ |
|
Method: "followers", |
|
Seed: validSeed, |
|
}, |
|
wantErr: nil, |
|
}, |
|
{ |
|
name: "valid mentions query", |
|
query: Query{ |
|
Method: "mentions", |
|
Seed: validSeed, |
|
Depth: 1, |
|
}, |
|
wantErr: nil, |
|
}, |
|
{ |
|
name: "valid thread query", |
|
query: Query{ |
|
Method: "thread", |
|
Seed: validSeed, |
|
Depth: 10, |
|
}, |
|
wantErr: nil, |
|
}, |
|
{ |
|
name: "valid query with inbound refs", |
|
query: Query{ |
|
Method: "follows", |
|
Seed: validSeed, |
|
Depth: 2, |
|
InboundRefs: []RefSpec{ |
|
{Kinds: []int{7}, FromDepth: 1}, |
|
}, |
|
}, |
|
wantErr: nil, |
|
}, |
|
{ |
|
name: "valid query with multiple ref specs", |
|
query: Query{ |
|
Method: "follows", |
|
Seed: validSeed, |
|
InboundRefs: []RefSpec{ |
|
{Kinds: []int{7}, FromDepth: 1}, |
|
{Kinds: []int{6}, FromDepth: 1}, |
|
}, |
|
OutboundRefs: []RefSpec{ |
|
{Kinds: []int{1}, FromDepth: 0}, |
|
}, |
|
}, |
|
wantErr: nil, |
|
}, |
|
{ |
|
name: "missing method", |
|
query: Query{Seed: validSeed}, |
|
wantErr: ErrMissingMethod, |
|
}, |
|
{ |
|
name: "invalid method", |
|
query: Query{ |
|
Method: "invalid", |
|
Seed: validSeed, |
|
}, |
|
wantErr: ErrInvalidMethod, |
|
}, |
|
{ |
|
name: "missing seed", |
|
query: Query{ |
|
Method: "follows", |
|
}, |
|
wantErr: ErrMissingSeed, |
|
}, |
|
{ |
|
name: "seed too short", |
|
query: Query{ |
|
Method: "follows", |
|
Seed: "abc123", |
|
}, |
|
wantErr: ErrInvalidSeed, |
|
}, |
|
{ |
|
name: "seed with invalid characters", |
|
query: Query{ |
|
Method: "follows", |
|
Seed: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdeg", |
|
}, |
|
wantErr: ErrInvalidSeed, |
|
}, |
|
{ |
|
name: "depth too high", |
|
query: Query{ |
|
Method: "follows", |
|
Seed: validSeed, |
|
Depth: 17, |
|
}, |
|
wantErr: ErrDepthTooHigh, |
|
}, |
|
{ |
|
name: "empty ref spec kinds", |
|
query: Query{ |
|
Method: "follows", |
|
Seed: validSeed, |
|
InboundRefs: []RefSpec{ |
|
{Kinds: []int{}, FromDepth: 1}, |
|
}, |
|
}, |
|
wantErr: ErrEmptyRefSpecKinds, |
|
}, |
|
} |
|
|
|
for _, tt := range tests { |
|
t.Run(tt.name, func(t *testing.T) { |
|
err := tt.query.Validate() |
|
if tt.wantErr == nil { |
|
if err != nil { |
|
t.Errorf("unexpected error: %v", err) |
|
} |
|
} else { |
|
if err != tt.wantErr { |
|
t.Errorf("error = %v, want %v", err, tt.wantErr) |
|
} |
|
} |
|
}) |
|
} |
|
} |
|
|
|
func TestQueryDefaults(t *testing.T) { |
|
validSeed := "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" |
|
|
|
q := Query{ |
|
Method: "follows", |
|
Seed: validSeed, |
|
Depth: 0, // Should default to 1 |
|
} |
|
|
|
err := q.Validate() |
|
if err != nil { |
|
t.Fatalf("unexpected error: %v", err) |
|
} |
|
|
|
if q.Depth != 1 { |
|
t.Errorf("Depth = %d, want 1 (default)", q.Depth) |
|
} |
|
} |
|
|
|
func TestKindsAtDepth(t *testing.T) { |
|
q := Query{ |
|
Method: "follows", |
|
Seed: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", |
|
Depth: 3, |
|
InboundRefs: []RefSpec{ |
|
{Kinds: []int{7}, FromDepth: 0}, // From seed |
|
{Kinds: []int{6, 16}, FromDepth: 1}, // From depth 1 |
|
{Kinds: []int{9735}, FromDepth: 2}, // From depth 2 |
|
}, |
|
OutboundRefs: []RefSpec{ |
|
{Kinds: []int{1}, FromDepth: 1}, |
|
}, |
|
} |
|
|
|
// Test inbound kinds at depth 0 |
|
kinds0 := q.InboundKindsAtDepth(0) |
|
if !kinds0[7] || kinds0[6] || kinds0[9735] { |
|
t.Errorf("InboundKindsAtDepth(0) = %v, want only kind 7", kinds0) |
|
} |
|
|
|
// Test inbound kinds at depth 1 |
|
kinds1 := q.InboundKindsAtDepth(1) |
|
if !kinds1[7] || !kinds1[6] || !kinds1[16] || kinds1[9735] { |
|
t.Errorf("InboundKindsAtDepth(1) = %v, want kinds 7, 6, 16", kinds1) |
|
} |
|
|
|
// Test inbound kinds at depth 2 |
|
kinds2 := q.InboundKindsAtDepth(2) |
|
if !kinds2[7] || !kinds2[6] || !kinds2[16] || !kinds2[9735] { |
|
t.Errorf("InboundKindsAtDepth(2) = %v, want all kinds", kinds2) |
|
} |
|
|
|
// Test outbound kinds at depth 0 |
|
outKinds0 := q.OutboundKindsAtDepth(0) |
|
if len(outKinds0) != 0 { |
|
t.Errorf("OutboundKindsAtDepth(0) = %v, want empty", outKinds0) |
|
} |
|
|
|
// Test outbound kinds at depth 1 |
|
outKinds1 := q.OutboundKindsAtDepth(1) |
|
if !outKinds1[1] { |
|
t.Errorf("OutboundKindsAtDepth(1) = %v, want kind 1", outKinds1) |
|
} |
|
} |
|
|
|
func TestExtractFromFilter(t *testing.T) { |
|
tests := []struct { |
|
name string |
|
filterJSON string |
|
wantQuery bool |
|
wantErr bool |
|
}{ |
|
{ |
|
name: "filter with valid graph query", |
|
filterJSON: `{"kinds":[1],"_graph":{"method":"follows","seed":"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef","depth":2}}`, |
|
wantQuery: true, |
|
wantErr: false, |
|
}, |
|
{ |
|
name: "filter without graph query", |
|
filterJSON: `{"kinds":[1,7]}`, |
|
wantQuery: false, |
|
wantErr: false, |
|
}, |
|
{ |
|
name: "filter with invalid graph query (missing method)", |
|
filterJSON: `{"kinds":[1],"_graph":{"seed":"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"}}`, |
|
wantQuery: false, |
|
wantErr: true, |
|
}, |
|
{ |
|
name: "filter with complex graph query", |
|
filterJSON: `{"kinds":[0],"_graph":{"method":"follows","seed":"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef","depth":3,"inbound_refs":[{"kinds":[7],"from_depth":1}]}}`, |
|
wantQuery: true, |
|
wantErr: false, |
|
}, |
|
} |
|
|
|
for _, tt := range tests { |
|
t.Run(tt.name, func(t *testing.T) { |
|
f := &filter.F{} |
|
_, err := f.Unmarshal([]byte(tt.filterJSON)) |
|
if err != nil { |
|
t.Fatalf("failed to unmarshal filter: %v", err) |
|
} |
|
|
|
q, err := ExtractFromFilter(f) |
|
|
|
if tt.wantErr { |
|
if err == nil { |
|
t.Error("expected error, got nil") |
|
} |
|
return |
|
} |
|
|
|
if err != nil { |
|
t.Errorf("unexpected error: %v", err) |
|
return |
|
} |
|
|
|
if tt.wantQuery && q == nil { |
|
t.Error("expected query, got nil") |
|
} |
|
if !tt.wantQuery && q != nil { |
|
t.Errorf("expected nil query, got %+v", q) |
|
} |
|
}) |
|
} |
|
} |
|
|
|
func TestIsGraphQuery(t *testing.T) { |
|
tests := []struct { |
|
name string |
|
filterJSON string |
|
want bool |
|
}{ |
|
{ |
|
name: "filter with graph query", |
|
filterJSON: `{"kinds":[1],"_graph":{"method":"follows","seed":"abc"}}`, |
|
want: true, |
|
}, |
|
{ |
|
name: "filter without graph query", |
|
filterJSON: `{"kinds":[1,7]}`, |
|
want: false, |
|
}, |
|
{ |
|
name: "filter with other extension", |
|
filterJSON: `{"kinds":[1],"_custom":"value"}`, |
|
want: false, |
|
}, |
|
} |
|
|
|
for _, tt := range tests { |
|
t.Run(tt.name, func(t *testing.T) { |
|
f := &filter.F{} |
|
_, err := f.Unmarshal([]byte(tt.filterJSON)) |
|
if err != nil { |
|
t.Fatalf("failed to unmarshal filter: %v", err) |
|
} |
|
|
|
got := IsGraphQuery(f) |
|
if got != tt.want { |
|
t.Errorf("IsGraphQuery() = %v, want %v", got, tt.want) |
|
} |
|
}) |
|
} |
|
} |
|
|
|
func TestQueryHasRefs(t *testing.T) { |
|
tests := []struct { |
|
name string |
|
query Query |
|
hasInbound bool |
|
hasOutbound bool |
|
hasRefs bool |
|
}{ |
|
{ |
|
name: "no refs", |
|
query: Query{ |
|
Method: "follows", |
|
Seed: "abc", |
|
}, |
|
hasInbound: false, |
|
hasOutbound: false, |
|
hasRefs: false, |
|
}, |
|
{ |
|
name: "only inbound refs", |
|
query: Query{ |
|
Method: "follows", |
|
Seed: "abc", |
|
InboundRefs: []RefSpec{ |
|
{Kinds: []int{7}}, |
|
}, |
|
}, |
|
hasInbound: true, |
|
hasOutbound: false, |
|
hasRefs: true, |
|
}, |
|
{ |
|
name: "only outbound refs", |
|
query: Query{ |
|
Method: "follows", |
|
Seed: "abc", |
|
OutboundRefs: []RefSpec{ |
|
{Kinds: []int{1}}, |
|
}, |
|
}, |
|
hasInbound: false, |
|
hasOutbound: true, |
|
hasRefs: true, |
|
}, |
|
{ |
|
name: "both refs", |
|
query: Query{ |
|
Method: "follows", |
|
Seed: "abc", |
|
InboundRefs: []RefSpec{ |
|
{Kinds: []int{7}}, |
|
}, |
|
OutboundRefs: []RefSpec{ |
|
{Kinds: []int{1}}, |
|
}, |
|
}, |
|
hasInbound: true, |
|
hasOutbound: true, |
|
hasRefs: true, |
|
}, |
|
} |
|
|
|
for _, tt := range tests { |
|
t.Run(tt.name, func(t *testing.T) { |
|
if got := tt.query.HasInboundRefs(); got != tt.hasInbound { |
|
t.Errorf("HasInboundRefs() = %v, want %v", got, tt.hasInbound) |
|
} |
|
if got := tt.query.HasOutboundRefs(); got != tt.hasOutbound { |
|
t.Errorf("HasOutboundRefs() = %v, want %v", got, tt.hasOutbound) |
|
} |
|
if got := tt.query.HasRefs(); got != tt.hasRefs { |
|
t.Errorf("HasRefs() = %v, want %v", got, tt.hasRefs) |
|
} |
|
}) |
|
} |
|
}
|
|
|