#!/usr/bin/env perl use strict; use warnings; use Test::More; use CGI::Simple; use_ok( 'Open311' ); use_ok( 'Open311::GetServiceRequestUpdates' ); use DateTime; use DateTime::Format::W3CDTF; use FixMyStreet::App; my $user = FixMyStreet::App->model('DB::User')->find_or_create( { email => 'system_user@example.com' } ); my $requests_xml = qq{ 638344 1 open This is a note UPDATED_DATETIME }; my $dt = DateTime->now; # basic xml -> perl object tests for my $test ( { desc => 'basic parsing - element missing', updated_datetime => '', res => { update_id => 638344, service_request_id => 1, status => 'open', description => 'This is a note' }, }, { desc => 'basic parsing - empty element', updated_datetime => '', res => { update_id => 638344, service_request_id => 1, status => 'open', description => 'This is a note', updated_datetime => {} } , }, { desc => 'basic parsing - element with no content', updated_datetime => '', res => { update_id => 638344, service_request_id => 1, status => 'open', description => 'This is a note', updated_datetime => {} } , }, { desc => 'basic parsing - element with content', updated_datetime => sprintf( '%s', $dt ), res => { update_id => 638344, service_request_id => 1, status => 'open', description => 'This is a note', updated_datetime => $dt } , }, ) { subtest $test->{desc} => sub { my $local_requests_xml = $requests_xml; $local_requests_xml =~ s/UPDATED_DATETIME/$test->{updated_datetime}/; my $o = Open311->new( jurisdiction => 'mysociety', endpoint => 'http://example.com', test_mode => 1, test_get_returns => { 'servicerequestupdates.xml' => $local_requests_xml } ); my $res = $o->get_service_request_updates; is_deeply $res->[0], $test->{ res }, 'result looks correct'; }; } subtest 'check extended request parsed correctly' => sub { my $extended_requests_xml = qq{ 638344 120384 1 open This is a note UPDATED_DATETIME }; my $updated_datetime = sprintf( '%s', $dt ); my $expected_res = { update_id => 638344, service_request_id => 1, service_request_id_ext => 120384, status => 'open', description => 'This is a note', updated_datetime => $dt }; $extended_requests_xml =~ s/UPDATED_DATETIME/$updated_datetime/; my $o = Open311->new( jurisdiction => 'mysociety', endpoint => 'http://example.com', test_mode => 1, test_get_returns => { 'servicerequestupdates.xml' => $extended_requests_xml } ); my $res = $o->get_service_request_updates; is_deeply $res->[0], $expected_res, 'result looks correct'; }; my $problem_rs = FixMyStreet::App->model('DB::Problem'); my $problem = $problem_rs->new( { postcode => 'EH99 1SP', latitude => 1, longitude => 1, areas => 1, title => '', detail => '', used_map => 1, user_id => 1, name => '', state => 'confirmed', service => '', cobrand => 'default', cobrand_data => '', user => $user, created => DateTime->now()->subtract( days => 1 ), lastupdate => DateTime->now()->subtract( days => 1 ), anonymous => 1, external_id => time(), bodies_str => 2482, } ); $problem->insert; for my $test ( { desc => 'OPEN status for confirmed problem does not change state', description => 'This is a note', external_id => 638344, start_state => 'confirmed', comment_status => 'OPEN', mark_fixed=> 0, mark_open => 0, problem_state => undef, end_state => 'confirmed', }, { desc => 'bad state does not update states but does create update', description => 'This is a note', external_id => 638344, start_state => 'confirmed', comment_status => 'INVALID_STATE', mark_fixed=> 0, mark_open => 0, problem_state => undef, end_state => 'confirmed', }, { desc => 'investigating status changes problem status', description => 'This is a note', external_id => 638344, start_state => 'confirmed', comment_status => 'INVESTIGATING', mark_fixed=> 0, mark_open => 0, problem_state => 'investigating', end_state => 'investigating', }, { desc => 'in progress status changes problem status', description => 'This is a note', external_id => 638344, start_state => 'confirmed', comment_status => 'IN_PROGRESS', mark_fixed=> 0, mark_open => 0, problem_state => 'in progress', end_state => 'in progress', }, { desc => 'action scheduled status changes problem status', description => 'This is a note', external_id => 638344, start_state => 'confirmed', comment_status => 'ACTION_SCHEDULED', mark_fixed=> 0, mark_open => 0, problem_state => 'action scheduled', end_state => 'action scheduled', }, { desc => 'not responsible status changes problem status', description => 'This is a note', external_id => 638344, start_state => 'confirmed', comment_status => 'NOT_COUNCILS_RESPONSIBILITY', mark_fixed=> 0, mark_open => 0, problem_state => 'not responsible', end_state => 'not responsible', }, { desc => 'internal referral status changes problem status', description => 'This is a note', external_id => 638344, start_state => 'confirmed', comment_status => 'INTERNAL_REFERRAL', mark_fixed=> 0, mark_open => 0, problem_state => 'internal referral', end_state => 'internal referral', }, { desc => 'duplicate status changes problem status', description => 'This is a note', external_id => 638344, start_state => 'confirmed', comment_status => 'DUPLICATE', mark_fixed=> 0, mark_open => 0, problem_state => 'duplicate', end_state => 'duplicate', }, { desc => 'fixed status marks report as fixed - council', description => 'This is a note', external_id => 638344, start_state => 'confirmed', comment_status => 'FIXED', mark_fixed=> 0, mark_open => 0, problem_state => 'fixed - council', end_state => 'fixed - council', }, { desc => 'status of CLOSED marks report as fixed - council', description => 'This is a note', external_id => 638344, start_state => 'confirmed', comment_status => 'CLOSED', mark_fixed=> 0, mark_open => 0, problem_state => 'fixed - council', end_state => 'fixed - council', }, { desc => 'status of OPEN re-opens fixed report', description => 'This is a note', external_id => 638344, start_state => 'fixed - user', comment_status => 'OPEN', mark_fixed => 0, mark_open => 0, problem_state => 'confirmed', end_state => 'confirmed', }, { desc => 'action sheduled re-opens fixed report as action scheduled', description => 'This is a note', external_id => 638344, start_state => 'fixed - user', comment_status => 'ACTION_SCHEDULED', mark_fixed => 0, mark_open => 0, problem_state => 'action scheduled', end_state => 'action scheduled', }, { desc => 'open status re-opens closed report', description => 'This is a note', external_id => 638344, start_state => 'not responsible', comment_status => 'OPEN', mark_fixed => 0, mark_open => 0, problem_state => 'confirmed', end_state => 'confirmed', }, { desc => 'fixed status leaves fixed - user report as fixed - user', description => 'This is a note', external_id => 638344, start_state => 'fixed - user', comment_status => 'FIXED', mark_fixed => 0, mark_open => 0, problem_state => undef, end_state => 'fixed - user', }, { desc => 'closed status updates fixed report', description => 'This is a note', external_id => 638344, start_state => 'fixed - user', comment_status => 'NO_FURTHER_ACTION', mark_fixed => 0, mark_open => 0, problem_state => 'unable to fix', end_state => 'unable to fix', }, { desc => 'no futher action status closes report', description => 'This is a note', external_id => 638344, start_state => 'confirmed', comment_status => 'NO_FURTHER_ACTION', mark_fixed => 0, mark_open => 0, problem_state => 'unable to fix', end_state => 'unable to fix', }, { desc => 'fixed status sets closed report as fixed', description => 'This is a note', external_id => 638344, start_state => 'unable to fix', comment_status => 'FIXED', mark_fixed => 0, mark_open => 0, problem_state => 'fixed - council', end_state => 'fixed - council', }, { desc => 'open status does not re-open hidden report', description => 'This is a note', external_id => 638344, start_state => 'hidden', comment_status => 'OPEN', mark_fixed => 0, mark_open => 0, problem_state => 'confirmed', end_state => 'hidden', }, ) { subtest $test->{desc} => sub { my $local_requests_xml = $requests_xml; my $updated_datetime = sprintf( '%s', $dt ); $local_requests_xml =~ s/UPDATED_DATETIME/$updated_datetime/; $local_requests_xml =~ s#\d+#@{[$problem->external_id]}#; $local_requests_xml =~ s#\d+#@{[$problem->id]}#; $local_requests_xml =~ s#\w+#$test->{comment_status}# if $test->{comment_status}; my $o = Open311->new( jurisdiction => 'mysociety', endpoint => 'http://example.com', test_mode => 1, test_get_returns => { 'servicerequestupdates.xml' => $local_requests_xml } ); $problem->comments->delete; $problem->lastupdate( DateTime->now()->subtract( days => 1 ) ); $problem->state( $test->{start_state} ); $problem->update; my $council_details = { areas => { 2482 => 1 } }; my $update = Open311::GetServiceRequestUpdates->new( system_user => $user ); $update->update_comments( $o, $council_details ); is $problem->comments->count, 1, 'comment count'; $problem->discard_changes; my $c = FixMyStreet::App->model('DB::Comment')->search( { external_id => $test->{external_id} } )->first; ok $c, 'comment exists'; is $c->text, $test->{description}, 'text correct'; is $c->mark_fixed, $test->{mark_fixed}, 'mark_closed correct'; is $c->problem_state, $test->{problem_state}, 'problem_state correct'; is $c->mark_open, $test->{mark_open}, 'mark_open correct'; is $problem->state, $test->{end_state}, 'correct problem state'; }; } foreach my $test ( { desc => 'date for comment correct', updated_datetime => sprintf( '%s', $dt ), external_id => 638344, }, ) { subtest $test->{desc} => sub { my $dt = DateTime->now(); $dt->subtract( minutes => 10 ); my $local_requests_xml = $requests_xml; my $updated = sprintf( '%s', DateTime::Format::W3CDTF->format_datetime( $dt ) ); $local_requests_xml =~ s/UPDATED_DATETIME/$updated/; $local_requests_xml =~ s#\d+#@{[$problem->external_id]}#; $local_requests_xml =~ s#\d+#@{[$problem->id]}#; my $o = Open311->new( jurisdiction => 'mysociety', endpoint => 'http://example.com', test_mode => 1, test_get_returns => { 'servicerequestupdates.xml' => $local_requests_xml } ); $problem->comments->delete; my $council_details = { areas => { 2482 => 1 } }; my $update = Open311::GetServiceRequestUpdates->new( system_user => $user ); $update->update_comments( $o, $council_details ); my $comment = $problem->comments->first; is $comment->created, $dt, 'created date set to date from XML'; is $comment->confirmed, $dt, 'confirmed date set to date from XML'; }; } my $problem2 = $problem_rs->new( { postcode => 'EH99 1SP', latitude => 1, longitude => 1, areas => 1, title => '', detail => '', used_map => 1, user_id => 1, name => '', state => 'confirmed', service => '', cobrand => 'default', cobrand_data => '', user => $user, created => DateTime->now(), lastupdate => DateTime->now(), anonymous => 1, external_id => $problem->external_id, bodies_str => 2651, } ); $problem2->insert(); $problem->comments->delete; $problem2->comments->delete; for my $test ( { desc => 'identical external_ids on problem resolved using council', updated_datetime => sprintf( '%s', $dt ), external_id => 638344, area_id => 2651, request_id => $problem2->external_id, request_id_ext => $problem2->id, p1_comments => 0, p2_comments => 1, }, { desc => 'identical external_ids on comments resolved', updated_datetime => sprintf( '%s', $dt ), external_id => 638344, area_id => 2482, request_id => $problem->external_id, request_id_ext => $problem->id, p1_comments => 1, p2_comments => 1, }, ) { subtest $test->{desc} => sub { my $local_requests_xml = $requests_xml; $local_requests_xml =~ s/UPDATED_DATETIME/$test->{updated_datetime}/; $local_requests_xml =~ s#\d+#$test->{request_id}#; $local_requests_xml =~ s#\d+#$test->{request_id_ext}#; my $o = Open311->new( jurisdiction => 'mysociety', endpoint => 'http://example.com', test_mode => 1, test_get_returns => { 'servicerequestupdates.xml' => $local_requests_xml } ); my $council_details = { areas => { $test->{area_id} => 1 } }; my $update = Open311::GetServiceRequestUpdates->new( system_user => $user ); $update->update_comments( $o, $council_details ); is $problem->comments->count, $test->{p1_comments}, 'comment count for first problem'; is $problem2->comments->count, $test->{p2_comments}, 'comment count for second problem'; }; } subtest 'using start and end date' => sub { my $local_requests_xml = $requests_xml; my $o = Open311->new( jurisdiction => 'mysociety', endpoint => 'http://example.com', test_mode => 1, test_get_returns => { 'servicerequestupdates.xml' => $local_requests_xml } ); my $start_dt = DateTime->now(); $start_dt->subtract( days => 1 ); my $end_dt = DateTime->now(); my $update = Open311::GetServiceRequestUpdates->new( system_user => $user, start_date => $start_dt, ); my $res = $update->update_comments( $o ); is $res, 0, 'returns 0 if start but no end date'; $update = Open311::GetServiceRequestUpdates->new( system_user => $user, end_date => $end_dt, ); $res = $update->update_comments( $o ); is $res, 0, 'returns 0 if end but no start date'; $update = Open311::GetServiceRequestUpdates->new( system_user => $user, start_date => $start_dt, end_date => $end_dt, ); my $council_details = { areas => { 2482 => 1 } }; $update->update_comments( $o, $council_details ); my $start = $start_dt . ''; my $end = $end_dt . ''; my $uri = URI->new( $o->test_uri_used ); my $c = CGI::Simple->new( $uri->query ); is $c->param('start_date'), $start, 'start date used'; is $c->param('end_date'), $end, 'end date used'; }; subtest 'check that existing comments are not duplicated' => sub { my $requests_xml = qq{ 638344 @{[ $problem->external_id ]} open This is a note UPDATED_DATETIME 638354 @{[ $problem->external_id ]} open This is a different note UPDATED_DATETIME2 }; $problem->comments->delete; my $comment = FixMyStreet::App->model('DB::Comment')->new( { problem => $problem, external_id => 638344, text => 'This is a note', user => $user, state => 'confirmed', mark_fixed => 0, anonymous => 0, confirmed => $dt, } ); $comment->insert; is $problem->comments->count, 1, 'one comment before fetching updates'; $requests_xml =~ s/UPDATED_DATETIME2/$dt/; $requests_xml =~ s/UPDATED_DATETIME/@{[ $comment->confirmed ]}/; my $o = Open311->new( jurisdiction => 'mysociety', endpoint => 'http://example.com', test_mode => 1, test_get_returns => { 'servicerequestupdates.xml' => $requests_xml } ); my $update = Open311::GetServiceRequestUpdates->new( system_user => $user, ); my $council_details = { areas => { 2482 => 1 } }; $update->update_comments( $o, $council_details ); $problem->discard_changes; is $problem->comments->count, 2, 'two comments after fetching updates'; $update->update_comments( $o, $council_details ); $problem->discard_changes; is $problem->comments->count, 2, 're-fetching updates does not add comments'; $problem->comments->delete; $update->update_comments( $o, $council_details ); $problem->discard_changes; is $problem->comments->count, 2, 'if comments are deleted then they are added'; }; foreach my $test ( { desc => 'check that closed and then open comment results in correct state', dt1 => $dt->subtract( hours => 1 ), dt2 => $dt, }, { desc => 'check that old comments do not change problem status', dt1 => $dt->subtract( hours => 2 ), dt2 => $dt, } ) { subtest $test->{desc} => sub { my $requests_xml = qq{ 638344 @{[ $problem->external_id ]} closed This is a note UPDATED_DATETIME 638354 @{[ $problem->external_id ]} open This is a different note UPDATED_DATETIME2 }; $problem->comments->delete; $problem->state( 'confirmed' ); $problem->lastupdate( $dt->subtract( hours => 3 ) ); $problem->update; $requests_xml =~ s/UPDATED_DATETIME/$test->{dt1}/; $requests_xml =~ s/UPDATED_DATETIME2/$test->{dt2}/; my $o = Open311->new( jurisdiction => 'mysociety', endpoint => 'http://example.com', test_mode => 1, test_get_returns => { 'servicerequestupdates.xml' => $requests_xml } ); my $update = Open311::GetServiceRequestUpdates->new( system_user => $user, ); my $council_details = { areas => { 2482 => 1 } }; $update->update_comments( $o, $council_details ); $problem->discard_changes; is $problem->comments->count, 2, 'two comments after fetching updates'; is $problem->state, 'confirmed', 'correct problem status'; }; } foreach my $test ( { desc => 'normally alerts are not suppressed', num_alerts => 1, suppress_alerts => 0, }, { desc => 'alerts suppressed if suppress_alerts set', num_alerts => 1, suppress_alerts => 1, }, { desc => 'alert suppression ok even if no alerts', num_alerts => 0, suppress_alerts => 1, }, { desc => 'alert suppression ok even if 2x alerts', num_alerts => 2, suppress_alerts => 1, } ) { subtest $test->{desc} => sub { my $requests_xml = qq{ 638344 @{[ $problem->external_id ]} closed This is a note UPDATED_DATETIME }; $problem->comments->delete; $problem->state( 'confirmed' ); $problem->lastupdate( $dt->subtract( hours => 3 ) ); $problem->update; my @alerts = map { my $alert = FixMyStreet::App->model('DB::Alert')->create( { alert_type => 'new_updates', parameter => $problem->id, confirmed => 1, user_id => $problem->user->id, } ) } (1..$test->{num_alerts}); $requests_xml =~ s/UPDATED_DATETIME/$dt/; my $o = Open311->new( jurisdiction => 'mysociety', endpoint => 'http://example.com', test_mode => 1, test_get_returns => { 'servicerequestupdates.xml' => $requests_xml } ); my $update = Open311::GetServiceRequestUpdates->new( system_user => $user, suppress_alerts => $test->{suppress_alerts}, ); my $council_details = { areas => { 2482 => 1 } }; $update->update_comments( $o, $council_details ); $problem->discard_changes; my $alerts_sent = FixMyStreet::App->model('DB::AlertSent')->search( { alert_id => [ map $_->id, @alerts ], parameter => $problem->comments->first->id, } ); if ( $test->{suppress_alerts} ) { is $alerts_sent->count(), $test->{num_alerts}, 'alerts suppressed'; } else { is $alerts_sent->count(), 0, 'alerts not suppressed'; } $alerts_sent->delete; for my $alert (@alerts) { $alert->delete; } } } $problem2->comments->delete(); $problem->comments->delete(); $problem2->delete; $problem->delete; $user->comments->delete; $user->problems->delete; $user->delete; done_testing();