query_test.go raw

   1  package graph
   2  
   3  import (
   4  	"testing"
   5  
   6  	"next.orly.dev/pkg/nostr/encoders/filter"
   7  )
   8  
   9  func TestQueryValidate(t *testing.T) {
  10  	validPubkey := "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
  11  
  12  	tests := []struct {
  13  		name    string
  14  		query   Query
  15  		wantErr error
  16  	}{
  17  		{
  18  			name:    "valid pp/out query",
  19  			query:   Query{Pubkey: validPubkey, Depth: 2, Edge: "pp", Direction: "out"},
  20  			wantErr: nil,
  21  		},
  22  		{
  23  			name:    "valid pp/in query",
  24  			query:   Query{Pubkey: validPubkey, Depth: 1, Edge: "pp", Direction: "in"},
  25  			wantErr: nil,
  26  		},
  27  		{
  28  			name:    "valid pe/both query",
  29  			query:   Query{Pubkey: validPubkey, Depth: 3, Edge: "pe", Direction: "both"},
  30  			wantErr: nil,
  31  		},
  32  		{
  33  			name:    "valid ee/out query",
  34  			query:   Query{Pubkey: validPubkey, Depth: 1, Edge: "ee", Direction: "out"},
  35  			wantErr: nil,
  36  		},
  37  		{
  38  			name:    "depth 0 defaults to 1",
  39  			query:   Query{Pubkey: validPubkey, Depth: 0, Edge: "pp", Direction: "out"},
  40  			wantErr: nil,
  41  		},
  42  		{
  43  			name:    "missing pubkey",
  44  			query:   Query{Edge: "pp", Direction: "out"},
  45  			wantErr: ErrMissingPubkey,
  46  		},
  47  		{
  48  			name:    "pubkey too short",
  49  			query:   Query{Pubkey: "abc123", Edge: "pp", Direction: "out"},
  50  			wantErr: ErrInvalidPubkey,
  51  		},
  52  		{
  53  			name:    "pubkey with invalid hex",
  54  			query:   Query{Pubkey: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdeg", Edge: "pp", Direction: "out"},
  55  			wantErr: ErrInvalidPubkey,
  56  		},
  57  		{
  58  			name:    "depth too high",
  59  			query:   Query{Pubkey: validPubkey, Depth: 17, Edge: "pp", Direction: "out"},
  60  			wantErr: ErrInvalidDepth,
  61  		},
  62  		{
  63  			name:    "missing edge",
  64  			query:   Query{Pubkey: validPubkey, Direction: "out"},
  65  			wantErr: ErrMissingEdge,
  66  		},
  67  		{
  68  			name:    "invalid edge",
  69  			query:   Query{Pubkey: validPubkey, Edge: "xx", Direction: "out"},
  70  			wantErr: ErrInvalidEdge,
  71  		},
  72  		{
  73  			name:    "missing direction",
  74  			query:   Query{Pubkey: validPubkey, Edge: "pp"},
  75  			wantErr: ErrMissingDirection,
  76  		},
  77  		{
  78  			name:    "invalid direction",
  79  			query:   Query{Pubkey: validPubkey, Edge: "pp", Direction: "left"},
  80  			wantErr: ErrInvalidDirection,
  81  		},
  82  	}
  83  
  84  	for _, tt := range tests {
  85  		t.Run(tt.name, func(t *testing.T) {
  86  			err := tt.query.Validate()
  87  			if tt.wantErr == nil {
  88  				if err != nil {
  89  					t.Errorf("unexpected error: %v", err)
  90  				}
  91  			} else {
  92  				if err != tt.wantErr {
  93  					t.Errorf("error = %v, want %v", err, tt.wantErr)
  94  				}
  95  			}
  96  		})
  97  	}
  98  }
  99  
 100  func TestQueryDefaults(t *testing.T) {
 101  	validPubkey := "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
 102  
 103  	q := Query{Pubkey: validPubkey, Depth: 0, Edge: "pp", Direction: "out"}
 104  	if err := q.Validate(); err != nil {
 105  		t.Fatalf("unexpected error: %v", err)
 106  	}
 107  	if q.Depth != 1 {
 108  		t.Errorf("Depth = %d, want 1 (default)", q.Depth)
 109  	}
 110  }
 111  
 112  func TestExtractFromFilter_Array(t *testing.T) {
 113  	validPubkey := "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
 114  
 115  	tests := []struct {
 116  		name       string
 117  		filterJSON string
 118  		wantQuery  bool
 119  		wantErr    bool
 120  		wantEdge   string
 121  		wantDir    string
 122  	}{
 123  		{
 124  			name:       "4-element array format",
 125  			filterJSON: `{"kinds":[1],"_graph":["` + validPubkey + `",2,"pp","out"]}`,
 126  			wantQuery:  true,
 127  			wantEdge:   "pp",
 128  			wantDir:    "out",
 129  		},
 130  		{
 131  			name:       "legacy object format (backward compat)",
 132  			filterJSON: `{"kinds":[1],"_graph":{"pubkey":"` + validPubkey + `","depth":2,"edge":"pp","direction":"out"}}`,
 133  			wantQuery:  true,
 134  			wantEdge:   "pp",
 135  			wantDir:    "out",
 136  		},
 137  		{
 138  			name:       "no graph query",
 139  			filterJSON: `{"kinds":[1,7]}`,
 140  			wantQuery:  false,
 141  		},
 142  		{
 143  			name:       "invalid array length",
 144  			filterJSON: `{"kinds":[1],"_graph":["` + validPubkey + `",2,"pp"]}`,
 145  			wantErr:    true,
 146  		},
 147  		{
 148  			name:       "invalid edge in array",
 149  			filterJSON: `{"kinds":[1],"_graph":["` + validPubkey + `",2,"xx","out"]}`,
 150  			wantErr:    true,
 151  		},
 152  	}
 153  
 154  	for _, tt := range tests {
 155  		t.Run(tt.name, func(t *testing.T) {
 156  			f := &filter.F{}
 157  			if _, err := f.Unmarshal([]byte(tt.filterJSON)); err != nil {
 158  				t.Fatalf("failed to unmarshal filter: %v", err)
 159  			}
 160  
 161  			q, err := ExtractFromFilter(f)
 162  
 163  			if tt.wantErr {
 164  				if err == nil {
 165  					t.Error("expected error, got nil")
 166  				}
 167  				return
 168  			}
 169  			if err != nil {
 170  				t.Errorf("unexpected error: %v", err)
 171  				return
 172  			}
 173  
 174  			if tt.wantQuery {
 175  				if q == nil {
 176  					t.Fatal("expected query, got nil")
 177  				}
 178  				if q.Edge != tt.wantEdge {
 179  					t.Errorf("Edge = %q, want %q", q.Edge, tt.wantEdge)
 180  				}
 181  				if q.Direction != tt.wantDir {
 182  					t.Errorf("Direction = %q, want %q", q.Direction, tt.wantDir)
 183  				}
 184  			} else if q != nil {
 185  				t.Errorf("expected nil query, got %+v", q)
 186  			}
 187  		})
 188  	}
 189  }
 190  
 191  func TestIsGraphQuery(t *testing.T) {
 192  	tests := []struct {
 193  		name       string
 194  		filterJSON string
 195  		want       bool
 196  	}{
 197  		{
 198  			name:       "filter with graph query",
 199  			filterJSON: `{"kinds":[1],"_graph":["abc",1,"pp","out"]}`,
 200  			want:       true,
 201  		},
 202  		{
 203  			name:       "filter without graph query",
 204  			filterJSON: `{"kinds":[1,7]}`,
 205  			want:       false,
 206  		},
 207  		{
 208  			name:       "filter with other extension",
 209  			filterJSON: `{"kinds":[1],"_custom":"value"}`,
 210  			want:       false,
 211  		},
 212  	}
 213  
 214  	for _, tt := range tests {
 215  		t.Run(tt.name, func(t *testing.T) {
 216  			f := &filter.F{}
 217  			if _, err := f.Unmarshal([]byte(tt.filterJSON)); err != nil {
 218  				t.Fatalf("failed to unmarshal filter: %v", err)
 219  			}
 220  			if got := IsGraphQuery(f); got != tt.want {
 221  				t.Errorf("IsGraphQuery() = %v, want %v", got, tt.want)
 222  			}
 223  		})
 224  	}
 225  }
 226  
 227  func TestQueryEdgeHelpers(t *testing.T) {
 228  	tests := []struct {
 229  		edge      string
 230  		direction string
 231  		pp, pe, ee bool
 232  		out, in_, both bool
 233  	}{
 234  		{"pp", "out", true, false, false, true, false, false},
 235  		{"pe", "in", false, true, false, false, true, false},
 236  		{"ee", "both", false, false, true, true, true, true},
 237  		{"pp", "both", true, false, false, true, true, true},
 238  	}
 239  
 240  	for _, tt := range tests {
 241  		q := Query{Edge: tt.edge, Direction: tt.direction}
 242  		if q.IsPubkeyPubkey() != tt.pp {
 243  			t.Errorf("edge=%s: IsPubkeyPubkey() = %v, want %v", tt.edge, q.IsPubkeyPubkey(), tt.pp)
 244  		}
 245  		if q.IsPubkeyEvent() != tt.pe {
 246  			t.Errorf("edge=%s: IsPubkeyEvent() = %v, want %v", tt.edge, q.IsPubkeyEvent(), tt.pe)
 247  		}
 248  		if q.IsEventEvent() != tt.ee {
 249  			t.Errorf("edge=%s: IsEventEvent() = %v, want %v", tt.edge, q.IsEventEvent(), tt.ee)
 250  		}
 251  		if q.IsOutbound() != tt.out {
 252  			t.Errorf("dir=%s: IsOutbound() = %v, want %v", tt.direction, q.IsOutbound(), tt.out)
 253  		}
 254  		if q.IsInbound() != tt.in_ {
 255  			t.Errorf("dir=%s: IsInbound() = %v, want %v", tt.direction, q.IsInbound(), tt.in_)
 256  		}
 257  		if q.IsBidirectional() != tt.both {
 258  			t.Errorf("dir=%s: IsBidirectional() = %v, want %v", tt.direction, q.IsBidirectional(), tt.both)
 259  		}
 260  	}
 261  }
 262