View | Details | Raw Unified | Return to bug 38050
Collapse All | Expand All

(-)a/t/db_dependent/api/v1/lists.t (-1 / +526 lines)
Line 0 Link Here
0
- 
1
#!/usr/bin/env perl
2
3
# This file is part of Koha.
4
#
5
# Koha is free software; you can redistribute it and/or modify it
6
# under the terms of the GNU General Public License as published by
7
# the Free Software Foundation; either version 3 of the License, or
8
# (at your option) any later version.
9
#
10
# Koha is distributed in the hope that it will be useful, but
11
# WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
# GNU General Public License for more details.
14
#
15
# You should have received a copy of the GNU General Public License
16
# along with Koha; if not, see <https://www.gnu.org/licenses>.
17
18
use Modern::Perl;
19
20
use Test::More tests => 6;
21
use Test::Mojo;
22
use Test::NoWarnings;
23
24
use JSON qw(encode_json);
25
26
use t::lib::TestBuilder;
27
use t::lib::Mocks;
28
29
use Koha::Virtualshelves;
30
use Koha::Database;
31
32
my $schema  = Koha::Database->new->schema;
33
my $builder = t::lib::TestBuilder->new;
34
35
my $t = Test::Mojo->new('Koha::REST::V1');
36
t::lib::Mocks::mock_preference( 'RESTBasicAuth', 1 );
37
38
subtest 'list() tests' => sub {
39
    plan tests => 13;
40
41
    $schema->storage->txn_begin;
42
43
    my $password = 'thePassword123';
44
45
    # Create librarian with necessary permissions (catalogue + lists)
46
    my $librarian = $builder->build_object(
47
        {
48
            class => 'Koha::Patrons',
49
            value => { flags => 4 + 2**20 }    # catalogue + lists permissions
50
        }
51
    );
52
    my $patron = $builder->build_object(
53
        {
54
            class => 'Koha::Patrons',
55
            value => { flags => 0 }            # no permissions
56
        }
57
    );
58
59
    $librarian->set_password( { password => $password, skip_validation => 1 } );
60
    $patron->set_password( { password => $password, skip_validation => 1 } );
61
62
    my $librarian_userid = $librarian->userid;
63
    my $unauth_userid    = $patron->userid;
64
65
    ## Authorized user tests
66
    # No lists, so empty array should be returned
67
    $t->get_ok("//$librarian_userid:$password@/api/v1/lists")->status_is(200)->json_is( [] );
68
69
    # Create test lists owned by librarian
70
    my $list_1 = $builder->build_object(
71
        {
72
            class => 'Koha::Virtualshelves',
73
            value => { owner => $librarian->id, public => 1 }
74
        }
75
    );
76
    my $list_2 = $builder->build_object(
77
        {
78
            class => 'Koha::Virtualshelves',
79
            value => { owner => $librarian->id, public => 0 }
80
        }
81
    );
82
83
    # One list
84
    my $q = encode_json( { list_id => $list_1->id } );
85
    $t->get_ok("//$librarian_userid:$password@/api/v1/lists?q=$q")->status_is(200)->json_is( [ $list_1->to_api ] );
86
87
    # Multiple lists
88
    $q = encode_json( { list_id => { -in => [ $list_1->id, $list_2->id ] } } );
89
    $t->get_ok("//$librarian_userid:$password@/api/v1/lists?q=$q")->status_is(200)
90
        ->json_is( [ $list_1->to_api, $list_2->to_api ] );
91
92
    # Unauthorized access
93
    $t->get_ok("/api/v1/lists?q=$q")->status_is( 401, "Anonymous users cannot access admin lists endpoint" );
94
95
    $t->get_ok("//$unauth_userid:$password@/api/v1/lists?q=$q")
96
        ->status_is( 403, "Patrons without permission cannot access admin lists endpoint" );
97
98
    $schema->storage->txn_rollback;
99
};
100
101
subtest 'get() tests' => sub {
102
    plan tests => 14;
103
104
    $schema->storage->txn_begin;
105
106
    my $password = 'thePassword123';
107
108
    # Create librarian with necessary permissions (catalogue + lists)
109
    my $librarian = $builder->build_object(
110
        {
111
            class => 'Koha::Patrons',
112
            value => { flags => 4 + 2**20 }    # catalogue + lists permissions
113
        }
114
    );
115
    my $patron = $builder->build_object(
116
        {
117
            class => 'Koha::Patrons',
118
            value => { flags => 0 }            # no permissions
119
        }
120
    );
121
122
    $librarian->set_password( { password => $password, skip_validation => 1 } );
123
    $patron->set_password( { password => $password, skip_validation => 1 } );
124
125
    my $librarian_userid = $librarian->userid;
126
    my $unauth_userid    = $patron->userid;
127
128
    # Create test lists
129
    my $public_list = $builder->build_object(
130
        {
131
            class => 'Koha::Virtualshelves',
132
            value => { owner => $patron->id, public => 1 }
133
        }
134
    );
135
136
    my $private_list = $builder->build_object(
137
        {
138
            class => 'Koha::Virtualshelves',
139
            value => { owner => $patron->id, public => 0 }
140
        }
141
    );
142
143
    ## Authorized user tests
144
    # Librarian can view public list
145
    $t->get_ok( "//$librarian_userid:$password@/api/v1/lists/" . $public_list->id )->status_is(200)
146
        ->json_is( $public_list->to_api );
147
148
    # Librarian can view own list
149
    my $librarian_list = $builder->build_object(
150
        {
151
            class => 'Koha::Virtualshelves',
152
            value => { owner => $librarian->id, public => 0 }
153
        }
154
    );
155
156
    $t->get_ok( "//$librarian_userid:$password@/api/v1/lists/" . $librarian_list->id )->status_is(200)
157
        ->json_is( $librarian_list->to_api );
158
159
    # Librarian cannot view another patron's private list
160
    $t->get_ok( "//$librarian_userid:$password@/api/v1/lists/" . $private_list->id )->status_is(403);
161
162
    ## Unauthorized user tests
163
    $t->get_ok( "/api/v1/lists/" . $public_list->id )->status_is(401);
164
165
    $t->get_ok( "//$unauth_userid:$password@/api/v1/lists/" . $public_list->id )->status_is(403);
166
167
    # Not found
168
    my $list_to_delete  = $builder->build_object( { class => 'Koha::Virtualshelves' } );
169
    my $non_existent_id = $list_to_delete->id;
170
    $list_to_delete->delete;
171
172
    $t->get_ok("//$librarian_userid:$password@/api/v1/lists/$non_existent_id")->status_is(404);
173
174
    $schema->storage->txn_rollback;
175
};
176
177
subtest 'add() tests' => sub {
178
    plan tests => 20;
179
180
    $schema->storage->txn_begin;
181
182
    my $password = 'thePassword123';
183
184
    # Create librarian with necessary permissions (catalogue + lists)
185
    my $librarian = $builder->build_object(
186
        {
187
            class => 'Koha::Patrons',
188
            value => { flags => 4 + 2**20 }    # catalogue + lists permissions
189
        }
190
    );
191
    my $patron = $builder->build_object(
192
        {
193
            class => 'Koha::Patrons',
194
            value => { flags => 0 }            # no permissions
195
        }
196
    );
197
    my $another_patron = $builder->build_object( { class => 'Koha::Patrons' } );
198
199
    $librarian->set_password( { password => $password, skip_validation => 1 } );
200
    $patron->set_password( { password => $password, skip_validation => 1 } );
201
202
    my $librarian_userid = $librarian->userid;
203
    my $unauth_userid    = $patron->userid;
204
205
    my $list_data = {
206
        name                     => "Test List",
207
        owner_id                 => $librarian->id,
208
        public                   => 1,
209
        allow_change_from_owner  => 1,
210
        allow_change_from_others => 0,
211
        default_sort_field       => 'title'
212
    };
213
214
    ## Unauthorized attempts
215
    $t->post_ok( "/api/v1/lists" => json => $list_data )->status_is(401);
216
217
    $t->post_ok( "//$unauth_userid:$password@/api/v1/lists" => json => $list_data )->status_is(403);
218
219
    ## Authorized user tests
220
    # Create list for self
221
    my $list_id =
222
        $t->post_ok( "//$librarian_userid:$password@/api/v1/lists" => json => $list_data )
223
        ->status_is( 201, 'REST3.2.1' )->header_like( Location => qr|^/api/v1/lists/\d+|, 'REST3.4.1' )
224
        ->json_is( '/name'   => $list_data->{name} )->json_is( '/owner_id' => $list_data->{owner_id} )
225
        ->json_is( '/public' => $list_data->{public} )->tx->res->json->{list_id};
226
227
    # Create list for another patron
228
    my $list_for_patron = {
229
        name                     => "Test List for Patron",
230
        owner_id                 => $another_patron->id,
231
        public                   => 0,
232
        allow_change_from_owner  => 1,
233
        allow_change_from_others => 0,
234
        default_sort_field       => 'title'
235
    };
236
237
    $t->post_ok( "//$librarian_userid:$password@/api/v1/lists" => json => $list_for_patron )->status_is(201)
238
        ->json_is( '/owner_id' => $another_patron->id )->json_is( '/name' => 'Test List for Patron' );
239
240
    # Invalid owner_id
241
    my $list_invalid_owner = {
242
        name                     => "Test List",
243
        owner_id                 => 999999,
244
        public                   => 1,
245
        allow_change_from_owner  => 1,
246
        allow_change_from_others => 0,
247
        default_sort_field       => 'title'
248
    };
249
250
    $t->post_ok( "//$librarian_userid:$password@/api/v1/lists" => json => $list_invalid_owner )->status_is(400)
251
        ->json_like( '/error' => qr/Invalid owner_id/ );
252
253
    # Test that sortfield is validated (UNIMARC conversion happens in store)
254
    my $list_with_sortfield = {
255
        name                     => "Test List with Sort",
256
        owner_id                 => $librarian->id,
257
        public                   => 1,
258
        allow_change_from_owner  => 1,
259
        allow_change_from_others => 0,
260
        default_sort_field       => 'copyrightdate'
261
    };
262
263
    $t->post_ok( "//$librarian_userid:$password@/api/v1/lists" => json => $list_with_sortfield )->status_is(201)
264
        ->json_has('/default_sort_field');
265
266
    $schema->storage->txn_rollback;
267
};
268
269
subtest 'update() tests' => sub {
270
    plan tests => 24;
271
272
    $schema->storage->txn_begin;
273
274
    my $password = 'thePassword123';
275
276
    # Create librarian with necessary permissions (catalogue + lists)
277
    my $librarian = $builder->build_object(
278
        {
279
            class => 'Koha::Patrons',
280
            value => { flags => 4 + 2**20 }    # catalogue + lists permissions
281
        }
282
    );
283
    my $patron = $builder->build_object(
284
        {
285
            class => 'Koha::Patrons',
286
            value => { flags => 0 }            # no permissions
287
        }
288
    );
289
290
    $librarian->set_password( { password => $password, skip_validation => 1 } );
291
    $patron->set_password( { password => $password, skip_validation => 1 } );
292
293
    my $librarian_userid = $librarian->userid;
294
    my $unauth_userid    = $patron->userid;
295
296
    # Create test list owned by librarian
297
    my $list = $builder->build_object(
298
        {
299
            class => 'Koha::Virtualshelves',
300
            value => {
301
                owner                    => $librarian->id,
302
                public                   => 1,
303
                allow_change_from_owner  => 1,
304
                allow_change_from_others => 0
305
            }
306
        }
307
    );
308
309
    my $update_data = {
310
        name                     => "Updated List Name",
311
        public                   => 0,
312
        allow_change_from_owner  => 1,
313
        allow_change_from_others => 0,
314
        default_sort_field       => 'title'
315
    };
316
317
    ## Unauthorized attempts
318
    $t->put_ok( "/api/v1/lists/" . $list->id => json => $update_data )->status_is(401);
319
320
    $t->put_ok( "//$unauth_userid:$password@/api/v1/lists/" . $list->id => json => $update_data )->status_is(403);
321
322
    ## Authorized user tests
323
    # Owner can update own list
324
    $t->put_ok( "//$librarian_userid:$password@/api/v1/lists/" . $list->id => json => $update_data )->status_is(200)
325
        ->json_is( '/name' => 'Updated List Name' )->json_is( '/public' => 0 );
326
327
    # Not found
328
    $t->put_ok( "//$librarian_userid:$password@/api/v1/lists/99999999" => json => $update_data )->status_is(404);
329
330
    # Librarian with edit_public_lists can update PUBLIC list owned by another patron
331
    my $patron_public_list = $builder->build_object(
332
        {
333
            class => 'Koha::Virtualshelves',
334
            value => {
335
                owner  => $patron->id,
336
                public => 1
337
            }
338
        }
339
    );
340
341
    my $update_data_2 = {
342
        name                     => "Updated by Librarian",
343
        public                   => 1,
344
        allow_change_from_owner  => 1,
345
        allow_change_from_others => 0,
346
        default_sort_field       => 'title'
347
    };
348
349
    $t->put_ok( "//$librarian_userid:$password@/api/v1/lists/" . $patron_public_list->id => json => $update_data_2 )
350
        ->status_is(200)->json_is( '/name' => 'Updated by Librarian' );
351
352
    # Librarian with edit_public_lists CANNOT update PRIVATE list owned by another patron
353
    my $patron_private_list = $builder->build_object(
354
        {
355
            class => 'Koha::Virtualshelves',
356
            value => {
357
                owner  => $patron->id,
358
                public => 0
359
            }
360
        }
361
    );
362
363
    $t->put_ok( "//$librarian_userid:$password@/api/v1/lists/" . $patron_private_list->id => json => $update_data_2 )
364
        ->status_is(403);
365
366
    # Owner can always update their own list (allow_change_from_owner doesn't block property updates)
367
    my $owner_list = $builder->build_object(
368
        {
369
            class => 'Koha::Virtualshelves',
370
            value => {
371
                owner                   => $librarian->id,
372
                public                  => 1,
373
                allow_change_from_owner => 0                 # This flag only affects list CONTENT, not properties
374
            }
375
        }
376
    );
377
378
    my $update_data_3 = {
379
        name                     => "Owner Can Update",
380
        public                   => 1,
381
        allow_change_from_owner  => 0,
382
        allow_change_from_others => 0,
383
        default_sort_field       => 'author'
384
    };
385
386
    $t->put_ok( "//$librarian_userid:$password@/api/v1/lists/" . $owner_list->id => json => $update_data_3 )
387
        ->status_is(200)->json_is( '/name' => 'Owner Can Update' );
388
389
    # Test sortfield validation and UNIMARC handling
390
    my $update_with_sortfield = {
391
        name                     => "List with Sortfield",
392
        public                   => 1,
393
        allow_change_from_owner  => 1,
394
        allow_change_from_others => 0,
395
        default_sort_field       => 'copyrightdate'          # Will be converted to publicationyear for UNIMARC
396
    };
397
398
    $t->put_ok( "//$librarian_userid:$password@/api/v1/lists/" . $list->id => json => $update_with_sortfield )
399
        ->status_is(200)->json_has('/default_sort_field');
400
401
    # Test invalid sortfield gets defaulted to 'title'
402
    my $update_invalid_sort = {
403
        name                     => "List with Invalid Sort",
404
        public                   => 1,
405
        allow_change_from_owner  => 1,
406
        allow_change_from_others => 0,
407
        default_sort_field       => 'invalid_field'
408
    };
409
410
    $t->put_ok( "//$librarian_userid:$password@/api/v1/lists/" . $list->id => json => $update_invalid_sort )
411
        ->status_is(200)->json_is( '/default_sort_field' => 'title' );
412
413
    $schema->storage->txn_rollback;
414
};
415
416
subtest 'delete() tests' => sub {
417
    plan tests => 17;
418
419
    $schema->storage->txn_begin;
420
421
    my $password = 'thePassword123';
422
423
    # Create librarian with necessary permissions (catalogue + lists)
424
    my $librarian = $builder->build_object(
425
        {
426
            class => 'Koha::Patrons',
427
            value => { flags => 4 + 2**20 }    # catalogue + lists permissions
428
        }
429
    );
430
    my $patron = $builder->build_object(
431
        {
432
            class => 'Koha::Patrons',
433
            value => { flags => 0 }            # no permissions
434
        }
435
    );
436
437
    $librarian->set_password( { password => $password, skip_validation => 1 } );
438
    $patron->set_password( { password => $password, skip_validation => 1 } );
439
440
    my $librarian_userid = $librarian->userid;
441
    my $unauth_userid    = $patron->userid;
442
443
    # Create test list owned by librarian
444
    my $list = $builder->build_object(
445
        {
446
            class => 'Koha::Virtualshelves',
447
            value => { owner => $librarian->id, public => 1 }
448
        }
449
    );
450
451
    ## Unauthorized attempts
452
    $t->delete_ok( "/api/v1/lists/" . $list->id )->status_is(401);
453
454
    $t->delete_ok( "//$unauth_userid:$password@/api/v1/lists/" . $list->id )->status_is(403);
455
456
    ## Authorized user tests
457
    # Owner can delete own list
458
    $t->delete_ok( "//$librarian_userid:$password@/api/v1/lists/" . $list->id )->status_is( 204, 'REST3.2.4' )
459
        ->content_is( '', 'REST3.3.4' );
460
461
    # Already deleted (not found)
462
    $t->delete_ok( "//$librarian_userid:$password@/api/v1/lists/" . $list->id )->status_is(404);
463
464
    # Librarian with delete_public_lists can delete PUBLIC list owned by another patron
465
    my $patron_public_list = $builder->build_object(
466
        {
467
            class => 'Koha::Virtualshelves',
468
            value => {
469
                owner  => $patron->id,
470
                public => 1
471
            }
472
        }
473
    );
474
475
    $t->delete_ok( "//$librarian_userid:$password@/api/v1/lists/" . $patron_public_list->id )->status_is(204);
476
477
    # Librarian with delete_public_lists CANNOT delete PRIVATE list owned by another patron
478
    my $patron_private_list = $builder->build_object(
479
        {
480
            class => 'Koha::Virtualshelves',
481
            value => {
482
                owner  => $patron->id,
483
                public => 0
484
            }
485
        }
486
    );
487
488
    $t->delete_ok( "//$librarian_userid:$password@/api/v1/lists/" . $patron_private_list->id )->status_is(403);
489
490
    # Owner can always delete their own list (allow_change_from_owner doesn't block deletion)
491
    my $owner_list = $builder->build_object(
492
        {
493
            class => 'Koha::Virtualshelves',
494
            value => {
495
                owner                   => $librarian->id,
496
                public                  => 1,
497
                allow_change_from_owner => 0                 # This flag only affects list CONTENT, not deletion
498
            }
499
        }
500
    );
501
502
    $t->delete_ok( "//$librarian_userid:$password@/api/v1/lists/" . $owner_list->id )->status_is(204);
503
504
    # Test patron can delete their own list even without special permissions
505
    my $patron_own_list = $builder->build_object(
506
        {
507
            class => 'Koha::Virtualshelves',
508
            value => {
509
                owner  => $patron->id,
510
                public => 0
511
            }
512
        }
513
    );
514
515
    # Give patron minimal permissions to access their own list
516
    my $patron_with_list = Koha::Patrons->find( $patron->id );
517
    $patron_with_list->flags(4);    # catalogue permission
518
    $patron_with_list->store;
519
520
    $t->delete_ok( "//$unauth_userid:$password@/api/v1/lists/" . $patron_own_list->id )->status_is(403)
521
        ;                           # Still 403 because patron needs lists permission
522
523
    $schema->storage->txn_rollback;
524
};
525
526
1;

Return to bug 38050