@@ -78,4 +78,162 @@ defmodule Supabase.PostgREST.FilterBuilderTest do
7878 test "process empty or condition" do
7979 assert process_condition ( { :or , [ ] } ) == "or()"
8080 end
81+
82+ describe "complex filter combinations" do
83+ test "handles triple nested conditions" do
84+ result =
85+ process_condition (
86+ { :and ,
87+ [
88+ { :or , [ { :eq , "type" , "admin" } , { :eq , "type" , "moderator" } ] } ,
89+ { :not ,
90+ { :and ,
91+ [
92+ { :lt , "created_at" , "2023-01-01" } ,
93+ { :or , [ { :eq , "status" , "banned" } , { :eq , "status" , "suspended" } ] }
94+ ] } }
95+ ] }
96+ )
97+
98+ assert result ==
99+ "and(or(type.eq.admin,type.eq.moderator),not.and(created_at.lt.2023-01-01,or(status.eq.banned,status.eq.suspended)))"
100+ end
101+
102+ test "handles multiple not operators in sequence" do
103+ result = process_condition ( { :not , { :not , { :eq , "active" , true } } } )
104+ assert result == "not.not.active.eq.true"
105+ end
106+
107+ test "processes complex array conditions with modifiers" do
108+ result =
109+ process_condition (
110+ { :and ,
111+ [
112+ { :eq , "roles" , [ "admin" , "editor" ] , any: true } ,
113+ { :not , { :eq , "permissions" , [ "delete" , "archive" ] , all: true } }
114+ ] }
115+ )
116+
117+ assert result ==
118+ "and(roles=eq(any).{admin,editor},not.permissions=eq(all).{delete,archive})"
119+ end
120+
121+ test "handles mixed operator types" do
122+ result =
123+ process_condition (
124+ { :or ,
125+ [
126+ { :gte , "score" , 90 } ,
127+ { :and , [ { :between , "score" , [ 80 , 89 ] } , { :eq , "bonus" , true } ] } ,
128+ { :lte , "score" , 50 }
129+ ] }
130+ )
131+
132+ assert result == "or(score.gte.90,and(score.between.[80,89],bonus.eq.true),score.lte.50)"
133+ end
134+ end
135+
136+ describe "additional operators" do
137+ test "process lt (less than) operator" do
138+ assert process_condition ( { :lt , "price" , 100 } ) == "price.lt.100"
139+ end
140+
141+ test "process gte (greater than or equal) operator" do
142+ assert process_condition ( { :gte , "quantity" , 5 } ) == "quantity.gte.5"
143+ end
144+
145+ test "process lte (less than or equal) operator" do
146+ assert process_condition ( { :lte , "stock" , 10 } ) == "stock.lte.10"
147+ end
148+
149+ test "process neq (not equal) operator" do
150+ assert process_condition ( { :neq , "status" , "deleted" } ) == "status.neq.deleted"
151+ end
152+
153+ test "process like operator" do
154+ assert process_condition ( { :like , "email" , "%@example.com" } ) == "email.like.%@example.com"
155+ end
156+
157+ test "process ilike (case insensitive like) operator" do
158+ assert process_condition ( { :ilike , "name" , "%john%" } ) == "name.ilike.%john%"
159+ end
160+
161+ test "process in operator" do
162+ assert process_condition ( { :in , "category" , [ "electronics" , "books" , "clothing" ] } ) ==
163+ "category.in.(electronics,books,clothing)"
164+ end
165+
166+ test "process is operator for null checks" do
167+ assert process_condition ( { :is , "deleted_at" , nil } ) == "deleted_at.is.null"
168+ assert process_condition ( { :is , "verified" , true } ) == "verified.is.true"
169+ assert process_condition ( { :is , "archived" , false } ) == "archived.is.false"
170+ end
171+
172+ test "process between operator" do
173+ assert process_condition ( { :between , "age" , [ 18 , 65 ] } ) == "age.between.[18,65]"
174+ end
175+ end
176+
177+ describe "edge cases and error handling" do
178+ test "handles string values with special characters" do
179+ assert process_condition ( { :eq , "name" , "O'Brien" } ) == "name.eq.O'Brien"
180+
181+ assert process_condition ( { :eq , "description" , "Line 1\n Line 2" } ) ==
182+ "description.eq.Line 1\n Line 2"
183+ end
184+
185+ test "handles numeric values" do
186+ assert process_condition ( { :eq , "price" , 19.99 } ) == "price.eq.19.99"
187+ assert process_condition ( { :gt , "count" , 1000 } ) == "count.gt.1000"
188+ end
189+
190+ test "handles boolean values" do
191+ assert process_condition ( { :eq , "active" , true } ) == "active.eq.true"
192+ assert process_condition ( { :neq , "archived" , false } ) == "archived.neq.false"
193+ end
194+
195+ test "single element and/or conditions" do
196+ assert process_condition ( { :and , [ { :eq , "status" , "active" } ] } ) == "and(status.eq.active)"
197+ assert process_condition ( { :or , [ { :gt , "age" , 18 } ] } ) == "or(age.gt.18)"
198+ end
199+
200+ test "deeply nested empty conditions" do
201+ assert process_condition ( { :and , [ { :or , [ ] } , { :and , [ ] } ] } ) == "and(or(),and())"
202+ end
203+
204+ test "array values without modifiers default behavior" do
205+ # This should raise or have specific behavior - adjust based on actual implementation
206+ assert process_condition ( { :eq , "tags" , [ "tag1" , "tag2" ] } ) == "tags.eq.[tag1,tag2]"
207+ end
208+ end
209+
210+ describe "filter builder integration" do
211+ alias Supabase.Fetcher.Request
212+ alias Supabase.PostgREST.FilterBuilder
213+
214+ setup do
215+ client = Supabase . init_client! ( "http://example.com" , "test-key" )
216+ request = Request . new ( client )
217+ { :ok , % { request: request } }
218+ end
219+
220+ test "multiple filter functions create proper query params" , % { request: request } do
221+ result =
222+ request
223+ |> FilterBuilder . eq ( "status" , "active" )
224+ |> FilterBuilder . gt ( "age" , 18 )
225+ |> FilterBuilder . within ( "role" , [ "admin" , "moderator" ] )
226+
227+ assert % Request { query: query } = result
228+ assert { "status" , "eq.active" } in query
229+ assert { "age" , "gt.18" } in query
230+ assert { "role" , "in.(admin,moderator)" } in query
231+ end
232+
233+ test "filter functions handle nil values" , % { request: request } do
234+ result = FilterBuilder . is ( request , "deleted_at" , nil )
235+ assert % Request { query: query } = result
236+ assert { "deleted_at" , "is.null" } in query
237+ end
238+ end
81239end
0 commit comments