ParseFilter: splitting von :multi über Text::ParseWords
[kivitendo-erp.git] / t / controllers / helpers / parse_filter.t
1 use lib 't';
2
3 use Test::More tests => 31;
4 use Test::Deep;
5 use Data::Dumper;
6
7 use_ok 'Support::TestSetup';
8 use_ok 'SL::Controller::Helper::ParseFilter';
9
10 use SL::DB::OrderItem;
11
12 undef *::any; # Test::Deep exports any (for junctions) and MoreCommon exports any (like in List::Moreutils)
13
14 Support::TestSetup::login();
15 my ($filter, $expected);
16
17 sub test ($$$;%) {
18   my ($filter, $expect, $msg, %params) = @_;
19   my $target = delete $params{target};
20   my $args = { parse_filter($filter, %params) };
21   my $got  = $args; $target ||= '';
22      $got = $filter             if $target =~ /filter/;
23      $got = $params{launder_to} if $target =~ /launder/;
24   cmp_deeply(
25     $got,
26     $expect,
27     $msg,
28   ) or do {
29     print STDERR "expected => ", Dumper($expect), "\ngot: => ", Dumper($got), $/;
30   }
31 }
32
33 test { }, {
34 }, 'minimal test';
35
36 test {
37   name => 'Test',
38   whut => 'moof',
39 }, {
40   query => bag(
41     name => 'Test',
42     whut => 'moof'
43   ),
44 }, 'basic test';
45
46 test {
47   customer => {
48     name => 'rainer',
49   }
50 }, {
51   query => [ 'customer.name' => 'rainer' ],
52   with_objects => [ 'customer' ],
53 }, 'joining customers';
54
55 test {
56   customer => {
57     chart => {
58       accno => 'test',
59     }
60   }
61 }, {
62   query => [ 'customer.chart.accno' => 'test' ],
63   with_objects => bag( 'customer', 'customer.chart' ),
64 }, 'nested joins';
65
66 test {
67   'customer:substr' => 'Meyer'
68 }, {
69   query => [ customer => '%Meyer%' ]
70 }, 'simple filter substr';
71
72 test {
73   'customer::ilike' => 'Meyer'
74 }, {
75   query => [ customer => { ilike => 'Meyer' } ]
76 }, 'simple method ilike';
77
78 test {
79   customer => {
80     chart => {
81       'accno:tail::like' => '1200'
82     }
83   },
84 },
85 {
86   query => [ 'customer.chart.accno' => { like => '%1200' } ],
87   with_objects => bag('customer', 'customer.chart' ),
88 }, 'all together';
89
90
91 test {
92   customer => {
93     name => 'test',
94   },
95   invoice => {
96     customer => {
97       name => 'test',
98     },
99   },
100 }, {
101   'query' => bag(
102                'invoice.customer.name'  => 'test',
103                'customer.name'          => 'test',
104             ),
105   'with_objects' => bag( 'invoice.customer', 'customer', 'invoice' )
106 }, 'object in more than one relationship';
107
108 test {
109   'orddate:date::' => 'lt',
110   'orddate:date' => '20.3.2010',
111 }, {
112     'query' => [
113                  'orddate' => { 'lt' => isa('DateTime') }
114                ]
115
116 }, 'method dispatch and date constructor';
117
118 test {
119   id => [
120     123, 125, 157
121   ]
122 }, {
123   query => [ id => [ 123,125,157 ] ],
124 }, 'arrays as value';
125
126 test {
127   'sellprice:number' => [
128     '123,4', '2,34', '0,4',
129   ]
130 }, {
131   query => [
132     sellprice => [ 123.4, 2.34, 0.4 ],
133   ],
134 }, 'arrays with filter';
135
136
137 ########### laundering
138
139 test {
140   'sellprice:number' => [
141     '123,4', '2,34', '0,4',
142   ]
143 }, {
144   'sellprice:number' => [ '123,4', '2,34', '0,4' ],
145   'sellprice_number_' => { '123,4' => 1, '2,34' => 1, '0,4' => 1 },
146 }, 'laundering with array', target => 'filter';
147
148 my %args = (
149   'sellprice:number' => [
150     '123,4', '2,34', '0,4',
151   ],
152 );
153 test {
154   %args,
155 }, {
156   %args
157 }, 'laundering into launder does not alter filter', target => 'filter', launder_to => {};
158
159
160 test {
161   part => {
162    'sellprice:number' => '123,4',
163   }
164 }, {
165   part => {
166     'sellprice:number' => '123,4',
167     'sellprice_number' => '123,4'
168   }
169 }, 'deep laundering', target => 'filter';
170
171
172 test {
173   part => {
174    'sellprice:number' => '123,4',
175   }
176 }, {
177   part => {
178     'sellprice_number' => '123,4'
179   }
180 }, 'deep laundering, check for laundered hash', target => 'launder', launder_to => { };
181
182 ### bug: sub objects
183
184 test {
185   order => {
186     customer => {
187       'name:substr::ilike' => 'test',
188     }
189   }
190 }, {
191   query => [ 'order.customer.name' => { ilike => '%test%' } ],
192   with_objects => bag('order.customer', 'order'),
193 }, 'sub objects have to retain their prefix';
194
195 ### class filter dispatch
196 #
197 test {
198   name => 'Test',
199   whut => 'moof',
200 }, {
201   query => bag(
202     name => 'Test',
203     whut => 'moof'
204   ),
205 }, 'object test simple', class => 'SL::DB::Manager::Part';
206
207 test {
208   'type' => 'assembly',
209 }, {
210   query => [
211     'assembly' => 1
212   ],
213 }, 'object test without prefix', class => 'SL::DB::Manager::Part';
214
215 test {
216   'part.type' => 'assembly',
217 }, {
218   query => [
219     'part.assembly' => 1
220   ],
221 }, 'object test with prefix', class => 'SL::DB::Manager::OrderItem';
222
223 test {
224   'type' => [ 'part', 'assembly' ],
225 }, {
226   query => [
227     or => [
228      and => [ or => [ assembly => 0, assembly => undef ],
229               "!inventory_accno_id" => 0,
230               "!inventory_accno_id" => undef,
231      ],
232      assembly => 1,
233     ]
234   ],
235 }, 'object test without prefix but complex value', class => 'SL::DB::Manager::Part';
236
237 test {
238   'part.type' => [ 'part', 'assembly' ],
239 }, {
240   query => [
241     or => [
242      and => [ or => [ 'part.assembly' => 0, 'part.assembly' => undef ],
243               "!part.inventory_accno_id" => 0,
244               "!part.inventory_accno_id" => undef,
245      ],
246      'part.assembly' => 1,
247     ]
248   ],
249 }, 'object test with prefix but complex value', class => 'SL::DB::Manager::OrderItem';
250
251 test {
252   description => 'test'
253 }, {
254   query => [ description => 'test' ],
255   with_objects => [ 'order' ]
256 }, 'with_objects don\'t get clobbered', with_objects => [ 'order' ];
257
258 test {
259   customer => {
260     description => 'test'
261   }
262 }, {
263   query => [ 'customer.description' => 'test' ],
264   with_objects => [ 'order', 'customer' ]
265 }, 'with_objects get extended with auto infered objects', with_objects => [ 'order' ];
266
267 test {
268   customer => {
269     description => 'test'
270   }
271 }, {
272   query => [ 'customer.description' => 'test' ],
273   with_objects => [ 'order', 'customer' ]
274 }, 'with_objects get extended with auto infered objects with classes', class => 'SL::DB::Manager::Order',  with_objects => [ 'order' ];
275
276 test {
277   customer => {
278     description => 'test'
279   }
280 }, {
281   query => [ 'customer.description' => 'test' ],
282   with_objects => [ 'customer' ]
283 }, 'with_objects: no duplicates', with_objects => [ 'customer' ];
284
285 test {
286   part => {
287    'partnumber:substr::ilike' => '1',
288   },
289 }, {
290   query => [
291    'part.partnumber', {
292      ilike => '%1%'
293    }
294  ]
295 }, 'Regression check: prefixing of fallback filtering in relation with custom filters', class => 'SL::DB::Manager::OrderItem';
296 test {
297   'description:substr:multi::ilike' => 'term1 term2',
298 }, {
299   query => [
300     and => [
301       description => { ilike => '%term1%' },
302       description => { ilike => '%term2%' },
303     ]
304   ]
305 }, 'simple :multi';
306
307 test {
308   part => {
309     'all:substr:multi::ilike' => 'term1 term2',
310   },
311 }, {
312   query => [
313     and => [
314       or => [
315         'part.partnumber'  => { ilike => '%term1%' },
316         'part.description' => { ilike => '%term1%' },
317       ],
318       or => [
319         'part.partnumber'  => { ilike => '%term2%' },
320         'part.description' => { ilike => '%term2%' },
321       ],
322     ]
323   ],
324 }, 'complex :multi with custom dispatch and prefix', class => 'SL::DB::Manager::OrderItem';
325
326 test {
327   'description:substr:multi::ilike' => q|term1 "term2 and half" 'term3 and stuff'|,
328 }, {
329   query => [
330     and => [
331       description => { ilike => '%term1%' },
332       description => { ilike => '%term2 and half%' },
333       description => { ilike => '%term3 and stuff%' },
334     ]
335   ]
336 }, ':multi with complex tokenizing';