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

(-)a/Koha/REST/V1/Patrons/SelfRenewal.pm (+1 lines)
Lines 121-126 sub submit { Link Here
121
                    $response->{confirmation_sent} = 1 if $result->{sent};
121
                    $response->{confirmation_sent} = 1 if $result->{sent};
122
                }
122
                }
123
123
124
                $c->res->headers->location( $c->req->url->to_string );
124
                return $c->render(
125
                return $c->render(
125
                    status  => 201,
126
                    status  => 201,
126
                    openapi => $response
127
                    openapi => $response
(-)a/t/db_dependent/Koha/Patron.t (-2 / +144 lines)
Lines 19-25 Link Here
19
19
20
use Modern::Perl;
20
use Modern::Perl;
21
21
22
use Test::More tests => 46;
22
use Test::More tests => 49;
23
use Test::NoWarnings;
23
use Test::NoWarnings;
24
use Test::Exception;
24
use Test::Exception;
25
use Test::Warn;
25
use Test::Warn;
Lines 721-727 subtest 'messaging_preferences() tests' => sub { Link Here
721
721
722
subtest 'to_api() tests' => sub {
722
subtest 'to_api() tests' => sub {
723
723
724
    plan tests => 6;
724
    plan tests => 7;
725
725
726
    $schema->storage->txn_begin;
726
    $schema->storage->txn_begin;
727
727
Lines 759-764 subtest 'to_api() tests' => sub { Link Here
759
    ok( exists $patron_json->{algo} );
759
    ok( exists $patron_json->{algo} );
760
    is( $patron_json->{algo}, 'algo' );
760
    is( $patron_json->{algo}, 'algo' );
761
761
762
    my $patron_eligible = $patron->to_api( { user => $consumer } );
763
    ok( exists $patron_eligible->{self_renewal_available} );
764
762
    $schema->storage->txn_rollback;
765
    $schema->storage->txn_rollback;
763
};
766
};
764
767
Lines 3562-3564 subtest "create_hold_group, hold_groups, visual_hold_group_id tests" => sub { Link Here
3562
3565
3563
    $schema->storage->txn_rollback;
3566
    $schema->storage->txn_rollback;
3564
};
3567
};
3568
3569
subtest "is_eligible_for_self_renewal" => sub {
3570
    plan tests => 8;
3571
3572
    $schema->storage->txn_begin;
3573
3574
    my $category = $builder->build_object(
3575
        {
3576
            class => 'Koha::Patron::Categories',
3577
            value => {
3578
                self_renewal_enabled     => 0,  self_renewal_availability_start => 10, self_renewal_if_expired => 10,
3579
                self_renewal_fines_block => 10, noissuescharge                  => 10
3580
            }
3581
        }
3582
    );
3583
3584
    my $patron = $builder->build_object(
3585
        {
3586
            class => 'Koha::Patrons',
3587
            value => { categorycode => $category->categorycode, dateexpiry => dt_from_string(), debarred => undef }
3588
        }
3589
    );
3590
3591
    is( $patron->is_eligible_for_self_renewal, 0, "Category not enabled" );
3592
3593
    $category->self_renewal_enabled(1)->store();
3594
    $patron->delete()->store()->discard_changes();
3595
    is( $patron->is_eligible_for_self_renewal, 1, "Category now enabled" );
3596
3597
    $patron->debarred('2026-01-01')->store;
3598
    is( $patron->is_eligible_for_self_renewal, 0, "Patron debarred" );
3599
    $patron->debarred(undef)->store;
3600
    $patron->delete()->store()->discard_changes();
3601
3602
    # Move expiry date outside the window
3603
    $patron->dateexpiry( dt_from_string()->add( days => 11 ) )->store;
3604
    is( $patron->is_eligible_for_self_renewal, 0, "Patron not yet within the expiry window" );
3605
3606
    # Shift the expiry window to cover new expiry date
3607
    $category->self_renewal_availability_start(12)->store();
3608
    $patron->delete()->store()->discard_changes();
3609
    is( $patron->is_eligible_for_self_renewal, 1, "Patron is back within the expiry window" );
3610
3611
    # Shift the date to now already be expired
3612
    $patron->dateexpiry( dt_from_string()->subtract( days => 11 ) )->store;
3613
    is( $patron->is_eligible_for_self_renewal, 0, "Patron can only self renew within 10 days of expiry" );
3614
3615
    # Expand the expiry window
3616
    $category->self_renewal_if_expired(12)->store();
3617
    $patron->delete()->store()->discard_changes();
3618
    is( $patron->is_eligible_for_self_renewal, 1, "Patron is back within the expiry window" );
3619
3620
    t::lib::Mocks::mock_preference( 'noissuescharge', 10 );
3621
3622
    my $fee1 = $builder->build_object(
3623
        {
3624
            class => 'Koha::Account::Lines',
3625
            value => {
3626
                borrowernumber    => $patron->borrowernumber,
3627
                amountoutstanding => 11,
3628
                debit_type_code   => 'OVERDUE',
3629
            }
3630
        }
3631
    )->store;
3632
    is( $patron->is_eligible_for_self_renewal, 0, "Patron is outside the charge limits" );
3633
3634
    $schema->storage->txn_rollback;
3635
};
3636
3637
subtest "request_modification" => sub {
3638
    plan tests => 5;
3639
3640
    $schema->storage->txn_begin;
3641
3642
    my $patron = $builder->build_object(
3643
        {
3644
            class => 'Koha::Patrons',
3645
        }
3646
    );
3647
3648
    my $modification_data = { firstname => 'Newname', changed_fields => 'firstname' };
3649
    t::lib::Mocks::mock_preference( 'OPACPatronDetails',                1 );
3650
    t::lib::Mocks::mock_preference( 'AutoApprovePatronProfileSettings', 0 );
3651
3652
    $patron->request_modification($modification_data);
3653
3654
    my @modifications = Koha::Patron::Modifications->search( { borrowernumber => $patron->borrowernumber } )->as_list;
3655
    is( scalar(@modifications), 1, "New modification has replaced any existing mods" );
3656
3657
    my $modification = $modifications[0];
3658
    is( $modification->changed_fields, 'firstname',                     'Fields correctly set' );
3659
    is( $modification->firstname,      $modification_data->{firstname}, 'Fields correctly set' );
3660
3661
    t::lib::Mocks::mock_preference( 'AutoApprovePatronProfileSettings', 1 );
3662
    $patron->request_modification($modification_data);
3663
3664
    @modifications = Koha::Patron::Modifications->search( { borrowernumber => $patron->borrowernumber } )->as_list;
3665
    is( scalar(@modifications),                0, "New modification has been approved and deleted" );
3666
    is( $patron->discard_changes()->firstname, $modification_data->{firstname}, 'Name updated' );
3667
3668
    $schema->storage->txn_rollback;
3669
};
3670
3671
subtest "create_expiry_notice_parameters" => sub {
3672
    plan tests => 1;
3673
3674
    $schema->storage->txn_begin;
3675
3676
    my $library = $builder->build_object( { class => 'Koha::Libraries' } );
3677
3678
    my $patron = $builder->build_object(
3679
        {
3680
            class => 'Koha::Patrons',
3681
            value => { branchcode => $library->branchcode }
3682
        }
3683
    );
3684
3685
    my $expected_return = {
3686
        'letter_params' => {
3687
            'borrowernumber' => $patron->borrowernumber,
3688
            'branchcode'     => $library->branchcode,
3689
            'tables'         => {
3690
                'borrowers' => $patron->borrowernumber,
3691
                'branches'  => $library->branchcode
3692
            },
3693
            'module'      => 'members',
3694
            'letter_code' => 'MEMBERSHIP_RENEWED',
3695
            'lang'        => $patron->lang
3696
        },
3697
        'message_name' => 'Patron_Expiry',
3698
        'forceprint'   => 0
3699
    };
3700
3701
    my $letter_params = $patron->create_expiry_notice_parameters(
3702
        { letter_code => 'MEMBERSHIP_RENEWED', forceprint => 0, is_notice_mandatory => 0 } );
3703
3704
    is_deeply( $letter_params, $expected_return, 'Letter params generated correctly' );
3705
    $schema->storage->txn_rollback;
3706
};
(-)a/t/db_dependent/api/v1/patron_categories.t (-12 / +70 lines)
Lines 18-24 Link Here
18
use Modern::Perl;
18
use Modern::Perl;
19
19
20
use Test::NoWarnings;
20
use Test::NoWarnings;
21
use Test::More tests => 2;
21
use Test::More tests => 3;
22
use Test::Mojo;
22
use Test::Mojo;
23
23
24
use t::lib::TestBuilder;
24
use t::lib::TestBuilder;
Lines 66-76 subtest 'list() tests' => sub { Link Here
66
66
67
    $t->get_ok("//$userid:$password@/api/v1/patron_categories")->status_is(200);
67
    $t->get_ok("//$userid:$password@/api/v1/patron_categories")->status_is(200);
68
68
69
    $t->get_ok("//$userid:$password@/api/v1/patron_categories?q={\"me.categorycode\":\"TEST\"}")
69
    $t->get_ok("//$userid:$password@/api/v1/patron_categories?q={\"me.categorycode\":\"TEST\"}")->status_is(200)
70
        ->status_is(200)
70
        ->json_has('/0/name')->json_is( '/0/name' => 'Test' )->json_hasnt('/1');
71
        ->json_has('/0/name')
72
        ->json_is( '/0/name' => 'Test' )
73
        ->json_hasnt('/1');
74
71
75
    # Off limits search
72
    # Off limits search
76
73
Lines 95-113 subtest 'list() tests' => sub { Link Here
95
    my $off_limits_userid = $off_limits_librarian->userid;
92
    my $off_limits_userid = $off_limits_librarian->userid;
96
93
97
    $t->get_ok("//$off_limits_userid:$off_limits_password@/api/v1/patron_categories?q={\"me.categorycode\":\"TEST\"}")
94
    $t->get_ok("//$off_limits_userid:$off_limits_password@/api/v1/patron_categories?q={\"me.categorycode\":\"TEST\"}")
98
        ->status_is(200)
95
        ->status_is(200)->json_hasnt('/0');
99
        ->json_hasnt('/0');
100
96
101
    # Off limits librarian category has changed to one within limits
97
    # Off limits librarian category has changed to one within limits
102
98
103
    $off_limits_librarian->branchcode( $library->branchcode )->store;
99
    $off_limits_librarian->branchcode( $library->branchcode )->store;
104
100
105
    $t->get_ok("//$off_limits_userid:$off_limits_password@/api/v1/patron_categories?q={\"me.categorycode\":\"TEST\"}")
101
    $t->get_ok("//$off_limits_userid:$off_limits_password@/api/v1/patron_categories?q={\"me.categorycode\":\"TEST\"}")
106
        ->status_is(200)
102
        ->status_is(200)->json_has('/0/name')->json_is( [ $category->to_api ] )->json_hasnt('/1');
107
        ->json_has('/0/name')
108
        ->json_is( [ $category->to_api ] )
109
        ->json_hasnt('/1');
110
103
111
    $schema->storage->txn_rollback;
104
    $schema->storage->txn_rollback;
112
105
113
};
106
};
107
108
subtest 'add() tests' => sub {
109
110
    plan tests => 9;
111
112
    $schema->storage->txn_begin;
113
114
    my $library = $builder->build_object( { class => 'Koha::Libraries' } );
115
116
    my $librarian = $builder->build_object(
117
        {
118
            class => 'Koha::Patrons',
119
            value => { flags => 2**3, categorycode => 'TEST', branchcode => $library->branchcode } # parameters flag = 3
120
        }
121
    );
122
123
    my $password = 'thePassword123';
124
    $librarian->set_password( { password => $password, skip_validation => 1 } );
125
    my $userid = $librarian->userid;
126
127
    my $patron = $builder->build_object(
128
        {
129
            class => 'Koha::Patrons',
130
            value => { flags => 0 }
131
        }
132
    );
133
134
    $patron->set_password( { password => $password, skip_validation => 1 } );
135
    my $unauth_userid = $patron->userid;
136
137
    my $category = {
138
        patron_category_id => 'new',
139
        name               => "A new category",
140
    };
141
142
    # Unauthorized attempt to write
143
    $t->post_ok( "//$unauth_userid:$password@/api/v1/patron_categories" => json => $category )->status_is(403);
144
145
    # Authorized attempt to write invalid data
146
    my $category_with_invalid_field = {
147
        blah => "Category Blah",
148
        name => "Won't work"
149
    };
150
151
    $t->post_ok( "//$userid:$password@/api/v1/patron_categories" => json => $category_with_invalid_field )
152
        ->status_is(400)->json_is(
153
        "/errors" => [
154
            {
155
                message => "Properties not allowed: blah.",
156
                path    => "/body"
157
            }
158
        ]
159
        );
160
161
    # Authorized attempt to write
162
    my $patron_category_id =
163
        $t->post_ok( "//$userid:$password@/api/v1/patron_categories" => json => $category )
164
        ->status_is( 201, 'REST3.2.1' )->header_like(
165
        Location => qr|^\/api\/v1\/patron_categories/\d*|,
166
        'REST3.4.1'
167
    )->json_is( '/name' => $category->{name} )->tx->res->json->{patron_category_id};
168
169
    $schema->storage->txn_rollback;
170
};
171
(-)a/t/db_dependent/api/v1/patrons.t (-116 / +64 lines)
Lines 125-140 subtest 'list() tests' => sub { Link Here
125
125
126
        $t->get_ok("//$userid:$password@/api/v1/patrons")->status_is( 200, 'list_borrowers makes /patrons accessible' );
126
        $t->get_ok("//$userid:$password@/api/v1/patrons")->status_is( 200, 'list_borrowers makes /patrons accessible' );
127
127
128
        $t->get_ok( "//$userid:$password@/api/v1/patrons?cardnumber=" . $librarian->cardnumber )
128
        $t->get_ok( "//$userid:$password@/api/v1/patrons?cardnumber=" . $librarian->cardnumber )->status_is(200)
129
            ->status_is(200)
130
            ->json_is( '/0/cardnumber' => $librarian->cardnumber );
129
            ->json_is( '/0/cardnumber' => $librarian->cardnumber );
131
130
132
        $t->get_ok( "//$userid:$password@/api/v1/patrons?q={\"cardnumber\":\"" . $librarian->cardnumber . "\"}" )
131
        $t->get_ok( "//$userid:$password@/api/v1/patrons?q={\"cardnumber\":\"" . $librarian->cardnumber . "\"}" )
133
            ->status_is(200)
132
            ->status_is(200)->json_is( '/0/cardnumber' => $librarian->cardnumber );
134
            ->json_is( '/0/cardnumber' => $librarian->cardnumber );
135
133
136
        $t->get_ok( "//$userid:$password@/api/v1/patrons?address2=" . $librarian->address2 )
134
        $t->get_ok( "//$userid:$password@/api/v1/patrons?address2=" . $librarian->address2 )->status_is(200)
137
            ->status_is(200)
138
            ->json_is( '/0/address2' => $librarian->address2 );
135
            ->json_is( '/0/address2' => $librarian->address2 );
139
136
140
        subtest 'restricted & expired' => sub {
137
        subtest 'restricted & expired' => sub {
Lines 148-166 subtest 'list() tests' => sub { Link Here
148
            $t->get_ok( "//$userid:$password@/api/v1/patrons?restricted="
145
            $t->get_ok( "//$userid:$password@/api/v1/patrons?restricted="
149
                    . Mojo::JSON->true
146
                    . Mojo::JSON->true
150
                    . "&cardnumber="
147
                    . "&cardnumber="
151
                    . $patron->cardnumber )
148
                    . $patron->cardnumber )->status_is(200)->json_has('/0/restricted')
152
                ->status_is(200)
149
                ->json_is( '/0/restricted' => Mojo::JSON->true )->json_has('/0/expired')
153
                ->json_has('/0/restricted')
150
                ->json_is( '/0/expired'    => Mojo::JSON->false )->json_hasnt('/1');
154
                ->json_is( '/0/restricted' => Mojo::JSON->true )
155
                ->json_has('/0/expired')
156
                ->json_is( '/0/expired' => Mojo::JSON->false )
157
                ->json_hasnt('/1');
158
151
159
            $patron->dateexpiry( dt_from_string->subtract( days => 2 ) )->store;
152
            $patron->dateexpiry( dt_from_string->subtract( days => 2 ) )->store;
160
            $t->get_ok( "//$userid:$password@/api/v1/patrons/" . $patron->borrowernumber )
153
            $t->get_ok( "//$userid:$password@/api/v1/patrons/" . $patron->borrowernumber )->status_is(200)
161
                ->status_is(200)
154
                ->json_has('/expired')->json_is( '/expired' => Mojo::JSON->true );
162
                ->json_has('/expired')
163
                ->json_is( '/expired' => Mojo::JSON->true );
164
155
165
            $t->get_ok( "//$userid:$password@/api/v1/patrons?"
156
            $t->get_ok( "//$userid:$password@/api/v1/patrons?"
166
                    . 'q={"extended_attributes.type":"CODE"}' => { 'x-koha-embed' => 'extended_attributes' } )
157
                    . 'q={"extended_attributes.type":"CODE"}' => { 'x-koha-embed' => 'extended_attributes' } )
Lines 189-203 subtest 'list() tests' => sub { Link Here
189
            $t->get_ok( "//$userid:$password@/api/v1/patrons?date_of_birth="
180
            $t->get_ok( "//$userid:$password@/api/v1/patrons?date_of_birth="
190
                    . $date_of_birth
181
                    . $date_of_birth
191
                    . "&cardnumber="
182
                    . "&cardnumber="
192
                    . $patron->cardnumber )
183
                    . $patron->cardnumber )->status_is(200)
193
                ->status_is(200)
194
                ->json_is( '/0/patron_id' => $patron->id, 'Filtering by date works' );
184
                ->json_is( '/0/patron_id' => $patron->id, 'Filtering by date works' );
195
185
196
            $t->get_ok( "//$userid:$password@/api/v1/patrons?last_seen="
186
            $t->get_ok( "//$userid:$password@/api/v1/patrons?last_seen="
197
                    . $last_seen_rfc3339
187
                    . $last_seen_rfc3339
198
                    . "&cardnumber="
188
                    . "&cardnumber="
199
                    . $patron->cardnumber )
189
                    . $patron->cardnumber )->status_is(200)
200
                ->status_is(200)
201
                ->json_is( '/0/patron_id' => $patron->id, 'Filtering by date-time works' );
190
                ->json_is( '/0/patron_id' => $patron->id, 'Filtering by date-time works' );
202
191
203
            my $q = encode_json(
192
            my $q = encode_json(
Lines 207-214 subtest 'list() tests' => sub { Link Here
207
                }
196
                }
208
            );
197
            );
209
198
210
            $t->get_ok("//$userid:$password@/api/v1/patrons?q=$q")
199
            $t->get_ok("//$userid:$password@/api/v1/patrons?q=$q")->status_is(200)
211
                ->status_is(200)
212
                ->json_is( '/0/patron_id' => $patron->id, 'Filtering by date works' );
200
                ->json_is( '/0/patron_id' => $patron->id, 'Filtering by date works' );
213
201
214
            $q = encode_json(
202
            $q = encode_json(
Lines 218-225 subtest 'list() tests' => sub { Link Here
218
                }
206
                }
219
            );
207
            );
220
208
221
            $t->get_ok("//$userid:$password@/api/v1/patrons?q=$q")
209
            $t->get_ok("//$userid:$password@/api/v1/patrons?q=$q")->status_is(200)
222
                ->status_is(200)
223
                ->json_is( '/0/patron_id' => $patron->id, 'Filtering by date-time works' );
210
                ->json_is( '/0/patron_id' => $patron->id, 'Filtering by date-time works' );
224
        };
211
        };
225
212
Lines 263-282 subtest 'list() tests' => sub { Link Here
263
        my $userid = $librarian->userid;
250
        my $userid = $librarian->userid;
264
251
265
        $t->get_ok( "//$userid:$password@/api/v1/patrons?_order_by=patron_id&q="
252
        $t->get_ok( "//$userid:$password@/api/v1/patrons?_order_by=patron_id&q="
266
                . encode_json( { library_id => [ $library_1->id, $library_2->id ] } ) )
253
                . encode_json( { library_id => [ $library_1->id, $library_2->id ] } ) )->status_is(200)
267
            ->status_is(200)
254
            ->json_is( '/0/patron_id' => $patron_1->id )->json_is( '/1/patron_id' => $patron_2->id )
268
            ->json_is( '/0/patron_id' => $patron_1->id )
269
            ->json_is( '/1/patron_id' => $patron_2->id )
270
            ->json_is( '/2/patron_id' => $patron_3->id );
255
            ->json_is( '/2/patron_id' => $patron_3->id );
271
256
272
        @libraries_where_can_see_patrons = ( $library_2->id );
257
        @libraries_where_can_see_patrons = ( $library_2->id );
273
258
274
        my $res =
259
        my $res =
275
            $t->get_ok( "//$userid:$password@/api/v1/patrons?_order_by=patron_id&q="
260
            $t->get_ok( "//$userid:$password@/api/v1/patrons?_order_by=patron_id&q="
276
                . encode_json( { library_id => [ $library_1->id, $library_2->id ] } ) )
261
                . encode_json( { library_id => [ $library_1->id, $library_2->id ] } ) )->status_is(200)
277
            ->status_is(200)
262
            ->json_is( '/0/patron_id' => $patron_3->id, 'Returns the only allowed patron' )->tx->res->json;
278
            ->json_is( '/0/patron_id' => $patron_3->id, 'Returns the only allowed patron' )
279
            ->tx->res->json;
280
263
281
        is( scalar @{$res}, 1, 'Only one patron returned' );
264
        is( scalar @{$res}, 1, 'Only one patron returned' );
282
265
Lines 339-348 subtest 'get() tests' => sub { Link Here
339
322
340
        $t->get_ok( "//$userid:$password@/api/v1/patrons/" . $patron->id )
323
        $t->get_ok( "//$userid:$password@/api/v1/patrons/" . $patron->id )
341
            ->status_is( 200, 'list_borrowers permission makes patron visible' )
324
            ->status_is( 200, 'list_borrowers permission makes patron visible' )
342
            ->json_is( '/patron_id'        => $patron->id )
325
            ->json_is( '/patron_id' => $patron->id )->json_is( '/category_id' => $patron->categorycode )
343
            ->json_is( '/category_id'      => $patron->categorycode )
326
            ->json_is( '/surname'   => $patron->surname )->json_is( '/patron_card_lost' => Mojo::JSON->false );
344
            ->json_is( '/surname'          => $patron->surname )
345
            ->json_is( '/patron_card_lost' => Mojo::JSON->false );
346
327
347
        $schema->storage->txn_rollback;
328
        $schema->storage->txn_rollback;
348
    };
329
    };
Lines 384-405 subtest 'get() tests' => sub { Link Here
384
        $librarian->set_password( { password => $password, skip_validation => 1 } );
365
        $librarian->set_password( { password => $password, skip_validation => 1 } );
385
        my $userid = $librarian->userid;
366
        my $userid = $librarian->userid;
386
367
387
        $t->get_ok( "//$userid:$password@/api/v1/patrons/" . $patron_1->id )
368
        $t->get_ok( "//$userid:$password@/api/v1/patrons/" . $patron_1->id )->status_is(200)
388
            ->status_is(200)
389
            ->json_is( '/patron_id' => $patron_1->id );
369
            ->json_is( '/patron_id' => $patron_1->id );
390
370
391
        $t->get_ok( "//$userid:$password@/api/v1/patrons/" . $patron_2->id )
371
        $t->get_ok( "//$userid:$password@/api/v1/patrons/" . $patron_2->id )->status_is(200)
392
            ->status_is(200)
393
            ->json_is( '/patron_id' => $patron_2->id );
372
            ->json_is( '/patron_id' => $patron_2->id );
394
373
395
        @libraries_where_can_see_patrons = ( $library_1->id );
374
        @libraries_where_can_see_patrons = ( $library_1->id );
396
375
397
        $t->get_ok( "//$userid:$password@/api/v1/patrons/" . $patron_1->id )
376
        $t->get_ok( "//$userid:$password@/api/v1/patrons/" . $patron_1->id )->status_is(200)
398
            ->status_is(200)
399
            ->json_is( '/patron_id' => $patron_1->id );
377
            ->json_is( '/patron_id' => $patron_1->id );
400
378
401
        $t->get_ok( "//$userid:$password@/api/v1/patrons/" . $patron_2->id )
379
        $t->get_ok( "//$userid:$password@/api/v1/patrons/" . $patron_2->id )->status_is(404)
402
            ->status_is(404)
403
            ->json_is( '/error' => 'Patron not found' );
380
            ->json_is( '/error' => 'Patron not found' );
404
381
405
        $schema->storage->txn_rollback;
382
        $schema->storage->txn_rollback;
Lines 481-494 subtest 'add() tests' => sub { Link Here
481
        delete $newpatron->{restricted};
458
        delete $newpatron->{restricted};
482
        delete $newpatron->{expired};
459
        delete $newpatron->{expired};
483
        delete $newpatron->{anonymized};
460
        delete $newpatron->{anonymized};
461
        delete $newpatron->{self_renewal_available};
484
462
485
        my $password = 'thePassword123';
463
        my $password = 'thePassword123';
486
        $librarian->set_password( { password => $password, skip_validation => 1 } );
464
        $librarian->set_password( { password => $password, skip_validation => 1 } );
487
        my $userid = $librarian->userid;
465
        my $userid = $librarian->userid;
488
        t::lib::Mocks::mock_preference( 'PatronDuplicateMatchingAddFields', 'firstname|surname|dateofbirth' );
466
        t::lib::Mocks::mock_preference( 'PatronDuplicateMatchingAddFields', 'firstname|surname|dateofbirth' );
489
467
490
        $t->post_ok( "//$userid:$password@/api/v1/patrons" => json => $newpatron )
468
        $t->post_ok( "//$userid:$password@/api/v1/patrons" => json => $newpatron )->status_is(409)
491
            ->status_is(409)
492
            ->json_is( '/error' => "A patron record matching these details already exists" );
469
            ->json_is( '/error' => "A patron record matching these details already exists" );
493
470
494
        # Create a library just to make sure its ID doesn't exist on the DB
471
        # Create a library just to make sure its ID doesn't exist on the DB
Lines 502-509 subtest 'add() tests' => sub { Link Here
502
479
503
        # Test duplicate userid constraint
480
        # Test duplicate userid constraint
504
        $t->post_ok( "//$userid:$password@/api/v1/patrons" => { 'x-confirm-not-duplicate' => 1 } => json => $newpatron )
481
        $t->post_ok( "//$userid:$password@/api/v1/patrons" => { 'x-confirm-not-duplicate' => 1 } => json => $newpatron )
505
            ->status_is(400)
482
            ->status_is(400)->json_is( '/error' => "Problem with " . $newpatron->{userid} );
506
            ->json_is( '/error' => "Problem with " . $newpatron->{userid} );
507
483
508
        $newpatron->{library_id} = $patron->branchcode;
484
        $newpatron->{library_id} = $patron->branchcode;
509
485
Lines 512-519 subtest 'add() tests' => sub { Link Here
512
        warning_like {
488
        warning_like {
513
            $t->post_ok(
489
            $t->post_ok(
514
                "//$userid:$password@/api/v1/patrons" => { 'x-confirm-not-duplicate' => 1 } => json => $newpatron )
490
                "//$userid:$password@/api/v1/patrons" => { 'x-confirm-not-duplicate' => 1 } => json => $newpatron )
515
                ->status_is(409)
491
                ->status_is(409)->json_has( '/error', 'Fails when trying to POST duplicate cardnumber' )
516
                ->json_has( '/error', 'Fails when trying to POST duplicate cardnumber' )
517
                ->json_like( '/conflict' => qr/(borrowers\.)?cardnumber/ );
492
                ->json_like( '/conflict' => qr/(borrowers\.)?cardnumber/ );
518
        }
493
        }
519
        qr/DBD::mysql::st execute failed: Duplicate entry '(.*?)' for key '(borrowers\.)?cardnumber'/;
494
        qr/DBD::mysql::st execute failed: Duplicate entry '(.*?)' for key '(borrowers\.)?cardnumber'/;
Lines 528-535 subtest 'add() tests' => sub { Link Here
528
        $newpatron->{category_id} = $deleted_category_id;    # Test invalid patron category
503
        $newpatron->{category_id} = $deleted_category_id;    # Test invalid patron category
529
504
530
        $t->post_ok( "//$userid:$password@/api/v1/patrons" => { 'x-confirm-not-duplicate' => 1 } => json => $newpatron )
505
        $t->post_ok( "//$userid:$password@/api/v1/patrons" => { 'x-confirm-not-duplicate' => 1 } => json => $newpatron )
531
            ->status_is(400)
506
            ->status_is(400)->json_is( '/error' => "Given category_id does not exist" );
532
            ->json_is( '/error' => "Given category_id does not exist" );
533
        $newpatron->{category_id} = $patron->categorycode;
507
        $newpatron->{category_id} = $patron->categorycode;
534
508
535
        $newpatron->{falseproperty} = "Non existent property";
509
        $newpatron->{falseproperty} = "Non existent property";
Lines 546-551 subtest 'add() tests' => sub { Link Here
546
        delete $newpatron->{restricted};
520
        delete $newpatron->{restricted};
547
        delete $newpatron->{expired};
521
        delete $newpatron->{expired};
548
        delete $newpatron->{anonymized};
522
        delete $newpatron->{anonymized};
523
        delete $newpatron->{self_renewal_available};
549
        $patron_to_delete->delete;
524
        $patron_to_delete->delete;
550
525
551
        # Set a date field
526
        # Set a date field
Lines 559-573 subtest 'add() tests' => sub { Link Here
559
        $letter_enqueued = 0;
534
        $letter_enqueued = 0;
560
        $t->post_ok(
535
        $t->post_ok(
561
            "//$userid:$password@/api/v1/patrons" => { 'x-koha-override' => 'welcome_yes' } => json => $newpatron )
536
            "//$userid:$password@/api/v1/patrons" => { 'x-koha-override' => 'welcome_yes' } => json => $newpatron )
562
            ->status_is( 201, 'Patron created successfully' )
537
            ->status_is( 201, 'Patron created successfully' )->header_like(
563
            ->header_like(
564
            Location => qr|^\/api\/v1\/patrons/\d*|,
538
            Location => qr|^\/api\/v1\/patrons/\d*|,
565
            'REST3.4.1'
539
            'REST3.4.1'
566
            )
540
        )->json_has( '/patron_id', 'got a patron_id' )->json_is( '/cardnumber' => $newpatron->{cardnumber} )
567
            ->json_has( '/patron_id', 'got a patron_id' )
541
            ->json_is( '/surname'       => $newpatron->{surname} )->json_is( '/firstname' => $newpatron->{firstname} )
568
            ->json_is( '/cardnumber'    => $newpatron->{cardnumber} )
569
            ->json_is( '/surname'       => $newpatron->{surname} )
570
            ->json_is( '/firstname'     => $newpatron->{firstname} )
571
            ->json_is( '/date_of_birth' => $newpatron->{date_of_birth}, 'Date field set (Bug 28585)' )
542
            ->json_is( '/date_of_birth' => $newpatron->{date_of_birth}, 'Date field set (Bug 28585)' )
572
            ->json_is( '/last_seen'     => $newpatron->{last_seen},     'Date-time field set (Bug 28585)' );
543
            ->json_is( '/last_seen'     => $newpatron->{last_seen},     'Date-time field set (Bug 28585)' );
573
544
Lines 694-701 subtest 'add() tests' => sub { Link Here
694
                    "city"        => "Konstanz",
665
                    "city"        => "Konstanz",
695
                    "library_id"  => "MPL"
666
                    "library_id"  => "MPL"
696
                }
667
                }
697
                )
668
            )->status_is(400)
698
                ->status_is(400)
699
                ->json_is( '/error' => "Tried to add more than one non-repeatable attributes. type=$code value=$attr" );
669
                ->json_is( '/error' => "Tried to add more than one non-repeatable attributes. type=$code value=$attr" );
700
670
701
            is( Koha::Patrons->search->count, $patrons_count, 'No patron added' );
671
            is( Koha::Patrons->search->count, $patrons_count, 'No patron added' );
Lines 710-717 subtest 'add() tests' => sub { Link Here
710
                    "city"        => "Konstanz",
680
                    "city"        => "Konstanz",
711
                    "library_id"  => "MPL"
681
                    "library_id"  => "MPL"
712
                }
682
                }
713
                )
683
            )->status_is(400)
714
                ->status_is(400)
715
                ->json_is(
684
                ->json_is(
716
                '/error' => "Your action breaks a unique constraint on the attribute. type=$code value=$attr" );
685
                '/error' => "Your action breaks a unique constraint on the attribute. type=$code value=$attr" );
717
686
Lines 872-879 subtest 'add() tests' => sub { Link Here
872
                    "city"        => "Bigtown",
841
                    "city"        => "Bigtown",
873
                    "library_id"  => "MPL",
842
                    "library_id"  => "MPL",
874
                }
843
                }
875
                )
844
            )->status_is( 201, 'Patron added with EnhancedMessagingPreferences disabled' )
876
                ->status_is( 201, 'Patron added with EnhancedMessagingPreferences disabled' )
877
                ->tx->res->json->{patron_id};
845
                ->tx->res->json->{patron_id};
878
846
879
            # No messaging preferences should be set
847
            # No messaging preferences should be set
Lines 931-939 subtest 'update() tests' => sub { Link Here
931
        delete $newpatron->{restricted};
899
        delete $newpatron->{restricted};
932
        delete $newpatron->{expired};
900
        delete $newpatron->{expired};
933
        delete $newpatron->{anonymized};
901
        delete $newpatron->{anonymized};
902
        delete $newpatron->{self_renewal_available};
934
903
935
        $t->put_ok( "//$userid:$password@/api/v1/patrons/-1" => json => $newpatron )
904
        $t->put_ok( "//$userid:$password@/api/v1/patrons/-1" => json => $newpatron )->status_is(404)
936
            ->status_is(404)
937
            ->json_has( '/error', 'Fails when trying to PUT nonexistent patron' );
905
            ->json_has( '/error', 'Fails when trying to PUT nonexistent patron' );
938
906
939
        # Create a library just to make sure its ID doesn't exist on the DB
907
        # Create a library just to make sure its ID doesn't exist on the DB
Lines 947-954 subtest 'update() tests' => sub { Link Here
947
        $newpatron->{category_id} = $deleted_category_id;
915
        $newpatron->{category_id} = $deleted_category_id;
948
916
949
        $t->put_ok( "//$userid:$password@/api/v1/patrons/" . $patron_2->borrowernumber => json => $newpatron )
917
        $t->put_ok( "//$userid:$password@/api/v1/patrons/" . $patron_2->borrowernumber => json => $newpatron )
950
            ->status_is(400)
918
            ->status_is(400)->json_is( '/error' => "Given category_id does not exist" );
951
            ->json_is( '/error' => "Given category_id does not exist" );
952
919
953
        # Restore the valid category
920
        # Restore the valid category
954
        $newpatron->{category_id} = $patron_2->categorycode;
921
        $newpatron->{category_id} = $patron_2->categorycode;
Lines 965-972 subtest 'update() tests' => sub { Link Here
965
932
966
        warning_like {
933
        warning_like {
967
            $t->put_ok( "//$userid:$password@/api/v1/patrons/" . $patron_2->borrowernumber => json => $newpatron )
934
            $t->put_ok( "//$userid:$password@/api/v1/patrons/" . $patron_2->borrowernumber => json => $newpatron )
968
                ->status_is(400)
935
                ->status_is(400)->json_is( '/error' => "Given library_id does not exist" );
969
                ->json_is( '/error' => "Given library_id does not exist" );
970
        }
936
        }
971
        qr/DBD::mysql::st execute failed: Cannot add or update a child row: a foreign key constraint fails/;
937
        qr/DBD::mysql::st execute failed: Cannot add or update a child row: a foreign key constraint fails/;
972
938
Lines 977-984 subtest 'update() tests' => sub { Link Here
977
        $newpatron->{falseproperty} = "Non existent property";
943
        $newpatron->{falseproperty} = "Non existent property";
978
944
979
        $t->put_ok( "//$userid:$password@/api/v1/patrons/" . $patron_2->borrowernumber => json => $newpatron )
945
        $t->put_ok( "//$userid:$password@/api/v1/patrons/" . $patron_2->borrowernumber => json => $newpatron )
980
            ->status_is(400)
946
            ->status_is(400)->json_is( '/errors/0/message' => 'Properties not allowed: falseproperty.' );
981
            ->json_is( '/errors/0/message' => 'Properties not allowed: falseproperty.' );
982
947
983
        # Get rid of the invalid attribute
948
        # Get rid of the invalid attribute
984
        delete $newpatron->{falseproperty};
949
        delete $newpatron->{falseproperty};
Lines 988-995 subtest 'update() tests' => sub { Link Here
988
        $newpatron->{userid}     = $patron_1->userid;
953
        $newpatron->{userid}     = $patron_1->userid;
989
954
990
        $t->put_ok( "//$userid:$password@/api/v1/patrons/" . $patron_2->borrowernumber => json => $newpatron )
955
        $t->put_ok( "//$userid:$password@/api/v1/patrons/" . $patron_2->borrowernumber => json => $newpatron )
991
            ->status_is(400)
956
            ->status_is(400)->json_has( '/error', "Problem with userid " . $patron_1->userid );
992
            ->json_has( '/error', "Problem with userid " . $patron_1->userid );
993
957
994
        $newpatron->{cardnumber} = $patron_1->id . $patron_2->id;
958
        $newpatron->{cardnumber} = $patron_1->id . $patron_2->id;
995
        $newpatron->{userid}     = "user" . $patron_1->id . $patron_2->id;
959
        $newpatron->{userid}     = "user" . $patron_1->id . $patron_2->id;
Lines 1011-1016 subtest 'update() tests' => sub { Link Here
1011
        $newpatron->{restricted} = $unauthorized_patron->to_api( { user => $authorized_patron } )->{restricted};
975
        $newpatron->{restricted} = $unauthorized_patron->to_api( { user => $authorized_patron } )->{restricted};
1012
        $newpatron->{expired}    = $unauthorized_patron->to_api( { user => $authorized_patron } )->{expired};
976
        $newpatron->{expired}    = $unauthorized_patron->to_api( { user => $authorized_patron } )->{expired};
1013
        $newpatron->{anonymized} = $unauthorized_patron->to_api( { user => $authorized_patron } )->{anonymized};
977
        $newpatron->{anonymized} = $unauthorized_patron->to_api( { user => $authorized_patron } )->{anonymized};
978
        $newpatron->{self_renewal_available} =
979
            $unauthorized_patron->to_api( { user => $authorized_patron } )->{self_renewal_available};
1014
980
1015
        my $got                 = $result->tx->res->json;
981
        my $got                 = $result->tx->res->json;
1016
        my $updated_on_got      = delete $got->{updated_on};
982
        my $updated_on_got      = delete $got->{updated_on};
Lines 1046-1051 subtest 'update() tests' => sub { Link Here
1046
        delete $newpatron->{restricted};
1012
        delete $newpatron->{restricted};
1047
        delete $newpatron->{expired};
1013
        delete $newpatron->{expired};
1048
        delete $newpatron->{anonymized};
1014
        delete $newpatron->{anonymized};
1015
        delete $newpatron->{self_renewal_available};
1049
1016
1050
        # attempt to update
1017
        # attempt to update
1051
        $authorized_patron->flags( 2**4 )->store;    # borrowers flag = 4
1018
        $authorized_patron->flags( 2**4 )->store;    # borrowers flag = 4
Lines 1073-1079 subtest 'update() tests' => sub { Link Here
1073
            ->status_is(
1040
            ->status_is(
1074
            403,
1041
            403,
1075
            "Non-superlibrarian user change of superlibrarian secondary_email to undefined forbidden"
1042
            "Non-superlibrarian user change of superlibrarian secondary_email to undefined forbidden"
1076
            )->json_is( { error => "Not enough privileges to change a superlibrarian's email" } );
1043
        )->json_is( { error => "Not enough privileges to change a superlibrarian's email" } );
1077
1044
1078
        $newpatron->{secondary_email}  = $superlibrarian->emailpro;
1045
        $newpatron->{secondary_email}  = $superlibrarian->emailpro;
1079
        $newpatron->{altaddress_email} = 'nonsense@no.no';
1046
        $newpatron->{altaddress_email} = 'nonsense@no.no';
Lines 1089-1095 subtest 'update() tests' => sub { Link Here
1089
            ->status_is(
1056
            ->status_is(
1090
            403,
1057
            403,
1091
            "Non-superlibrarian user change of superlibrarian altaddress_email to undefined forbidden"
1058
            "Non-superlibrarian user change of superlibrarian altaddress_email to undefined forbidden"
1092
            )->json_is( { error => "Not enough privileges to change a superlibrarian's email" } );
1059
        )->json_is( { error => "Not enough privileges to change a superlibrarian's email" } );
1093
1060
1094
        # update patron without sending email
1061
        # update patron without sending email
1095
        delete $newpatron->{email};
1062
        delete $newpatron->{email};
Lines 1160-1174 subtest 'update() tests' => sub { Link Here
1160
                    "city"        => "Konstanz",
1127
                    "city"        => "Konstanz",
1161
                    "library_id"  => "MPL"
1128
                    "library_id"  => "MPL"
1162
                }
1129
                }
1163
                )
1130
            )->status_is(400)
1164
                ->status_is(400)
1165
                ->json_is(
1131
                ->json_is(
1166
                '/error' => "Missing mandatory extended attribute (type=" . $attr_type_mandatory->code . ")" );
1132
                '/error' => "Missing mandatory extended attribute (type=" . $attr_type_mandatory->code . ")" );
1167
1133
1168
            $t->put_ok( "//$userid:$password@/api/v1/patrons/"
1134
            $t->put_ok( "//$userid:$password@/api/v1/patrons/"
1169
                    . $superlibrarian->borrowernumber => { 'x-koha-embed' => 'extended_attributes' } => json =>
1135
                    . $superlibrarian->borrowernumber => { 'x-koha-embed' => 'extended_attributes' } => json =>
1170
                    $newpatron )
1136
                    $newpatron )->status_is(400)
1171
                ->status_is(400)
1172
                ->json_is( '/error'      => 'Tried to use an invalid attribute type. type=' . $deleted_attr_code )
1137
                ->json_is( '/error'      => 'Tried to use an invalid attribute type. type=' . $deleted_attr_code )
1173
                ->json_is( '/error_code' => 'invalid_attribute_type' );
1138
                ->json_is( '/error_code' => 'invalid_attribute_type' );
1174
1139
Lines 1185-1197 subtest 'update() tests' => sub { Link Here
1185
1150
1186
            $t->put_ok( "//$userid:$password@/api/v1/patrons/"
1151
            $t->put_ok( "//$userid:$password@/api/v1/patrons/"
1187
                    . $superlibrarian->borrowernumber => { 'x-koha-embed' => 'extended_attributes' } => json =>
1152
                    . $superlibrarian->borrowernumber => { 'x-koha-embed' => 'extended_attributes' } => json =>
1188
                    $newpatron )
1153
                    $newpatron )->status_is(400)
1189
                ->status_is(400)
1190
                ->json_is( '/error' => 'Your action breaks a unique constraint on the attribute. type='
1154
                ->json_is( '/error' => 'Your action breaks a unique constraint on the attribute. type='
1191
                    . $attr_type_unique->code
1155
                    . $attr_type_unique->code
1192
                    . ' value='
1156
                    . ' value='
1193
                    . $unique_attr->attribute )
1157
                    . $unique_attr->attribute )->json_is( '/error_code' => 'attribute_not_unique' );
1194
                ->json_is( '/error_code' => 'attribute_not_unique' );
1195
1158
1196
            $newpatron->{extended_attributes} = [
1159
            $newpatron->{extended_attributes} = [
1197
                { type => $attr_type_repeatable->code, value => 'a' },
1160
                { type => $attr_type_repeatable->code, value => 'a' },
Lines 1202-1214 subtest 'update() tests' => sub { Link Here
1202
1165
1203
            $t->put_ok( "//$userid:$password@/api/v1/patrons/"
1166
            $t->put_ok( "//$userid:$password@/api/v1/patrons/"
1204
                    . $superlibrarian->borrowernumber => { 'x-koha-embed' => 'extended_attributes' } => json =>
1167
                    . $superlibrarian->borrowernumber => { 'x-koha-embed' => 'extended_attributes' } => json =>
1205
                    $newpatron )
1168
                    $newpatron )->status_is(400)
1206
                ->status_is(400)
1207
                ->json_is( '/error' => 'Your action breaks a unique constraint on the attribute. type='
1169
                ->json_is( '/error' => 'Your action breaks a unique constraint on the attribute. type='
1208
                    . $attr_type_unique->code
1170
                    . $attr_type_unique->code
1209
                    . ' value='
1171
                    . ' value='
1210
                    . $unique_attr->attribute )
1172
                    . $unique_attr->attribute )->json_is( '/error_code' => 'attribute_not_unique' );
1211
                ->json_is( '/error_code' => 'attribute_not_unique' );
1212
1173
1213
            $newpatron->{extended_attributes} = [
1174
            $newpatron->{extended_attributes} = [
1214
                { type => $attr_type_repeatable->code, value => 'a' },
1175
                { type => $attr_type_repeatable->code, value => 'a' },
Lines 1218-1229 subtest 'update() tests' => sub { Link Here
1218
1179
1219
            $t->put_ok( "//$userid:$password@/api/v1/patrons/"
1180
            $t->put_ok( "//$userid:$password@/api/v1/patrons/"
1220
                    . $superlibrarian->borrowernumber => { 'x-koha-embed' => 'extended_attributes' } => json =>
1181
                    . $superlibrarian->borrowernumber => { 'x-koha-embed' => 'extended_attributes' } => json =>
1221
                    $newpatron )
1182
                    $newpatron )->status_is(400)
1222
                ->status_is(400)
1223
                ->json_is( '/error' => 'Tried to add more than one non-repeatable attributes. type='
1183
                ->json_is( '/error' => 'Tried to add more than one non-repeatable attributes. type='
1224
                    . $attr_type_mandatory->code
1184
                    . $attr_type_mandatory->code
1225
                    . ' value=pong' )
1185
                    . ' value=pong' )->json_is( '/error_code' => 'non_repeatable_attribute' );
1226
                ->json_is( '/error_code' => 'non_repeatable_attribute' );
1227
1186
1228
            my $unique_value = $unique_attr->attribute;
1187
            my $unique_value = $unique_attr->attribute;
1229
            $unique_attr->delete;
1188
            $unique_attr->delete;
Lines 1282-1289 subtest 'delete() tests' => sub { Link Here
1282
1241
1283
        t::lib::Mocks::mock_preference( 'AnonymousPatron', $patron->borrowernumber );
1242
        t::lib::Mocks::mock_preference( 'AnonymousPatron', $patron->borrowernumber );
1284
        $t->delete_ok( "//$userid:$password@/api/v1/patrons/" . $patron->borrowernumber )
1243
        $t->delete_ok( "//$userid:$password@/api/v1/patrons/" . $patron->borrowernumber )
1285
            ->status_is( 409, 'Anonymous patron cannot be deleted' )
1244
            ->status_is( 409, 'Anonymous patron cannot be deleted' )->json_is(
1286
            ->json_is(
1287
            {
1245
            {
1288
                error      => 'Anonymous patron cannot be deleted',
1246
                error      => 'Anonymous patron cannot be deleted',
1289
                error_code => 'is_anonymous_patron'
1247
                error_code => 'is_anonymous_patron'
Lines 1305-1312 subtest 'delete() tests' => sub { Link Here
1305
        $guarantee->add_guarantor( { guarantor_id => $patron->id, relationship => 'parent' } );
1263
        $guarantee->add_guarantor( { guarantor_id => $patron->id, relationship => 'parent' } );
1306
1264
1307
        $t->delete_ok( "//$userid:$password@/api/v1/patrons/" . $patron->borrowernumber )
1265
        $t->delete_ok( "//$userid:$password@/api/v1/patrons/" . $patron->borrowernumber )
1308
            ->status_is( 409, 'Patron with checkouts cannot be deleted' )
1266
            ->status_is( 409, 'Patron with checkouts cannot be deleted' )->json_is(
1309
            ->json_is(
1310
            {
1267
            {
1311
                error      => 'Pending checkouts prevent deletion',
1268
                error      => 'Pending checkouts prevent deletion',
1312
                error_code => 'has_checkouts'
1269
                error_code => 'has_checkouts'
Lines 1317-1324 subtest 'delete() tests' => sub { Link Here
1317
        $checkout->delete;
1274
        $checkout->delete;
1318
1275
1319
        $t->delete_ok( "//$userid:$password@/api/v1/patrons/" . $patron->borrowernumber )
1276
        $t->delete_ok( "//$userid:$password@/api/v1/patrons/" . $patron->borrowernumber )
1320
            ->status_is( 409, 'Patron with debt cannot be deleted' )
1277
            ->status_is( 409, 'Patron with debt cannot be deleted' )->json_is(
1321
            ->json_is(
1322
            {
1278
            {
1323
                error      => 'Pending debts prevent deletion',
1279
                error      => 'Pending debts prevent deletion',
1324
                error_code => 'has_debt'
1280
                error_code => 'has_debt'
Lines 1329-1336 subtest 'delete() tests' => sub { Link Here
1329
        $patron->account->pay( { amount => 10, debits => [$debit] } );
1285
        $patron->account->pay( { amount => 10, debits => [$debit] } );
1330
1286
1331
        $t->delete_ok( "//$userid:$password@/api/v1/patrons/" . $patron->borrowernumber )
1287
        $t->delete_ok( "//$userid:$password@/api/v1/patrons/" . $patron->borrowernumber )
1332
            ->status_is( 409, 'Patron with guarantees cannot be deleted' )
1288
            ->status_is( 409, 'Patron with guarantees cannot be deleted' )->json_is(
1333
            ->json_is(
1334
            {
1289
            {
1335
                error      => 'Patron is a guarantor and it prevents deletion',
1290
                error      => 'Patron is a guarantor and it prevents deletion',
1336
                error_code => 'has_guarantees'
1291
                error_code => 'has_guarantees'
Lines 1343-1350 subtest 'delete() tests' => sub { Link Here
1343
        $patron->protected(1)->store();
1298
        $patron->protected(1)->store();
1344
1299
1345
        $t->delete_ok( "//$userid:$password@/api/v1/patrons/" . $patron->borrowernumber )
1300
        $t->delete_ok( "//$userid:$password@/api/v1/patrons/" . $patron->borrowernumber )
1346
            ->status_is( 409, 'Protected patron cannot be deleted' )
1301
            ->status_is( 409, 'Protected patron cannot be deleted' )->json_is(
1347
            ->json_is(
1348
            {
1302
            {
1349
                error      => 'Protected patrons cannot be deleted',
1303
                error      => 'Protected patrons cannot be deleted',
1350
                error_code => 'is_protected'
1304
                error_code => 'is_protected'
Lines 1354-1361 subtest 'delete() tests' => sub { Link Here
1354
        $patron->protected(0)->store();
1308
        $patron->protected(0)->store();
1355
1309
1356
        $t->delete_ok( "//$userid:$password@/api/v1/patrons/" . $patron->borrowernumber )
1310
        $t->delete_ok( "//$userid:$password@/api/v1/patrons/" . $patron->borrowernumber )
1357
            ->status_is( 204, 'REST3.2.4' )
1311
            ->status_is( 204, 'REST3.2.4' )->content_is( '', 'REST3.3.4' );
1358
            ->content_is( '', 'REST3.3.4' );
1359
1312
1360
        my $deleted_patrons = Koha::Old::Patrons->search( { borrowernumber => $patron->borrowernumber } );
1313
        my $deleted_patrons = Koha::Old::Patrons->search( { borrowernumber => $patron->borrowernumber } );
1361
        is( $deleted_patrons->count, 1, 'The patron has been moved to the vault' );
1314
        is( $deleted_patrons->count, 1, 'The patron has been moved to the vault' );
Lines 1387-1399 subtest 'guarantors_can_see_charges() tests' => sub { Link Here
1387
    )->status_is(401)->json_is( { error => "Authentication failure." } );
1340
    )->status_is(401)->json_is( { error => "Authentication failure." } );
1388
1341
1389
    $t->put_ok( "//$userid:$password@/api/v1/public/patrons/$other_patron_id/guarantors/can_see_charges" => json =>
1342
    $t->put_ok( "//$userid:$password@/api/v1/public/patrons/$other_patron_id/guarantors/can_see_charges" => json =>
1390
            { allowed => Mojo::JSON->true } )
1343
            { allowed => Mojo::JSON->true } )->status_is(403)
1391
        ->status_is(403)
1392
        ->json_is( { error => "Unprivileged user cannot access another user's resources" } );
1344
        ->json_is( { error => "Unprivileged user cannot access another user's resources" } );
1393
1345
1394
    $t->put_ok( "//$userid:$password@/api/v1/public/patrons/$patron_id/guarantors/can_see_charges" => json =>
1346
    $t->put_ok( "//$userid:$password@/api/v1/public/patrons/$patron_id/guarantors/can_see_charges" => json =>
1395
            { allowed => Mojo::JSON->true } )
1347
            { allowed => Mojo::JSON->true } )->status_is(403)
1396
        ->status_is(403)
1397
        ->json_is( '/error', 'The current configuration doesn\'t allow the requested action.' );
1348
        ->json_is( '/error', 'The current configuration doesn\'t allow the requested action.' );
1398
1349
1399
    t::lib::Mocks::mock_preference( 'AllowPatronToSetFinesVisibilityForGuarantor', 1 );
1350
    t::lib::Mocks::mock_preference( 'AllowPatronToSetFinesVisibilityForGuarantor', 1 );
Lines 1431-1445 subtest 'guarantors_can_see_checkouts() tests' => sub { Link Here
1431
            { allowed => Mojo::JSON->true } )->status_is(401)->json_is( { error => "Authentication failure." } );
1382
            { allowed => Mojo::JSON->true } )->status_is(401)->json_is( { error => "Authentication failure." } );
1432
1383
1433
    $t->put_ok( "//$userid:$password@/api/v1/public/patrons/$other_patron_id/guarantors/can_see_checkouts" => json =>
1384
    $t->put_ok( "//$userid:$password@/api/v1/public/patrons/$other_patron_id/guarantors/can_see_checkouts" => json =>
1434
            { allowed => Mojo::JSON->true } )
1385
            { allowed => Mojo::JSON->true } )->status_is(403)
1435
        ->status_is(403)
1436
        ->json_is( { error => "Unprivileged user cannot access another user's resources" } );
1386
        ->json_is( { error => "Unprivileged user cannot access another user's resources" } );
1437
1387
1438
    t::lib::Mocks::mock_preference( 'AllowPatronToSetCheckoutsVisibilityForGuarantor', 0 );
1388
    t::lib::Mocks::mock_preference( 'AllowPatronToSetCheckoutsVisibilityForGuarantor', 0 );
1439
1389
1440
    $t->put_ok( "//$userid:$password@/api/v1/public/patrons/$patron_id/guarantors/can_see_checkouts" => json =>
1390
    $t->put_ok( "//$userid:$password@/api/v1/public/patrons/$patron_id/guarantors/can_see_checkouts" => json =>
1441
            { allowed => Mojo::JSON->true } )
1391
            { allowed => Mojo::JSON->true } )->status_is(403)
1442
        ->status_is(403)
1443
        ->json_is( '/error', 'The current configuration doesn\'t allow the requested action.' );
1392
        ->json_is( '/error', 'The current configuration doesn\'t allow the requested action.' );
1444
1393
1445
    t::lib::Mocks::mock_preference( 'AllowPatronToSetCheckoutsVisibilityForGuarantor', 1 );
1394
    t::lib::Mocks::mock_preference( 'AllowPatronToSetCheckoutsVisibilityForGuarantor', 1 );
Lines 1482-1489 sub unauthorized_access_tests { Link Here
1482
        $unauthorized_patron->set_password( { password => $password, skip_validation => 1 } );
1431
        $unauthorized_patron->set_password( { password => $password, skip_validation => 1 } );
1483
        my $unauth_userid = $unauthorized_patron->userid;
1432
        my $unauth_userid = $unauthorized_patron->userid;
1484
1433
1485
        $t->$verb_ok( "//$unauth_userid:$password\@$endpoint" => json => $json )
1434
        $t->$verb_ok( "//$unauth_userid:$password\@$endpoint" => json => $json )->status_is(403)
1486
            ->status_is(403)
1487
            ->json_has('/required_permissions');
1435
            ->json_has('/required_permissions');
1488
    };
1436
    };
1489
}
1437
}
(-)a/t/db_dependent/api/v1/patrons_self_renewal.t (-1 / +198 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::NoWarnings;
21
use Test::More tests => 3;
22
use Test::Mojo;
23
24
use t::lib::TestBuilder;
25
use t::lib::Mocks;
26
27
use Koha::Database;
28
use Koha::DateUtils qw( dt_from_string );
29
30
my $schema  = Koha::Database->new->schema;
31
my $builder = t::lib::TestBuilder->new();
32
33
my $t = Test::Mojo->new('Koha::REST::V1');
34
35
t::lib::Mocks::mock_preference( 'RESTBasicAuth', 1 );
36
37
subtest 'start()' => sub {
38
    plan tests => 9;
39
40
    $schema->storage->txn_begin;
41
42
    my $category = $builder->build_object(
43
        {
44
            class => 'Koha::Patron::Categories',
45
            value => {
46
                self_renewal_enabled         => 0, self_renewal_availability_start => 10, self_renewal_if_expired => 10,
47
                self_renewal_fines_block     => 10, noissuescharge                 => 10,
48
                self_renewal_failure_message => 'This is a failure message'
49
            }
50
        }
51
    );
52
53
    my $patron = $builder->build_object(
54
        {
55
            class => 'Koha::Patrons',
56
            value => { categorycode => $category->categorycode, dateexpiry => dt_from_string(), debarred => undef }
57
        }
58
    );
59
60
    my $password = 'thePassword123';
61
    $patron->set_password( { password => $password, skip_validation => 1 } );
62
    my $userid = $patron->userid;
63
64
    my $borrowernumber = $patron->borrowernumber;
65
66
    $t->get_ok("//$userid:$password@/api/v1/public/patrons/$borrowernumber/self_renewal")
67
        ->status_is( 403, 'REST3.2.2' )->json_is( { error => "You are not eligible for self-renewal" } );
68
69
    $category->self_renewal_enabled(1)->store();
70
71
    t::lib::Mocks::mock_preference( 'OPACPatronDetails', 1 );
72
73
    $t->get_ok("//$userid:$password@/api/v1/public/patrons/$borrowernumber/self_renewal")
74
        ->status_is( 200, 'REST3.2.2' )->json_is(
75
        {
76
            self_renewal_settings => {
77
                self_renewal_failure_message     => $category->self_renewal_failure_message,
78
                self_renewal_information_message => $category->self_renewal_information_message,
79
                opac_patron_details              => 1
80
            }
81
        }
82
        );
83
84
    my $new_category = $builder->build_object(
85
        {
86
            class => 'Koha::Patron::Categories',
87
            value => {
88
                self_renewal_enabled         => 1, self_renewal_availability_start => 10, self_renewal_if_expired => 10,
89
                self_renewal_fines_block     => 10, noissuescharge                 => 10,
90
                self_renewal_failure_message => 'This is a failure message'
91
            }
92
        }
93
    );
94
    $t->get_ok("//$userid:$password@/api/v1/public/patrons/$borrowernumber/self_renewal")
95
        ->status_is( 200, 'REST3.2.2' )->json_is(
96
        {
97
            self_renewal_settings => {
98
                self_renewal_failure_message     => $category->self_renewal_failure_message,
99
                self_renewal_information_message => $category->self_renewal_information_message,
100
                opac_patron_details              => 1
101
            }
102
        }
103
        );
104
105
    $schema->storage->txn_rollback;
106
};
107
108
subtest 'submit()' => sub {
109
    plan tests => 11;
110
111
    $schema->storage->txn_begin;
112
113
    my $category = $builder->build_object(
114
        {
115
            class => 'Koha::Patron::Categories',
116
            value => {
117
                self_renewal_enabled         => 0, self_renewal_availability_start => 10, self_renewal_if_expired => 10,
118
                self_renewal_fines_block     => 10, noissuescharge                 => 10,
119
                self_renewal_failure_message => 'This is a failure message'
120
            }
121
        }
122
    );
123
    my $branch = $builder->build_object( { class => 'Koha::Libraries' } );
124
125
    my $patron = $builder->build_object(
126
        {
127
            class => 'Koha::Patrons',
128
            value => {
129
                branchcode => $branch->branchcode, categorycode => $category->categorycode,
130
                dateexpiry => dt_from_string(),    debarred     => undef, lang => 'default'
131
            }
132
        }
133
    );
134
135
    my $password = 'thePassword123';
136
    $patron->set_password( { password => $password, skip_validation => 1 } );
137
    my $userid = $patron->userid;
138
139
    my $borrowernumber = $patron->borrowernumber;
140
141
    $t->post_ok( "//$userid:$password@/api/v1/public/patrons/$borrowernumber/self_renewal" => json => {} )
142
        ->status_is( 403, 'REST3.2.2' )->json_is( { error => "You are not eligible for self-renewal" } );
143
144
    t::lib::Mocks::mock_preference( 'OPACPatronDetails',                1 );
145
    t::lib::Mocks::mock_preference( 'AutoApprovePatronProfileSettings', 0 );
146
    $category->self_renewal_enabled(1)->store();
147
148
    my $date;
149
    if ( C4::Context->preference('BorrowerRenewalPeriodBase') eq 'combination' ) {
150
        $date =
151
            ( dt_from_string gt dt_from_string( $patron->dateexpiry ) )
152
            ? dt_from_string
153
            : dt_from_string( $patron->dateexpiry );
154
    } else {
155
        $date =
156
            C4::Context->preference('BorrowerRenewalPeriodBase') eq 'dateexpiry'
157
            ? dt_from_string( $patron->dateexpiry )
158
            : dt_from_string;
159
    }
160
    my $expiry_date = $patron->category->get_expiry_date($date);
161
162
    my $renewal_notice = $builder->build_object(
163
        {
164
            class => 'Koha::Notice::Templates',
165
            value => {
166
                module                 => 'members',
167
                code                   => 'MEMBERSHIP_RENEWED',
168
                branchcode             => $branch->branchcode,
169
                message_transport_type => 'print',
170
                lang                   => 'default'
171
            }
172
        }
173
    );
174
175
    my $counter = Koha::Notice::Messages->search( { borrowernumber => $patron->borrowernumber } )->count;
176
    $t->post_ok( "//$userid:$password@/api/v1/public/patrons/$borrowernumber/self_renewal" => json => {} )
177
        ->status_is( 201, 'REST3.2.2' )
178
        ->json_is( { expiry_date => $expiry_date->truncate( to => 'day' ), confirmation_sent => 1 } );
179
    is(
180
        Koha::Notice::Messages->search( { borrowernumber => $patron->borrowernumber } )->count, $counter + 1,
181
        "Notice queued"
182
    );
183
184
    # Test that modifications are created correctly
185
    t::lib::Mocks::mock_preference( 'OPACPatronDetails', 1 );
186
    my $modification_data = { patron => { firstname => 'Newname' } };
187
    $patron->dateexpiry( dt_from_string() )->store();
188
189
    $t->post_ok(
190
        "//$userid:$password@/api/v1/public/patrons/$borrowernumber/self_renewal" => json => $modification_data )
191
        ->status_is( 201, 'REST3.2.2' )
192
        ->json_is( { expiry_date => $expiry_date->truncate( to => 'day' ), confirmation_sent => 1 } );
193
194
    my @modifications = Koha::Patron::Modifications->search( { borrowernumber => $patron->borrowernumber } )->as_list;
195
    is( scalar(@modifications), 1, "New modification has replaced any existing mods" );
196
197
    $schema->storage->txn_rollback;
198
};

Return to bug 26355