diff options
Diffstat (limited to 't/cobrand/zurich.t')
-rw-r--r-- | t/cobrand/zurich.t | 995 |
1 files changed, 995 insertions, 0 deletions
diff --git a/t/cobrand/zurich.t b/t/cobrand/zurich.t new file mode 100644 index 000000000..0a84d2d03 --- /dev/null +++ b/t/cobrand/zurich.t @@ -0,0 +1,995 @@ +# TODO +# Overdue alerts + +use strict; +use warnings; +use DateTime; +use Email::MIME; +use LWP::Protocol::PSGI; +use Test::More; +use Test::LongString; +use Path::Tiny; +use t::Mock::MapItZurich; + +# Check that you have the required locale installed - the following +# should return a line with de_CH.utf8 in. If not install that locale. +# +# locale -a | grep de_CH +# +# To generate the translations use: +# +# commonlib/bin/gettext-makemo FixMyStreet + +use FixMyStreet; +my $cobrand = FixMyStreet::Cobrand::Zurich->new(); + +my $sample_file = path(__FILE__)->parent->parent->child("app/controller/sample.jpg"); +ok $sample_file->exists, "sample file $sample_file exists"; +my $sample_photo = $sample_file->slurp_raw; + +# This is a helper method that will send the reports but with the config +# correctly set - notably STAGING_FLAGS send_reports needs to be true, and +# zurich must be allowed cobrand if we want to be able to call cobrand +# methods on it. +sub send_reports_for_zurich { + FixMyStreet::override_config { + STAGING_FLAGS => { send_reports => 1 }, + ALLOWED_COBRANDS => ['zurich'] + }, sub { + # Actually send the report + FixMyStreet::DB->resultset('Problem')->send_reports('zurich'); + }; +} +sub reset_report_state { + my ($report, $created) = @_; + $report->discard_changes; + $report->unset_extra_metadata('moderated_overdue'); + $report->unset_extra_metadata('subdiv_overdue'); + $report->unset_extra_metadata('closed_overdue'); + $report->unset_extra_metadata('closure_status'); + $report->whensent(undef); + $report->state('unconfirmed'); + $report->created($created) if $created; + $report->update; +} + +use FixMyStreet::TestMech; +my $mech = FixMyStreet::TestMech->new; + +# Front page test +ok $mech->host("zurich.example.com"), "change host to Zurich"; +FixMyStreet::override_config { + ALLOWED_COBRANDS => [ 'zurich' ], +}, sub { + $mech->get_ok('/'); +}; +$mech->content_like( qr/zurich/i ); + +# Set up bodies +my $zurich = $mech->create_body_ok( 1, 'Zurich' ); +$zurich->parent( undef ); +$zurich->update; +my $division = $mech->create_body_ok( 2, 'Division 1' ); +$division->parent( $zurich->id ); +$division->send_method( 'Zurich' ); +$division->endpoint( 'division@example.org' ); +$division->update; +$division->body_areas->find_or_create({ area_id => 423017 }); +my $subdivision = $mech->create_body_ok( 3, 'Subdivision A' ); +$subdivision->parent( $division->id ); +$subdivision->send_method( 'Zurich' ); +$subdivision->endpoint( 'subdivision@example.org' ); +$subdivision->update; +my $external_body = $mech->create_body_ok( 4, 'External Body' ); +$external_body->send_method( 'Zurich' ); +$external_body->endpoint( 'external_body@example.net' ); +$external_body->update; + +sub get_export_rows_count { + my $mech = shift; + FixMyStreet::override_config { + ALLOWED_COBRANDS => [ 'zurich' ], + }, sub { + $mech->get_ok( '/admin/stats?export=1' ); + }; + is $mech->res->code, 200, 'csv retrieved ok'; + is $mech->content_type, 'text/csv', 'content_type correct' and do { + my @lines = split /\n/, $mech->content; + return @lines - 1; + }; + return; +} + +my $EXISTING_REPORT_COUNT = 0; + +my $superuser; +subtest "set up superuser" => sub { + $superuser = $mech->log_in_ok( 'super@example.org' ); + # a user from body $zurich is a superuser, as $zurich has no parent id! + $superuser->update({ from_body => $zurich->id }); + $EXISTING_REPORT_COUNT = get_export_rows_count($mech); + $mech->log_out_ok; +}; + +my @reports = $mech->create_problems_for_body( 1, $division->id, 'Test', { + state => 'unconfirmed', + confirmed => undef, + cobrand => 'zurich', + photo => $sample_photo, + areas => ',423017,', +}); +my $report = $reports[0]; + +FixMyStreet::override_config { + ALLOWED_COBRANDS => [ 'zurich' ], + MAP_TYPE => 'Zurich,OSM', +}, sub { + $mech->get_ok( '/report/' . $report->id ); +}; +$mech->content_contains('Überprüfung ausstehend') + or die $mech->content; + +# Check logging in to deal with this report +FixMyStreet::override_config { + ALLOWED_COBRANDS => [ 'zurich' ], +}, sub { + $mech->get_ok( '/admin' ); + is $mech->uri->path, '/auth', "got sent to the sign in page"; + + my $user = $mech->log_in_ok( 'dm1@example.org') ; + $user->from_body( undef ); + $user->update; + ok $mech->get( '/admin' ); + is $mech->res->code, 403, 'Got 403'; + $user->from_body( $division->id ); + $user->update; + + $mech->get_ok( '/admin' ); +}; +is $mech->uri->path, '/admin', "am logged in"; + +$mech->content_contains( 'report_edit/' . $report->id ); +$mech->content_contains( DateTime->now->strftime("%d.%m.%Y") ); +$mech->content_contains( 'Erfasst' ); + +subtest "changing of categories" => sub { + # create a few categories (which are actually contacts) + foreach my $name ( qw/Cat1 Cat2/ ) { + $mech->create_contact_ok( + body => $division, + category => $name, + email => "$name\@example.org", + ); + } + + # full Categories dropdown is hidden for unconfirmed reports + $report->update({ state => 'confirmed' }); + + # put report into known category + my $original_category = $report->category; + $report->update({ category => 'Cat1' }); + is( $report->category, "Cat1", "Category set to Cat1" ); + + # get the latest comment + my $comments_rs = $report->comments->search({},{ order_by => { -desc => "created" } }); + ok ( !$comments_rs->first, "There are no comments yet" ); + + # change the category via the web interface + FixMyStreet::override_config { + ALLOWED_COBRANDS => [ 'zurich' ], + MAP_TYPE => 'Zurich,OSM', + }, sub { + $mech->get_ok( '/admin/report_edit/' . $report->id ); + $mech->submit_form_ok( { with_fields => { category => 'Cat2' } } ); + }; + + # check changes correctly saved + $report->discard_changes(); + is( $report->category, "Cat2", "Category changed to Cat2 as expected" ); + + # Check that a new comment has been created. + my $new_comment = $comments_rs->first(); + is( $new_comment->text, "Weitergeleitet von Cat1 an Cat2", "category change comment created" ); + + # restore report to original category. + $report->update({category => $original_category }); +}; + +sub get_moderated_count { + # my %date_params = ( ); + # my $moderated = FixMyStreet::DB->resultset('Problem')->search({ + # extra => { like => '%moderated_overdue,I1:0%' }, %date_params } )->count; + # return $moderated; + + # use a separate mech to avoid stomping on test state + my $mech = FixMyStreet::TestMech->new; + my $user = $mech->log_in_ok( 'super@example.org' ); + + FixMyStreet::override_config { + ALLOWED_COBRANDS => [ 'zurich' ], + }, sub { + $mech->get( '/admin/stats' ); + }; + if ($mech->content =~/Innerhalb eines Arbeitstages moderiert: (\d+)/) { + return $1; + } + else { + fail sprintf "Could not get moderation results (%d)", $mech->status; + return undef; + } +} + +subtest "report_edit" => sub { + + FixMyStreet::override_config { + ALLOWED_COBRANDS => [ 'zurich' ], + MAP_TYPE => 'Zurich,OSM', + }, sub { + + reset_report_state($report); + ok ( ! $report->get_extra_metadata('moderated_overdue'), 'Report currently unmoderated' ); + is get_moderated_count(), 0; + + $mech->get_ok( '/admin/report_edit/' . $report->id ); + $mech->content_contains( 'Unbestätigt' ); # Unconfirmed email + $mech->submit_form_ok( { with_fields => { state => 'confirmed' } } ); + $mech->get_ok( '/report/' . $report->id ); + + $report->discard_changes(); + + $mech->content_contains('Aufgenommen'); + $mech->content_contains('Test Test'); + $mech->content_lacks('photo/' . $report->id . '.0.jpeg'); + $mech->email_count_is(0); + + $report->discard_changes; + + is ( $report->get_extra_metadata('moderated_overdue'), 0, 'Report now marked moderated' ); + is get_moderated_count(), 1; + + # Set state back to 10 days ago so that report is overdue + my $created = $report->created; + reset_report_state($report, $created->clone->subtract(days => 10)); + + is get_moderated_count(), 0; + + $mech->get_ok( '/admin/report_edit/' . $report->id ); + $mech->submit_form_ok( { with_fields => { state => 'confirmed' } } ); + $mech->get_ok( '/report/' . $report->id ); + + $report->discard_changes; + is ( $report->get_extra_metadata('moderated_overdue'), 1, 'moderated_overdue set correctly when overdue' ); + is get_moderated_count(), 0, 'Moderated count not increased when overdue'; + + reset_report_state($report, $created); + + $mech->get_ok( '/admin/report_edit/' . $report->id ); + $mech->submit_form_ok( { with_fields => { state => 'confirmed' } } ); + $mech->get_ok( '/report/' . $report->id ); + $report->discard_changes; + is ( $report->get_extra_metadata('moderated_overdue'), 0, 'Marking confirmed sets moderated_overdue' ); + is ( $report->get_extra_metadata('closed_overdue'), undef, 'Marking confirmed does NOT set closed_overdue' ); + is get_moderated_count(), 1; + + $mech->get_ok( '/admin/report_edit/' . $report->id ); + $mech->submit_form_ok( { with_fields => { state => 'hidden' } } ); + $mech->get_ok( '/admin/report_edit/' . $report->id ); + + $report->discard_changes; + is ( $report->get_extra_metadata('moderated_overdue'), 0, 'Still marked moderated_overdue' ); + is ( $report->get_extra_metadata('closed_overdue'), undef, "Marking hidden doesn't set closed_overdue..." ); + is ( $report->state, 'planned', 'Marking hidden actually sets state to planned'); + is ( $report->get_extra_metadata('closure_status'), 'hidden', 'Marking hidden sets closure_status to hidden'); + is get_moderated_count(), 1, 'Check still counted moderated' + or diag $report->get_column('extra'); + + # publishing actually sets hidden + $mech->form_with_fields( 'status_update' ); + $mech->submit_form_ok( { button => 'publish_response' } ); + $mech->get_ok( '/admin/report_edit/' . $report->id ); + $report->discard_changes; + + is ( $report->get_extra_metadata('closed_overdue'), 0, "Closing as hidden sets closed_overdue..." ); + is ( $report->state, 'hidden', 'Closing as hidden sets state to hidden'); + is ( $report->get_extra_metadata('closure_status'), undef, 'Closing as hidden unsets closure_status'); + + + reset_report_state($report); + is ( $report->get_extra_metadata('moderated_overdue'), undef, 'Sanity check' ); + is get_moderated_count(), 0; + + # Check that setting to 'hidden' also triggers moderation + $mech->get_ok( '/admin/report_edit/' . $report->id ); + $mech->submit_form_ok( { with_fields => { state => 'hidden' } } ); + $mech->get_ok( '/admin/report_edit/' . $report->id ); + $mech->form_with_fields( 'status_update' ); + $mech->submit_form_ok( { button => 'publish_response' } ); + + $report->discard_changes; + is ( $report->get_extra_metadata('moderated_overdue'), 0, 'Marking hidden from scratch sets moderated_overdue' ); + is ( $report->get_extra_metadata('closed_overdue'), 0, 'Marking hidden from scratch also set closed_overdue' ); + is get_moderated_count(), 1; + + is ($cobrand->get_or_check_overdue($report), 0, 'sanity check'); + $report->update({ created => $created->clone->subtract(days => 10) }); + is ($cobrand->get_or_check_overdue($report), 0, 'overdue call not increased'); + + reset_report_state($report, $created); + } +}; + +FixMyStreet::override_config { + ALLOWED_COBRANDS => [ 'zurich' ], + MAP_TYPE => 'Zurich,OSM', +}, sub { + # Photo publishing + $mech->get_ok( '/admin/report_edit/' . $report->id ); + $mech->submit_form_ok( { with_fields => { state => 'confirmed', publish_photo => 1 } } ); + $mech->get_ok( '/report/' . $report->id ); + $mech->content_contains('photo/' . $report->id . '.0.jpeg'); + + # Internal notes + $mech->get_ok( '/admin/report_edit/' . $report->id ); + $mech->submit_form_ok( { with_fields => { new_internal_note => 'Initial internal note.' } } ); + $mech->submit_form_ok( { with_fields => { new_internal_note => 'Another internal note.' } } ); + $mech->content_contains( 'Initial internal note.' ); + $mech->content_contains( 'Another internal note.' ); + + # Original description + $mech->submit_form_ok( { with_fields => { detail => 'Edited details text.' } } ); + $mech->content_contains( 'Edited details text.' ); + $mech->content_contains( 'Originaltext: “Test Test 1 for ' . $division->id . ' Detail”' ); + + $mech->get_ok( '/admin/report_edit/' . $report->id ); + $mech->submit_form_ok( { with_fields => { body_subdivision => $subdivision->id } } ); + + $mech->get_ok( '/report/' . $report->id ); + $mech->content_contains('In Bearbeitung'); + $mech->content_contains('Test Test'); +}; + +send_reports_for_zurich(); +my $email = $mech->get_email; +like $email->header('Subject'), qr/Neue Meldung/, 'subject looks okay'; +like $email->header('To'), qr/subdivision\@example.org/, 'to line looks correct'; +$mech->clear_emails_ok; + +$mech->log_out_ok; + +subtest 'SDM' => sub { + my $user = $mech->log_in_ok( 'sdm1@example.org') ; + $user->update({ from_body => undef }); + FixMyStreet::override_config { + ALLOWED_COBRANDS => [ 'zurich' ], + }, sub { + ok $mech->get( '/admin' ); + }; + is $mech->res->code, 403, 'Got 403'; + $user->from_body( $subdivision->id ); + $user->update; + + FixMyStreet::override_config { + ALLOWED_COBRANDS => [ 'zurich' ], + }, sub { + $mech->get_ok( '/admin' ); + }; + is $mech->uri->path, '/admin', "am logged in"; + + $mech->content_contains( 'report_edit/' . $report->id ); + $mech->content_contains( DateTime->now->strftime("%d.%m.%Y") ); + $mech->content_contains( 'In Bearbeitung' ); + + FixMyStreet::override_config { + ALLOWED_COBRANDS => [ 'zurich' ], + MAP_TYPE => 'Zurich,OSM', + }, sub { + $mech->get_ok( '/admin/report_edit/' . $report->id ); + $mech->content_contains( 'Initial internal note' ); + + $mech->submit_form_ok( { with_fields => { status_update => 'This is an update.' } } ); + is $mech->uri->path, '/admin/report_edit/' . $report->id, "still on edit page"; + $mech->content_contains('This is an update'); + ok $mech->form_with_fields( 'status_update' ); + $mech->submit_form_ok( { button => 'no_more_updates' } ); + is $mech->uri->path, '/admin/summary', "redirected now finished with report."; + + $mech->get_ok( '/report/' . $report->id ); + $mech->content_contains('In Bearbeitung'); + $mech->content_contains('Test Test'); + }; + + send_reports_for_zurich(); + $email = $mech->get_email; + like $email->header('Subject'), qr/Feedback/, 'subject looks okay'; + like $email->header('To'), qr/division\@example.org/, 'to line looks correct'; + $mech->clear_emails_ok; + + $report->discard_changes; + is $report->state, 'planned', 'Report now in planned state'; + + subtest 'send_back' => sub { + FixMyStreet::override_config { + ALLOWED_COBRANDS => [ 'zurich' ], + MAP_TYPE => 'Zurich,OSM', + }, sub { + $report->update({ bodies_str => $subdivision->id, state => 'in progress' }); + $mech->get_ok( '/admin/report_edit/' . $report->id ); + $mech->submit_form_ok( { form_number => 2, button => 'send_back' } ); + $report->discard_changes; + is $report->state, 'confirmed', 'Report sent back to confirmed state'; + is $report->bodies_str, $division->id, 'Report sent back to division'; + }; + }; + + subtest 'not contactable' => sub { + FixMyStreet::override_config { + ALLOWED_COBRANDS => [ 'zurich' ], + MAP_TYPE => 'Zurich,OSM', + }, sub { + $report->update({ bodies_str => $subdivision->id, state => 'in progress' }); + $mech->get_ok( '/admin/report_edit/' . $report->id ); + $mech->submit_form_ok( { button => 'not_contactable', form_number => 2 } ); + $report->discard_changes; + is $report->state, 'planned', 'Report sent back to Rueckmeldung ausstehend state'; + is $report->get_extra_metadata('closure_status'), 'partial', 'Report sent back to partial (not_contactable) state'; + is $report->bodies_str, $division->id, 'Report sent back to division'; + }; + }; + + $mech->log_out_ok; +}; + +my $user = $mech->log_in_ok( 'dm1@example.org') ; +FixMyStreet::override_config { + ALLOWED_COBRANDS => [ 'zurich' ], +}, sub { + $mech->get_ok( '/admin' ); +}; + +reset_report_state($report); +$report->update({ state => 'planned' }); + +$mech->content_contains( 'report_edit/' . $report->id ); +$mech->content_contains( DateTime->now->strftime("%d.%m.%Y") ); + +# User confirms their email address +$report->set_extra_metadata(email_confirmed => 1); +$report->update; + +FixMyStreet::override_config { + ALLOWED_COBRANDS => [ 'zurich' ], + MAP_TYPE => 'Zurich,OSM', +}, sub { + $mech->get_ok( '/admin/report_edit/' . $report->id ); + $mech->content_lacks( 'Unbestätigt' ); # Confirmed email + $mech->submit_form_ok( { with_fields => { status_update => 'FINAL UPDATE' } } ); + $mech->form_with_fields( 'status_update' ); + $mech->submit_form_ok( { button => 'publish_response' } ); + + $mech->get_ok( '/report/' . $report->id ); +}; +$mech->content_contains('Beantwortet'); +$mech->content_contains('Test Test'); +$mech->content_contains('FINAL UPDATE'); + +$email = $mech->get_email; +like $email->header('To'), qr/test\@example.com/, 'to line looks correct'; +like $email->header('From'), qr/do-not-reply\@example.org/, 'from line looks correct'; +like $email->body, qr/FINAL UPDATE/, 'body looks correct'; +$mech->clear_emails_ok; + +# Assign planned (via confirmed), don't confirm email +@reports = $mech->create_problems_for_body( 1, $division->id, 'Second', { + state => 'unconfirmed', + confirmed => undef, + cobrand => 'zurich', + photo => $sample_photo, + areas => ',423017,', +}); +$report = $reports[0]; + +FixMyStreet::override_config { + ALLOWED_COBRANDS => [ 'zurich' ], + MAP_TYPE => 'Zurich,OSM', +}, sub { + $mech->get_ok( '/admin/report_edit/' . $report->id ); + $mech->submit_form_ok( { with_fields => { state => 'confirmed' } } ); + $mech->get_ok( '/admin/report_edit/' . $report->id ); + $mech->submit_form_ok( { with_fields => { state => 'planned' } } ); + $mech->get_ok( '/report/' . $report->id ); +}; +$mech->content_contains('In Bearbeitung'); +$mech->content_contains('Second Test'); + +FixMyStreet::override_config { + ALLOWED_COBRANDS => [ 'zurich' ], + MAP_TYPE => 'Zurich,OSM', +}, sub { + $mech->get_ok( '/admin/report_edit/' . $report->id ); + $mech->content_contains( 'Unbestätigt' ); + $report->discard_changes; + $mech->form_with_fields( 'status_update' ); + $mech->submit_form_ok( { button => 'publish_response', with_fields => { status_update => 'FINAL UPDATE' } } ); + + $mech->get_ok( '/report/' . $report->id ); +}; +$mech->content_contains('Beantwortet'); +$mech->content_contains('Second Test'); +$mech->content_contains('FINAL UPDATE'); + +$mech->email_count_is(0); + +# Report assigned to third party + +@reports = $mech->create_problems_for_body( 1, $division->id, 'Third', { + state => 'unconfirmed', + confirmed => undef, + cobrand => 'zurich', + photo => $sample_photo, + areas => ',423017,', +}); +$report = $reports[0]; + +subtest "external report triggers email" => sub { + my $EXTERNAL_MESSAGE = 'Look Ma, no hands!'; + FixMyStreet::override_config { + ALLOWED_COBRANDS => [ 'zurich' ], + MAP_TYPE => 'Zurich,OSM', + }, sub { + + # required to see body_external field + $report->state('planned'); + $report->set_extra_metadata('closure_status' => 'closed'); + # Set the public_response manually here because the default one will have line breaks that get escaped as HTML, causing the comparison to fail. + $report->set_extra_metadata('public_response' => 'Freundliche Gruesse Ihre Stadt Zuerich'); + $report->update; + + $mech->get_ok( '/admin/report_edit/' . $report->id ); + $mech->form_with_fields( 'publish_response' ); + $mech->submit_form_ok( { + button => 'publish_response', + with_fields => { + body_external => $external_body->id, + external_message => $EXTERNAL_MESSAGE, + } }); + $report->discard_changes; + $mech->get_ok( '/report/' . $report->id ); + }; + is ($report->state, 'closed', 'Report was closed correctly'); + $mech->content_contains('Extern') + or die $mech->content; + $mech->content_contains('Third Test'); + $mech->content_contains($report->get_extra_metadata('public_response')) or die $mech->content; + send_reports_for_zurich(); + $email = $mech->get_email; + like $email->header('Subject'), qr/Weitergeleitete Meldung/, 'subject looks okay'; + like $email->header('To'), qr/external_body\@example.net/, 'to line looks correct'; + like $email->body, qr/External Body/, 'body has right name'; + like $email->body, qr/$EXTERNAL_MESSAGE/, 'external_message was passed on'; + unlike $email->body, qr/test\@example.com/, 'body does not contain email address'; + $mech->clear_emails_ok; + + subtest "Test third_personal boolean setting" => sub { + FixMyStreet::override_config { + ALLOWED_COBRANDS => [ 'zurich' ], + MAP_TYPE => 'Zurich,OSM', + }, sub { + $mech->get_ok( '/admin' ); + # required to see body_external field + $report->state('planned'); + $report->set_extra_metadata('closure_status' => 'closed'); + $report->set_extra_metadata('public_response' => 'Freundliche Gruesse Ihre Stadt Zuerich'); + $report->update; + + is $mech->uri->path, '/admin', "am logged in"; + $mech->content_contains( 'report_edit/' . $report->id ); + $mech->get_ok( '/admin/report_edit/' . $report->id ); + $mech->form_with_fields( 'publish_response' ); + $mech->submit_form_ok( { + button => 'publish_response', + with_fields => { + body_external => $external_body->id, + third_personal => 1, + } }); + $mech->get_ok( '/report/' . $report->id ); + }; + $mech->content_contains('Extern'); + $mech->content_contains('Third Test'); + $mech->content_contains($report->get_extra_metadata('public_response')); + send_reports_for_zurich(); + $email = $mech->get_email; + like $email->header('Subject'), qr/Weitergeleitete Meldung/, 'subject looks okay'; + like $email->header('To'), qr/external_body\@example.net/, 'to line looks correct'; + like $email->body, qr/External Body/, 'body has right name'; + like $email->body, qr/test\@example.com/, 'body does contain email address'; + $mech->clear_emails_ok; + }; + + subtest "Test external wish sending" => sub { + FixMyStreet::override_config { + ALLOWED_COBRANDS => [ 'zurich' ], + MAP_TYPE => 'Zurich,OSM', + }, sub { + # set as wish + $report->discard_changes; + $report->state('planned'); + $report->set_extra_metadata('closure_status' => 'investigating'); + $report->update; + is ($report->state, 'planned', 'Sanity check') or die; + + $mech->get_ok( '/admin/report_edit/' . $report->id ); + + $mech->form_with_fields( 'publish_response' ); + $mech->submit_form_ok( { + button => 'publish_response', + with_fields => { + body_external => $external_body->id, + external_message => $EXTERNAL_MESSAGE, + } }); + }; + send_reports_for_zurich(); + $email = $mech->get_email; + like $email->header('Subject'), qr/Weitergeleitete Meldung/, 'subject looks okay'; + like $email->header('To'), qr/external_body\@example.net/, 'to line looks correct'; + like $email->body, qr/External Body/, 'body has right name'; + like $email->body, qr/$EXTERNAL_MESSAGE/, 'external_message was passed on'; + like $email->body, qr/test\@example.com/, 'body contains email address'; + $mech->clear_emails_ok; + }; + + subtest "Closure email includes public response" => sub { + my $PUBLIC_RESPONSE = "This is the public response to your report. Freundliche Gruesse."; + FixMyStreet::override_config { + ALLOWED_COBRANDS => [ 'zurich' ], + MAP_TYPE => 'Zurich,OSM', + }, sub { + # set as extern + reset_report_state($report); + $report->state('planned'); + $report->set_extra_metadata('closure_status' => 'closed'); + $report->set_extra_metadata('email_confirmed' => 1); + $report->unset_extra_metadata('public_response'); + $report->update; + is ($report->state, 'planned', 'Sanity check') or die; + + $mech->get_ok( '/admin/report_edit/' . $report->id ); + + $mech->form_with_fields( 'publish_response' ); + $mech->submit_form_ok( { + button => 'publish_response', + with_fields => { + body_external => $external_body->id, + external_message => $EXTERNAL_MESSAGE, + status_update => $PUBLIC_RESPONSE, + } }); + }; + $email = $mech->get_email; + my $report_id = $report->id; + like Encode::decode('MIME-Header', $email->header('Subject')), qr/Meldung #$report_id/, 'subject looks okay'; + like $email->header('To'), qr/test\@example.com/, 'to line looks correct'; + like $email->body, qr/$PUBLIC_RESPONSE/, 'public_response was passed on' or die $email->body; + $mech->clear_emails_ok; + }; + $report->comments->delete; # delete the comments, as they confuse later tests +}; + +subtest "superuser and dm can see stats" => sub { + $mech->log_out_ok; + $user = $mech->log_in_ok( 'super@example.org' ); + + FixMyStreet::override_config { + ALLOWED_COBRANDS => [ 'zurich' ], + }, sub { + $mech->get( '/admin/stats' ); + }; + is $mech->res->code, 200, "superuser should be able to see stats page"; + $mech->log_out_ok; + + $user = $mech->log_in_ok( 'dm1@example.org' ); + FixMyStreet::override_config { + ALLOWED_COBRANDS => [ 'zurich' ], + }, sub { + $mech->get( '/admin/stats' ); + }; + is $mech->res->code, 200, "dm can now also see stats page"; + $mech->log_out_ok; +}; + +subtest "only superuser can edit bodies" => sub { + $user = $mech->log_in_ok( 'dm1@example.org' ); + FixMyStreet::override_config { + ALLOWED_COBRANDS => [ 'zurich' ], + }, sub { + $mech->get( '/admin/body/' . $zurich->id ); + }; + is $mech->res->code, 403, "only superuser should be able to edit bodies"; + $mech->log_out_ok; +}; + +subtest "only superuser can see 'Add body' form" => sub { + LWP::Protocol::PSGI->register(t::Mock::MapItZurich->run_if_script, host => 'mapit.zurich'); + $user = $mech->log_in_ok( 'dm1@example.org' ); + FixMyStreet::override_config { + ALLOWED_COBRANDS => [ 'zurich' ], + MAPIT_URL => 'http://mapit.zurich/', + MAPIT_TYPES => [ 'O08' ], + MAPIT_ID_WHITELIST => [ 423017 ], + }, sub { + $mech->get_ok( '/admin/bodies' ); + }; + $mech->content_lacks( '<form method="post" action="bodies"' ); + $mech->log_out_ok; +}; + +subtest "phone number is mandatory" => sub { + LWP::Protocol::PSGI->register(t::Mock::MapItZurich->run_if_script, host => 'mapit.zurich'); + FixMyStreet::override_config { + MAPIT_TYPES => [ 'O08' ], + MAPIT_URL => 'http://mapit.zurich/', + ALLOWED_COBRANDS => [ 'zurich' ], + MAPIT_ID_WHITELIST => [ 423017 ], + MAP_TYPE => 'Zurich,OSM', + }, sub { + $user = $mech->log_in_ok( 'dm1@example.org' ); + $mech->get_ok( '/report/new?lat=47.381817&lon=8.529156' ); + $mech->submit_form( with_fields => { phone => "" } ); + $mech->content_contains( 'Diese Information wird benötigt' ); + $mech->log_out_ok; + }; +}; + +subtest "phone number is not mandatory for reports from mobile apps" => sub { + LWP::Protocol::PSGI->register(t::Mock::MapItZurich->run_if_script, host => 'mapit.zurich'); + FixMyStreet::override_config { + MAPIT_TYPES => [ 'O08' ], + MAPIT_URL => 'http://mapit.zurich/', + ALLOWED_COBRANDS => [ 'zurich' ], + MAPIT_ID_WHITELIST => [ 423017 ], + MAP_TYPE => 'Zurich,OSM', + }, sub { + $mech->post_ok( '/report/new/mobile?lat=47.381817&lon=8.529156' , { + service => 'iPhone', + detail => 'Problem-Bericht', + lat => 47.381817, + lon => 8.529156, + email => 'user@example.org', + pc => '', + name => '', + category => 'bad category', + }); + my $res = $mech->response; + ok $res->header('Content-Type') =~ m{^application/json\b}, 'response should be json'; + unlike $res->content, qr/Diese Information wird benötigt/, 'response should not contain phone error'; + # Clear out the mailq + $mech->clear_emails_ok; + }; +}; + +subtest "problems can't be assigned to deleted bodies" => sub { + LWP::Protocol::PSGI->register(t::Mock::MapItZurich->run_if_script, host => 'mapit.zurich'); + $user = $mech->log_in_ok( 'dm1@example.org' ); + $user->from_body( $zurich->id ); + $user->update; + $report->state( 'confirmed' ); + $report->update; + FixMyStreet::override_config { + ALLOWED_COBRANDS => [ 'zurich' ], + MAPIT_URL => 'http://mapit.zurich/', + MAPIT_TYPES => [ 'O08' ], + MAPIT_ID_WHITELIST => [ 423017 ], + MAP_TYPE => 'Zurich,OSM', + }, sub { + $mech->get_ok( '/admin/body/' . $external_body->id ); + $mech->submit_form_ok( { with_fields => { deleted => 1 } } ); + $mech->get_ok( '/admin/report_edit/' . $report->id ); + $mech->content_lacks( $external_body->name ) + or do { + diag $mech->content; + diag $external_body->name; + die; + }; + }; + $user->from_body( $division->id ); + $user->update; + $mech->log_out_ok; +}; + +subtest "photo must be supplied for categories that require it" => sub { + LWP::Protocol::PSGI->register(t::Mock::MapItZurich->run_if_script, host => 'mapit.zurich'); + FixMyStreet::App->model('DB::Contact')->find_or_create({ + body => $division, + category => "Graffiti - photo required", + email => "graffiti\@example.org", + confirmed => 1, + deleted => 0, + editor => "editor", + whenedited => DateTime->now(), + note => "note for graffiti", + extra => { photo_required => 1 } + }); + FixMyStreet::override_config { + MAPIT_TYPES => [ 'O08' ], + MAPIT_URL => 'http://mapit.zurich/', + ALLOWED_COBRANDS => [ 'zurich' ], + MAPIT_ID_WHITELIST => [ 423017 ], + MAP_TYPE => 'Zurich,OSM', + }, sub { + $mech->get_ok('/report/new?lat=47.381817&lon=8.529156'); + $mech->submit_form_ok({ with_fields => { + detail => 'Problem-Bericht', + email => 'user@example.org', + category => 'Graffiti - photo required', + }}); + is $mech->res->code, 200, "missing photo shouldn't return anything but 200"; + $mech->content_contains(_("Photo is required."), 'response should contain photo error message'); + }; +}; + +subtest "test stats" => sub { + FixMyStreet::override_config { + ALLOWED_COBRANDS => [ 'zurich' ], + }, sub { + $user = $mech->log_in_ok( 'super@example.org' ); + + $mech->get_ok( '/admin/stats' ); + is $mech->res->code, 200, "superuser should be able to see stats page"; + + $mech->content_contains('Innerhalb eines Arbeitstages moderiert: 3'); + $mech->content_contains('Innerhalb von fünf Arbeitstagen abgeschlossen: 3'); + # my @data = $mech->content =~ /(?:moderiert|abgeschlossen): \d+/g; + # diag Dumper(\@data); use Data::Dumper; + + my $export_count = get_export_rows_count($mech); + if (defined $export_count) { + is $export_count - $EXISTING_REPORT_COUNT, 3, 'Correct number of reports'; + $mech->content_contains('fixed - council'); + } + }; +}; + +subtest "test admin_log" => sub { + diag $report->id; + my @entries = FixMyStreet::DB->resultset('AdminLog')->search({ + object_type => 'problem', + object_id => $report->id, + }); + + # XXX: following is dependent on all of test up till now, rewrite to explicitly + # test which things need to be logged! + is scalar @entries, 4, 'State changes logged'; + is $entries[-1]->action, 'state change to closed', 'State change logged as expected'; +}; + +subtest 'email images to external partners' => sub { + FixMyStreet::override_config { + ALLOWED_COBRANDS => [ 'zurich' ], + MAP_TYPE => 'Zurich,OSM', + }, sub { + reset_report_state($report); + + my $photo = path(__FILE__)->parent->child('zurich-logo_portal.x.jpg')->slurp_raw; + my $photoset = FixMyStreet::App::Model::PhotoSet->new({ + data_items => [ $photo ], + }); + my $fileid = $photoset->data; + + $report->set_extra_metadata('publish_photo' => 1); + # The below email comparison must not have an external message. + $report->unset_extra_metadata('external_message'); + $report->update({ + state => 'closed', + photo => $fileid, + external_body => $external_body->id, + }); + + $mech->clear_emails_ok; + send_reports_for_zurich(); + + my @emails = $mech->get_email; + my $email_as_string = $mech->get_first_email(@emails); + my ($boundary) = $email_as_string =~ /boundary="([A-Za-z0-9.]*)"/ms; + my $email = Email::MIME->new($email_as_string); + + my $expected_email_content = path(__FILE__)->parent->child('zurich_attachments.txt')->slurp; + + my $REPORT_ID = $report->id; + $expected_email_content =~ s{Subject: (.*?)\r?\n}{ + my $subj = Encode::decode('MIME-Header', $1); + $subj =~ s{REPORT_ID}{$REPORT_ID}g; + 'Subject: ' . Email::MIME::Encode::mime_encode($subj, "utf-8") . "\n"; + }eg; + $expected_email_content =~ s{REPORT_ID}{$REPORT_ID}g; + $expected_email_content =~ s{BOUNDARY}{$boundary}g; + my $expected_email = Email::MIME->new($expected_email_content); + + my @email_parts; + $email->walk_parts(sub { + my ($part) = @_; + push @email_parts, [ { $part->header_pairs }, $part->body ]; + }); + my @expected_email_parts; + $expected_email->walk_parts(sub { + my ($part) = @_; + push @expected_email_parts, [ { $part->header_pairs }, $part->body ]; + }); + is_deeply \@email_parts, \@expected_email_parts, 'MIME email text ok' + or do { + (my $test_name = $0) =~ s{/}{_}g; + my $path = path("test-output-$test_name.tmp"); + $path->spew($email_as_string); + diag "Saved output in $path"; + }; + }; +}; + +subtest 'Status update shown as appropriate' => sub { + FixMyStreet::override_config { + ALLOWED_COBRANDS => [ 'zurich' ], + MAP_TYPE => 'Zurich,OSM', + }, sub { + # ALL closed states must hide the public_response edit, and public ones + # must show the answer in blue. + for (['planned', 1, 0, 0], + ['fixed - council', 0, 1, 0], + ['closed', 0, 1, 0], + ['hidden', 0, 0, 1]) + { + my ($state, $update, $public, $user_response) = @$_; + $report->update({ state => $state }); + $mech->get_ok( '/admin/report_edit/' . $report->id ); + $mech->contains_or_lacks($update, "name='status_update'"); + $mech->contains_or_lacks($public || $user_response, '<div class="admin-official-answer">'); + + if ($public) { + $mech->get_ok( '/report/' . $report->id ); + $mech->content_contains('Antwort</h4>'); + } + } + }; +}; + +subtest 'time_spent' => sub { + FixMyStreet::override_config { + ALLOWED_COBRANDS => [ 'zurich' ], + MAP_TYPE => 'Zurich,OSM', + }, sub { + my $report = $reports[0]; + + is $report->get_time_spent, 0, '0 minutes spent'; + $report->update({ state => 'in progress' }); + $mech->get_ok( '/admin/report_edit/' . $report->id ); + $mech->form_with_fields( 'time_spent' ); + $mech->submit_form_ok( { + with_fields => { + time_spent => 10, + } }); + is $report->get_time_spent, 10, '10 minutes spent'; + }; +}; + +$mech->log_out_ok; + +FixMyStreet::override_config { + ALLOWED_COBRANDS => [ 'zurich' ], + MAPIT_URL => 'http://mapit.zurich/', + MAPIT_TYPES => [ 'ZZZ' ], +}, sub { + LWP::Protocol::PSGI->register(t::Mock::MapItZurich->run_if_script, host => 'mapit.zurich'); + subtest 'users at the top level can be edited' => sub { + $mech->log_in_ok( $superuser->email ); + $mech->get_ok('/admin/user_edit/' . $superuser->id ); + }; +}; + +END { + $mech->delete_body($subdivision); + $mech->delete_body($division); + $mech->delete_body($zurich); + $mech->delete_body($external_body); + $mech->delete_user( 'dm1@example.org' ); + $mech->delete_user( 'sdm1@example.org' ); + ok $mech->host("www.fixmystreet.com"), "change host back"; + done_testing(); +} + +1; |