diff options
Diffstat (limited to 't')
93 files changed, 10717 insertions, 2552 deletions
diff --git a/t/00-check-config.t b/t/00-check-config.t index 20f0fe8cf..00e782e74 100644 --- a/t/00-check-config.t +++ b/t/00-check-config.t @@ -16,7 +16,8 @@ use FixMyStreet; # load the config file and store the contents in a readonly hash my $example_config = YAML::LoadFile( FixMyStreet->path_to("conf/general.yml-example") ); -my $local_config = YAML::LoadFile( FixMyStreet->path_to("conf/general.yml") ); +my $CONF_FILE = $ENV{FMS_OVERRIDE_CONFIG} || 'general'; +my $local_config = YAML::LoadFile( FixMyStreet->path_to("conf/${CONF_FILE}.yml") ); # find all keys missing from each config my @missing_from_example = find_missing( $example_config, $local_config ); diff --git a/t/00-check-we-are-staging.t b/t/00-check-we-are-staging.t index a1da68798..072b5df76 100644 --- a/t/00-check-we-are-staging.t +++ b/t/00-check-we-are-staging.t @@ -5,20 +5,12 @@ use Test::More; use FixMyStreet; -# check that all the fields listed in general-example are also present in -# general - helps prevent later test failures due to un-noticed additions to the -# config file. - -# This code will bail_out to prevent the test suite proceeding to save time if -# issues are found. - -# load the config file and store the contents in a readonly hash - -mySociety::Config::set_file( FixMyStreet->path_to("conf/general") ); +# check that we are running on staging BAIL_OUT( "Test suite modifies databases so should not be run on live servers" ) - unless mySociety::Config::get('STAGING_SITE', undef); + unless FixMyStreet->config('STAGING_SITE'); -ok mySociety::Config::get('STAGING_SITE', undef), 'staging server'; +my $staging = FixMyStreet->config('STAGING_SITE'); +ok $staging, 'staging server'; done_testing(); diff --git a/t/Mock/Facebook.pm b/t/Mock/Facebook.pm new file mode 100644 index 000000000..339eae536 --- /dev/null +++ b/t/Mock/Facebook.pm @@ -0,0 +1,52 @@ +package t::Mock::Facebook; + +use JSON::MaybeXS; +use Web::Simple; +use MooX::Types::MooseLike::Base qw(:all); + +has json => ( + is => 'lazy', + default => sub { + JSON->new->pretty->allow_blessed->convert_blessed; + }, +); + +has returns_email => ( + is => 'rw', + isa => Bool, + default => 1, +); + +sub dispatch_request { + my $self = shift; + + sub (GET + /v2.8/dialog/oauth + ?*) { + my ($self) = @_; + return [ 200, [ 'Content-Type' => 'text/html' ], [ 'FB login page' ] ]; + }, + + sub (GET + /v2.8/oauth/access_token + ?*) { + my ($self) = @_; + return [ 200, [ 'Content-Type' => 'application/json' ], [ '{"access_token": "access_token"}' ] ]; + }, + + sub (GET + /me + ?fields=) { + my ($self, $fields) = @_; + my $data = { + id => '123456789', + name => 'Fiona Tester', + }; + $data->{email} = 'facebook@example.org' if $self->returns_email; + my $json = $self->json->encode($data); + return [ 200, [ 'Content-Type' => 'text/html' ], [ $json ] ]; + }, + + sub (GET + /search + ?q=) { + my ($self, $q) = @_; + my $response = $self->query($q); + my $json = $self->json->encode($response); + return [ 200, [ 'Content-Type' => 'application/json' ], [ $json ] ]; + }, +} + +__PACKAGE__->run_if_script; diff --git a/t/Mock/MapIt.pm b/t/Mock/MapIt.pm new file mode 100644 index 000000000..43d44d519 --- /dev/null +++ b/t/Mock/MapIt.pm @@ -0,0 +1,112 @@ +package t::Mock::MapIt; + +use JSON::MaybeXS; +use Web::Simple; +use LWP::Protocol::PSGI; + +use mySociety::Locale; + +has json => ( + is => 'lazy', + default => sub { + JSON->new->pretty->allow_blessed->convert_blessed; + }, +); + +sub output { + my ($self, $response) = @_; + # We must make sure we output correctly for testing purposes, we might + # be within a different locale here... + my $json = mySociety::Locale::in_gb_locale { + $self->json->encode($response) }; + return [ 200, [ 'Content-Type' => 'application/json' ], [ $json ] ]; +} + +my @PLACES = ( + [ 'EH1 1BB', 55.952055, -3.189579, 2651, 'Edinburgh City Council', 'UTA', 20728, 'City Centre', 'UTE' ], + [ 'SW1A 1AA', 51.501009, -0.141588, 2504, 'Westminster City Council', 'LBO' ], + [ 'GL50 2PR', 51.896268, -2.093063, 2226, 'Gloucestershire County Council', 'CTY', 2326, 'Cheltenham Borough Council', 'DIS', 4544, 'Lansdown', 'DIW', 143641, 'Lansdown and Park', 'CED' ], + [ '?', 51.754926, -1.256179, 2237, 'Oxfordshire County Council', 'CTY', 2421, 'Oxford City Council', 'DIS' ], + [ 'BR1 3UH', 51.4021, 0.01578, 2482, 'Bromley Council', 'LBO' ], + [ '?', 50.78301, -0.646929 ], + [ 'GU51 4AE', 51.279456, -0.846216, 2333, 'Hart District Council', 'DIS', 2227, 'Hampshire County Council', 'CTY' ], + [ 'WS1 4NH', 52.563074, -1.991032, 2535, 'Sandwell Borough Council', 'MTD' ], +); + +sub dispatch_request { + my $self = shift; + + sub (GET + /postcode/*) { + my ($self, $postcode) = @_; + foreach (@PLACES) { + if ($postcode eq $_->[0] || $postcode eq $_->[0] =~ s/ //gr) { + return $self->output({wgs84_lat => $_->[1], wgs84_lon => $_->[2], postcode => $postcode, coordsyst => 'G'}); + } + } + my $response = { + wgs84_lat => 51.5, wgs84_lon => -2.1, postcode => $postcode, coordsyst => 'G', + }; + return $self->output($response); + }, + + sub (GET + /point/**.*) { + my ($self, $point) = @_; + foreach (@PLACES) { + if ($point eq "4326/$_->[2],$_->[1]") { + my %out; + for (my $i=3; $i<@$_; $i+=3) { + $out{"$_->[$i]"} = { id => $_->[$i], name => $_->[$i+1], type => $_->[$i+2] }; + } + return $self->output(\%out); + } + } + my $response = { + "63999" => {"parent_area" => 2245, "generation_high" => 25, "all_names" => {}, "id" => 63999, "codes" => {"ons" => "00HYNS", "gss" => "E05008366", "unit_id" => "44025"}, "name" => "Kington", "country" => "E", "type_name" => "Unitary Authority electoral division (UTE)", "generation_low" => 12, "country_name" => "England", "type" => "UTE"}, + "2245" => {"parent_area" => undef, "generation_high" => 25, "all_names" => {}, "id" => 2245, "codes" => {"ons" => "00HY", "gss" => "E06000054", "unit_id" => "43925"}, "name" => "Wiltshire Council", "country" => "E", "type_name" => "Unitary Authority", "generation_low" => 11, "country_name" => "England", "type" => "UTA"} + }; + return $self->output($response); + }, + + sub (GET + /areas/*) { + my ($self, $areas) = @_; + if ($areas eq 'Hart') { + $self->output({2333 => {parent_area => undef, id => 2333, name => "Hart District Council", type => "DIS"}}); + } elsif ($areas eq 'Birmingham') { + $self->output({2514 => {parent_area => undef, id => 2514, name => "Birmingham City Council", type => "MTD"}}); + } elsif ($areas eq 'Gloucestershire') { + $self->output({2226 => {parent_area => undef, id => 2226, name => "Gloucestershire County Council", type => "CTY"}}); + } elsif ($areas eq 'Cheltenham') { + $self->output({2326 => {parent_area => undef, id => 2326, name => "Cheltenham Borough Council", type => "DIS"}}); + } elsif ($areas eq 'Lansdown and Park') { + $self->output({22261 => {parent_area => 2226, id => 22261, name => "Lansdown and Park", type => "CED"}}); + } elsif ($areas eq 'Lansdown') { + $self->output({23261 => {parent_area => 2326, id => 23261, name => "Lansdown", type => "DIW"}}); + } elsif ($areas eq 'UTA') { + $self->output({2650 => {parent_area => undef, id => 2650, name => "Aberdeen Council", type => "UTA"}}); + } + }, + + sub (GET + /area/*) { + my ($self, $area) = @_; + my $response = { "id" => $area, "name" => "Area $area", "type" => "UTA" }; + return $self->output($response); + }, + + sub (GET + /area/*/children) { + my ($self, $area) = @_; + my $response = { + "60705" => { "parent_area" => 2245, "generation_high" => 25, "all_names" => { }, "id" => 60705, "codes" => { "ons" => "00HY226", "gss" => "E04011842", "unit_id" => "17101" }, "name" => "Trowbridge", "country" => "E", "type_name" => "Civil parish/community", "generation_low" => 12, "country_name" => "England", "type" => "CPC" }, + "62883" => { "parent_area" => 2245, "generation_high" => 25, "all_names" => { }, "id" => 62883, "codes" => { "ons" => "00HY026", "gss" => "E04011642", "unit_id" => "17205" }, "name" => "Bradford-on-Avon", "country" => "E", "type_name" => "Civil parish/community", "generation_low" => 12, "country_name" => "England", "type" => "CPC" }, + }; + return $self->output($response); + }, + + sub (GET + /area/*/example_postcode) { + my ($self, $area) = @_; + return [ 200, [ 'Content-Type' => 'application/json' ], [ '"AB12 1AA"' ] ]; + }, +} + +LWP::Protocol::PSGI->register(t::Mock::MapIt->to_psgi_app, host => 'mapit.uk'); + +__PACKAGE__->run_if_script; diff --git a/t/Mock/MapItZurich.pm b/t/Mock/MapItZurich.pm new file mode 100644 index 000000000..9195749f6 --- /dev/null +++ b/t/Mock/MapItZurich.pm @@ -0,0 +1,49 @@ +package t::Mock::MapItZurich; + +use JSON::MaybeXS; +use Web::Simple; + +use mySociety::Locale; + +has json => ( + is => 'lazy', + default => sub { + JSON->new->pretty->allow_blessed->convert_blessed; + }, +); + +sub dispatch_request { + my $self = shift; + + sub (GET + /areas/**) { + my ($self, $areas) = @_; + my $response = { + "423017" => {"parent_area" => undef, "generation_high" => 4, "all_names" => {}, "id" => 423017, "codes" => {}, "name" => "Zurich", "country" => "G", "type_name" => "OpenStreetMap Layer 8", "generation_low" => 4, "country_name" => "Global", "type" => "O08"} + }; + my $json = $self->json->encode($response); + return [ 200, [ 'Content-Type' => 'application/json' ], [ $json ] ]; + }, + + sub (GET + /point/**) { + my ($self, $point) = @_; + my $response = { + "423017" => {"parent_area" => undef, "generation_high" => 4, "all_names" => {}, "id" => 423017, "codes" => {}, "name" => "Zurich", "country" => "G", "type_name" => "OpenStreetMap Layer 8", "generation_low" => 4, "country_name" => "Global", "type" => "O08"} + }; + my $json = $self->json->encode($response); + return [ 200, [ 'Content-Type' => 'application/json' ], [ $json ] ]; + }, + + sub (GET + /area/*/example_postcode) { + my ($self, $area) = @_; + my $json = $self->json->encode({}); + return [ 200, [ 'Content-Type' => 'application/json' ], [ $json ] ]; + }, + + sub (GET + /area/*/children) { + my ($self, $area) = @_; + my $json = $self->json->encode({}); + return [ 200, [ 'Content-Type' => 'application/json' ], [ $json ] ]; + }, +} + +__PACKAGE__->run_if_script; diff --git a/t/Mock/Nominatim.pm b/t/Mock/Nominatim.pm new file mode 100644 index 000000000..5c8c549d1 --- /dev/null +++ b/t/Mock/Nominatim.pm @@ -0,0 +1,37 @@ +package t::Mock::Nominatim; + +use JSON::MaybeXS; +use Web::Simple; + +has json => ( + is => 'lazy', + default => sub { + JSON->new->pretty->allow_blessed->convert_blessed; + }, +); + +sub dispatch_request { + my $self = shift; + + sub (GET + /search + ?q=) { + my ($self, $q) = @_; + my $response = $self->query($q); + my $json = $self->json->encode($response); + return [ 200, [ 'Content-Type' => 'application/json' ], [ $json ] ]; + }, +} + +sub query { + my ($self, $q) = @_; + if ($q eq 'high street') { + return [ + {"osm_type"=>"way","osm_id"=>"4684282","lat"=>"55.9504009","lon"=>"-3.1858425","display_name"=>"High Street, Old Town, City of Edinburgh, Scotland, EH1 1SP, United Kingdom","class"=>"highway","type"=>"tertiary","importance"=>0.55892577838734}, + {"osm_type"=>"node","osm_id"=>"27424410","lat"=>"55.8596449","lon"=>"-4.240377","display_name"=>"High Street, Collegelands, Merchant City, Glasgow, Glasgow City, Scotland, G, United Kingdom","class"=>"railway","type"=>"station","importance"=>0.53074299592768} + ]; + } + return []; +} + + + +__PACKAGE__->run_if_script; diff --git a/t/Mock/Static.pm b/t/Mock/Static.pm new file mode 100644 index 000000000..260c181cd --- /dev/null +++ b/t/Mock/Static.pm @@ -0,0 +1,18 @@ +package t::Mock::Static; + +use Path::Tiny; +use Web::Simple; + +my $sample_file = path(__FILE__)->parent->parent->child("app/controller/sample.jpg"); +my $sample_photo = $sample_file->slurp_raw; + +sub dispatch_request { + my $self = shift; + + sub (GET + /image.jpeg) { + my ($self) = @_; + return [ 200, [ 'Content-Type' => 'image/jpeg' ], [ $sample_photo ] ]; + }, +} + +__PACKAGE__->run_if_script; diff --git a/t/Mock/Twitter.pm b/t/Mock/Twitter.pm new file mode 100644 index 000000000..930895e28 --- /dev/null +++ b/t/Mock/Twitter.pm @@ -0,0 +1,43 @@ +package t::Mock::Twitter; + +use JSON::MaybeXS; +use Web::Simple; +use MooX::Types::MooseLike::Base qw(:all); + +has json => ( + is => 'lazy', + default => sub { + JSON->new->pretty->allow_blessed->convert_blessed; + }, +); + +sub dispatch_request { + my $self = shift; + + sub (GET + /oauth/authenticate + ?*) { + my ($self) = @_; + return [ 200, [ 'Content-Type' => 'text/html' ], [ 'TwitteB login page' ] ]; + }, + + sub (GET + /oauth/access_token + ?*) { + my ($self) = @_; + return [ 200, [ 'Content-Type' => 'text/plain' ], [ 'oauth_token=access_token&oauth_token_secret=secret' ] ]; + }, + + sub (GET + /oauth/request_token + ?*) { + my ($self) = @_; + return [ 200, [ 'Content-Type' => 'text/plain' ], [ 'oauth_token=request-token&oauth_token_secret=secret&oauth_callback_confirmed=true' ] ]; + }, + + sub (GET + /1.1/account/verify_credentials.json + ?*) { + my ($self) = @_; + my $data = { + id => '987654321', + name => 'Fiona Tester', + }; + my $json = $self->json->encode($data); + return [ 200, [ 'Content-Type' => 'text/html' ], [ $json ] ]; + }, +} + +__PACKAGE__->run_if_script; diff --git a/t/app/01app.t b/t/app/01app.t index 02ffcd217..df562b829 100644 --- a/t/app/01app.t +++ b/t/app/01app.t @@ -1,10 +1,31 @@ #!/usr/bin/env perl + use strict; use warnings; -use Test::More; +package FixMyStreet::Cobrand::Tester; +use parent 'FixMyStreet::Cobrand::FiksGataMi'; +sub front_stats_data { { new => 0, fixed => 0, updates => 12345 } } + +package main; + +use Test::More; use Catalyst::Test 'FixMyStreet::App'; +use charnames ':full'; +use Encode qw(encode); ok( request('/')->is_success, 'Request should succeed' ); +SKIP: { +FixMyStreet::override_config { + ALLOWED_COBRANDS => [ 'tester' ], +}, sub { + skip 'Test will not pass on Mac OS', 1 if $^O eq 'darwin'; + + my $page = get('/'); + my $num = encode('UTF-8', "12\N{NO-BREAK SPACE}345"); + like $page, qr/$num/; +}; +} + done_testing(); diff --git a/t/app/controller/about.t b/t/app/controller/about.t index 4e49cdac9..cec50abfa 100644 --- a/t/app/controller/about.t +++ b/t/app/controller/about.t @@ -1,3 +1,4 @@ +use utf8; use strict; use warnings; @@ -7,25 +8,26 @@ use Test::WWW::Mechanize::Catalyst 'FixMyStreet::App'; ok( my $mech = Test::WWW::Mechanize::Catalyst->new, 'Created mech object' ); # check that we can get the page -$mech->get_ok('/about'); -$mech->content_like(qr{About us ::\s+FixMyStreet}); +$mech->get_ok('/faq'); +$mech->content_like(qr{Frequently Asked Questions ::\s+FixMyStreet}); $mech->content_contains('html class="no-js" lang="en-gb"'); -SKIP: { - skip( "Need 'emptyhomes' in ALLOWED_COBRANDS config", 8 ) - unless FixMyStreet::Cobrand->exists('emptyhomes'); +$mech->get_ok('/privacy'); +is $mech->res->code, 200, "got 200 for final destination"; +is $mech->res->previous->code, 302, "got 302 for redirect"; +is $mech->uri->path, '/about/privacy'; - # check that geting the page as EHA produces a different page - ok $mech->host("reportemptyhomes.co.uk"), 'change host to reportemptyhomes'; - $mech->get_ok('/about'); - $mech->content_like(qr{About us ::\s+Report Empty Homes}); - $mech->content_contains('html lang="en-gb"'); +$mech->get('/about/page-that-does-not-exist'); +ok !$mech->res->is_success(), "want a bad response"; +is $mech->res->code, 404, "got 404"; - # check that geting the page as EHA in welsh produces a different page - ok $mech->host("cy.reportemptyhomes.co.uk"), 'host to cy.reportemptyhomes'; - $mech->get_ok('/about'); - $mech->content_like(qr{Amdanom ni ::\s+Adrodd am Eiddo Gwag}); - $mech->content_contains('html lang="cy"'); -} +FixMyStreet::override_config { + ALLOWED_COBRANDS => [ 'fiksgatami' ], +}, sub { + ok $mech->host("www.fiksgatami.no"), 'host to fiksgatami'; + $mech->get_ok('/faq'); + $mech->content_like(qr{Ofte spurte spørsmål ::}); + $mech->content_contains('html class="no-js" lang="nb"'); +}; done_testing(); diff --git a/t/app/controller/admin.t b/t/app/controller/admin.t index 71a391c59..5f8abe5a6 100644 --- a/t/app/controller/admin.t +++ b/t/app/controller/admin.t @@ -6,28 +6,23 @@ use FixMyStreet::TestMech; my $mech = FixMyStreet::TestMech->new; -my $secret = FixMyStreet::App->model('DB::Secret')->search(); +my $user = $mech->create_user_ok('test@example.com', name => 'Test User'); -# don't explode if there's nothing in the secret table -if ( $secret == 0 ) { - diag "You need to put an entry in the secret table for the admin tests to run"; - plan skip_all => 'No entry in secret table'; -} +my $user2 = $mech->create_user_ok('test2@example.com', name => 'Test User 2'); + +my $superuser = $mech->create_user_ok('superuser@example.com', name => 'Super User', is_superuser => 1); -my $user = - FixMyStreet::App->model('DB::User') - ->find_or_create( { email => 'test@example.com', name => 'Test User' } ); -ok $user, "created test user"; +my $oxfordshire = $mech->create_body_ok(2237, 'Oxfordshire County Council', id => 2237); +my $oxfordshirecontact = $mech->create_contact_ok( body_id => $oxfordshire->id, category => 'Potholes', email => 'potholes@example.com' ); +$mech->create_contact_ok( body_id => $oxfordshire->id, category => 'Traffic lights', email => 'lights@example.com' ); +my $oxfordshireuser = $mech->create_user_ok('counciluser@example.com', name => 'Council User', from_body => $oxfordshire); -my $user2 = - FixMyStreet::App->model('DB::User') - ->find_or_create( { email => 'test2@example.com', name => 'Test User 2' } ); -ok $user2, "created second test user"; +my $oxford = $mech->create_body_ok(2421, 'Oxford City Council'); +$mech->create_contact_ok( body_id => $oxford->id, category => 'Graffiti', email => 'graffiti@example.net' ); +my $bromley = $mech->create_body_ok(2482, 'Bromley Council', id => 2482); -my $user3 = - FixMyStreet::App->model('DB::User') - ->find( { email => 'test3@example.com', name => 'Test User 2' } ); +my $user3 = $mech->create_user_ok('test3@example.com', name => 'Test User 2'); if ( $user3 ) { $mech->delete_user( $user3 ); @@ -45,7 +40,7 @@ my $dt = DateTime->new( my $report = FixMyStreet::App->model('DB::Problem')->find_or_create( { postcode => 'SW1A 1AA', - council => '2504', + bodies_str => '2504', areas => ',105255,11806,11828,2247,2504,', category => 'Other', title => 'Report to Edit', @@ -53,6 +48,7 @@ my $report = FixMyStreet::App->model('DB::Problem')->find_or_create( used_map => 't', name => 'Test User', anonymous => 'f', + external_id => '13', state => 'confirmed', confirmed => $dt->ymd . ' ' . $dt->hms, lang => 'en-gb', @@ -69,13 +65,15 @@ my $report = FixMyStreet::App->model('DB::Problem')->find_or_create( my $alert = FixMyStreet::App->model('DB::Alert')->find_or_create( { - alert_type => 'new_updates', - parameter => $report->id, + alert_type => 'area_problems', + parameter => 2482, confirmed => 1, user => $user, }, ); +$mech->log_in_ok( $superuser->email ); + subtest 'check summary counts' => sub { my $problems = FixMyStreet::App->model('DB::Problem')->search( { state => { -in => [qw/confirmed fixed closed investigating planned/, 'in progress', 'fixed - user', 'fixed - council'] } } ); @@ -84,16 +82,20 @@ subtest 'check summary counts' => sub { my $problem_count = $problems->count; $problems->update( { cobrand => '' } ); - FixMyStreet::App->model('DB::Problem')->search( { council => 2489 } )->update( { council => 1 } ); + FixMyStreet::App->model('DB::Problem')->search( { bodies_str => 2489 } )->update( { bodies_str => 1 } ); my $q = FixMyStreet::App->model('DB::Questionnaire')->find_or_new( { problem => $report, }); - $q->whensent( \'ms_current_timestamp()' ); + $q->whensent( \'current_timestamp' ); $q->in_storage ? $q->update : $q->insert; my $alerts = FixMyStreet::App->model('DB::Alert')->search( { confirmed => { '>' => 0 } } ); my $a_count = $alerts->count; - $mech->get_ok('/admin'); + FixMyStreet::override_config { + ALLOWED_COBRANDS => [ 'fixmystreet' ], + }, sub { + $mech->get_ok('/admin'); + }; $mech->title_like(qr/Summary/); @@ -105,11 +107,10 @@ subtest 'check summary counts' => sub { $mech->content_contains( "$q_count questionnaires sent" ); - SKIP: { - skip( "Need 'barnet' in ALLOWED_COBRANDS config", 7 ) - unless FixMyStreet::Cobrand->exists('barnet'); - - ok $mech->host('barnet.fixmystreet.com'); + FixMyStreet::override_config { + ALLOWED_COBRANDS => [ 'oxfordshire' ], + }, sub { + ok $mech->host('oxfordshire.fixmystreet.com'); $mech->get_ok('/admin'); $mech->title_like(qr/Summary/); @@ -118,11 +119,11 @@ subtest 'check summary counts' => sub { my ($num_alerts) = $mech->content =~ /(\d+) confirmed alerts/; my ($num_qs) = $mech->content =~ /(\d+) questionnaires sent/; - $report->council(2489); - $report->cobrand('barnet'); + $report->bodies_str(2237); + $report->cobrand('oxfordshire'); $report->update; - $alert->cobrand('barnet'); + $alert->cobrand('oxfordshire'); $alert->update; $mech->get_ok('/admin'); @@ -131,48 +132,56 @@ subtest 'check summary counts' => sub { $mech->content_contains( ($num_alerts+1) . " confirmed alerts" ); $mech->content_contains( ($num_qs+1) . " questionnaires sent" ); - $report->council(2504); + $report->bodies_str(2504); $report->cobrand(''); $report->update; $alert->cobrand(''); $alert->update; - } + }; - FixMyStreet::App->model('DB::Problem')->search( { council => 1 } )->update( { council => 2489 } ); - ok $mech->host('fixmystreet.com'); + FixMyStreet::App->model('DB::Problem')->search( { bodies_str => 1 } )->update( { bodies_str => 2489 } ); + ok $mech->host('www.fixmystreet.com'); }; -my $host = FixMyStreet->config('BASE_URL'); -$mech->get_ok('/admin/council_contacts/2650'); +# This override is wrapped around ALL the /admin/body tests +FixMyStreet::override_config { + MAPIT_URL => 'http://mapit.uk/', + MAPIT_TYPES => [ 'UTA' ], + BASE_URL => 'http://www.example.org', +}, sub { + +my $body = $mech->create_body_ok(2650, 'Aberdeen City Council'); +$mech->get_ok('/admin/body/' . $body->id); $mech->content_contains('Aberdeen City Council'); $mech->content_like(qr{AB\d\d}); -$mech->content_contains("$host/around"); +$mech->content_contains("http://www.example.org/around"); subtest 'check contact creation' => sub { my $contact = FixMyStreet::App->model('DB::Contact')->search( - { body_id => 2650, category => [ 'test category', 'test/category' ] } + { body_id => $body->id, category => [ 'test category', 'test/category' ] } ); $contact->delete_all; my $history = FixMyStreet::App->model('DB::ContactsHistory')->search( - { body_id => 2650, category => [ 'test category', 'test/category' ] } + { body_id => $body->id, category => [ 'test category', 'test/category' ] } ); $history->delete_all; - $mech->get_ok('/admin/council_contacts/2650'); + $mech->get_ok('/admin/body/' . $body->id); $mech->submit_form_ok( { with_fields => { category => 'test category', email => 'test@example.com', note => 'test note', non_public => undef, + confirmed => 0, } } ); $mech->content_contains( 'test category' ); - $mech->content_contains( '<td>test@example.com' ); + $mech->content_contains( 'test@example.com' ); $mech->content_contains( '<td>test note' ); - $mech->content_contains( '<td>Public' ); + $mech->content_contains( 'Private: No' ); $mech->submit_form_ok( { with_fields => { category => 'private category', @@ -182,7 +191,7 @@ subtest 'check contact creation' => sub { } } ); $mech->content_contains( 'private category' ); - $mech->content_contains( '<td>Non Public' ); + $mech->content_contains( 'Private: Yes' ); $mech->submit_form_ok( { with_fields => { category => 'test/category', @@ -190,57 +199,67 @@ subtest 'check contact creation' => sub { note => 'test/note', non_public => 'on', } } ); - $mech->get_ok('/admin/council_edit/2650/test/category'); + $mech->get_ok('/admin/body/' . $body->id . '/test/category'); }; subtest 'check contact editing' => sub { - $mech->get_ok('/admin/council_edit/2650/test%20category'); + $mech->get_ok('/admin/body/' . $body->id .'/test%20category'); - $mech->submit_form_ok( { with_fields => { + $mech->submit_form_ok( { with_fields => { email => 'test2@example.com', note => 'test2 note', non_public => undef, } } ); $mech->content_contains( 'test category' ); - $mech->content_contains( '<td>test2@example.com' ); + $mech->content_contains( 'test2@example.com' ); $mech->content_contains( '<td>test2 note' ); - $mech->content_contains( '<td>Public' ); + $mech->content_contains( 'Private: No' ); - $mech->submit_form_ok( { with_fields => { + $mech->get_ok('/admin/body/' . $body->id . '/test%20category'); + $mech->submit_form_ok( { with_fields => { + email => 'test2@example.com, test3@example.com', + note => 'test3 note', + } } ); + + $mech->content_contains( 'test2@example.com,test3@example.com' ); + + $mech->get_ok('/admin/body/' . $body->id . '/test%20category'); + $mech->content_contains( '<td><strong>test2@example.com,test3@example.com' ); + + $mech->get_ok('/admin/body/' . $body->id . '/test%20category'); + $mech->submit_form_ok( { with_fields => { email => 'test2@example.com', note => 'test2 note', non_public => 'on', } } ); - $mech->content_contains( '<td>Non Public' ); + $mech->content_contains( 'Private: Yes' ); - $mech->get_ok('/admin/council_edit/2650/test%20category'); + $mech->get_ok('/admin/body/' . $body->id . '/test%20category'); $mech->content_contains( '<td><strong>test2@example.com' ); }; subtest 'check contact updating' => sub { - $mech->get_ok('/admin/council_edit/2650/test%20category'); + $mech->get_ok('/admin/body/' . $body->id . '/test%20category'); $mech->content_like(qr{test2\@example.com</strong>[^<]*</td>[^<]*<td>No}s); - $mech->get_ok('/admin/council_contacts/2650'); + $mech->get_ok('/admin/body/' . $body->id); $mech->form_number( 1 ); $mech->tick( 'confirmed', 'test category' ); $mech->submit_form_ok({form_number => 1}); - $mech->content_like(qr'test2@example.com</td>[^<]*<td>Yes's); - $mech->get_ok('/admin/council_edit/2650/test%20category'); + $mech->content_like(qr'test2@example.com</td>[^<]*<td>\s*Confirmed: Yes's); + $mech->get_ok('/admin/body/' . $body->id . '/test%20category'); $mech->content_like(qr{test2\@example.com[^<]*</td>[^<]*<td><strong>Yes}s); }; -my $open311 = - FixMyStreet::App->model('DB::Body')->search( { area_id => 2650 } ); -$open311->delete if $open311; +$body->update({ send_method => undef }); subtest 'check open311 configuring' => sub { - $mech->get_ok('/admin/council_contacts/2650/'); + $mech->get_ok('/admin/body/' . $body->id); $mech->content_lacks('Council contacts configured via Open311'); $mech->form_number(3); @@ -256,13 +275,9 @@ subtest 'check open311 configuring' => sub { } ); $mech->content_contains('Council contacts configured via Open311'); - $mech->content_contains('Configuration updated - contacts will be generated automatically later'); - - $open311 = - FixMyStreet::App->model('DB::Body')->search( { area_id => 2650 } ); + $mech->content_contains('Values updated'); - is $open311->count, 1, 'only one configuration'; - my $conf = $open311->first; + my $conf = FixMyStreet::App->model('DB::Body')->find( $body->id ); is $conf->endpoint, 'http://example.com/open311', 'endpoint configured'; is $conf->api_key, 'api key', 'api key configured'; is $conf->jurisdiction, 'mySociety', 'jurisdiction configures'; @@ -280,24 +295,24 @@ subtest 'check open311 configuring' => sub { } ); - $mech->content_contains('Configuration updated'); + $mech->content_contains('Values updated'); - $open311 = - FixMyStreet::App->model('DB::Body')->search( { area_id => 2650 } ); - - is $open311->count, 1, 'only one configuration'; - $conf = $open311->first; + $conf = FixMyStreet::App->model('DB::Body')->find( $body->id ); is $conf->endpoint, 'http://example.org/open311', 'endpoint updated'; is $conf->api_key, 'new api key', 'api key updated'; is $conf->jurisdiction, 'open311', 'jurisdiction configures'; }; subtest 'check text output' => sub { - $mech->get_ok('/admin/council_contacts/2650?text=1'); + $mech->get_ok('/admin/body/' . $body->id . '?text=1'); is $mech->content_type, 'text/plain'; $mech->content_contains('test category'); }; + +}; # END of override wrap + + my $log_entries = FixMyStreet::App->model('DB::AdminLog')->search( { object_type => 'problem', @@ -327,7 +342,6 @@ foreach my $test ( non_public => undef, }, changes => { title => 'Edited Report', }, - log_count => 1, log_entries => [qw/edit/], resend => 0, }, @@ -344,7 +358,6 @@ foreach my $test ( non_public => undef, }, changes => { detail => 'Edited Detail', }, - log_count => 2, log_entries => [qw/edit edit/], resend => 0, }, @@ -361,7 +374,6 @@ foreach my $test ( non_public => undef, }, changes => { name => 'Edited User', }, - log_count => 3, log_entries => [qw/edit edit edit/], resend => 0, user => $user, @@ -381,7 +393,6 @@ foreach my $test ( changes => { flagged => 'on', }, - log_count => 4, log_entries => [qw/edit edit edit edit/], resend => 0, user => $user, @@ -399,7 +410,6 @@ foreach my $test ( non_public => undef, }, changes => { email => $user2->email, }, - log_count => 5, log_entries => [qw/edit edit edit edit edit/], resend => 0, user => $user2, @@ -417,8 +427,7 @@ foreach my $test ( non_public => undef, }, changes => { state => 'unconfirmed' }, - log_count => 6, - log_entries => [qw/state_change edit edit edit edit edit/], + log_entries => [qw/edit state_change edit edit edit edit edit/], resend => 0, }, { @@ -434,8 +443,7 @@ foreach my $test ( non_public => undef, }, changes => { state => 'confirmed' }, - log_count => 7, - log_entries => [qw/state_change state_change edit edit edit edit edit/], + log_entries => [qw/edit state_change edit state_change edit edit edit edit edit/], resend => 0, }, { @@ -451,9 +459,8 @@ foreach my $test ( non_public => undef, }, changes => { state => 'fixed' }, - log_count => 8, log_entries => - [qw/state_change state_change state_change edit edit edit edit edit/], + [qw/edit state_change edit state_change edit state_change edit edit edit edit edit/], resend => 0, }, { @@ -469,9 +476,8 @@ foreach my $test ( non_public => undef, }, changes => { state => 'hidden' }, - log_count => 9, log_entries => [ - qw/state_change state_change state_change state_change edit edit edit edit edit/ + qw/edit state_change edit state_change edit state_change edit state_change edit edit edit edit edit/ ], resend => 0, }, @@ -491,9 +497,8 @@ foreach my $test ( state => 'confirmed', anonymous => 1, }, - log_count => 11, log_entries => [ - qw/edit state_change state_change state_change state_change state_change edit edit edit edit edit/ + qw/edit state_change edit state_change edit state_change edit state_change edit state_change edit edit edit edit edit/ ], resend => 0, }, @@ -510,9 +515,8 @@ foreach my $test ( non_public => undef, }, changes => {}, - log_count => 12, log_entries => [ - qw/resend edit state_change state_change state_change state_change state_change edit edit edit edit edit/ + qw/resend edit state_change edit state_change edit state_change edit state_change edit state_change edit edit edit edit edit/ ], resend => 1, }, @@ -531,9 +535,8 @@ foreach my $test ( changes => { non_public => 'on', }, - log_count => 13, log_entries => [ - qw/edit resend edit state_change state_change state_change state_change state_change edit edit edit edit edit/ + qw/edit resend edit state_change edit state_change edit state_change edit state_change edit state_change edit edit edit edit edit/ ], resend => 0, }, @@ -543,6 +546,7 @@ foreach my $test ( $log_entries->reset; $mech->get_ok("/admin/report_edit/$report_id"); + @{$test->{fields}}{'external_id', 'external_body', 'external_team', 'category'} = (13, "", "", "Other"); is_deeply( $mech->visible_form_values(), $test->{fields}, 'initial form values' ); my $new_fields = { @@ -557,13 +561,13 @@ foreach my $test ( } is_deeply( $mech->visible_form_values(), $new_fields, 'changed form values' ); - is $log_entries->count, $test->{log_count}, 'log entry count'; + is $log_entries->count, scalar @{$test->{log_entries}}, 'log entry count'; is $log_entries->next->action, $_, 'log entry added' for @{ $test->{log_entries} }; $report->discard_changes; - if ( $report->state eq 'confirmed' ) { - $mech->content_contains( 'type="submit" name="resend"', 'no resend button' ); + if ($report->state eq 'confirmed' && $report->whensent) { + $mech->content_contains( 'type="submit" name="resend"', 'resend button' ); } else { $mech->content_lacks( 'type="submit" name="resend"', 'no resend button' ); } @@ -584,6 +588,33 @@ foreach my $test ( }; } +FixMyStreet::override_config { + ALLOWED_COBRANDS => 'fixmystreet', +}, sub { + +subtest 'change report category' => sub { + my ($ox_report) = $mech->create_problems_for_body(1, $oxfordshire->id, 'Unsure', { + category => 'Potholes', + areas => ',2237,2421,', # Cached used by categories_for_point... + latitude => 51.7549262252, + longitude => -1.25617899435, + whensent => \'current_timestamp', + }); + $mech->get_ok("/admin/report_edit/" . $ox_report->id); + + $mech->submit_form_ok( { with_fields => { category => 'Traffic lights' } }, 'form_submitted' ); + $ox_report->discard_changes; + is $ox_report->category, 'Traffic lights'; + isnt $ox_report->whensent, undef; + + $mech->submit_form_ok( { with_fields => { category => 'Graffiti' } }, 'form_submitted' ); + $ox_report->discard_changes; + is $ox_report->category, 'Graffiti'; + is $ox_report->whensent, undef; +}; + +}; + subtest 'change email to new user' => sub { $log_entries->delete; $mech->get_ok("/admin/report_edit/$report_id"); @@ -593,9 +624,13 @@ subtest 'change email to new user' => sub { state => $report->state, name => $report->name, email => $report->user->email, + category => 'Other', anonymous => 1, flagged => 'on', non_public => 'on', + external_id => '13', + external_body => '', + external_team => '', }; is_deeply( $mech->visible_form_values(), $fields, 'initial form values' ); @@ -845,6 +880,10 @@ for my $test ( }; } +my $westminster = $mech->create_body_ok(2504, 'Westminster City Council'); +$report->bodies_str($westminster->id); +$report->update; + for my $test ( { desc => 'user is problem owner', @@ -853,18 +892,18 @@ for my $test ( update_fixed => 0, update_reopen => 0, update_state => undef, - user_council => undef, + user_body => undef, content => 'user is problem owner', }, { - desc => 'user is council user', + desc => 'user is body user', problem_user => $user, update_user => $user2, update_fixed => 0, update_reopen => 0, update_state => undef, - user_council => 2504, - content => 'user is from same council as problem - 2504', + user_body => $westminster->id, + content => 'user is from same council as problem - ' . $westminster->id, }, { desc => 'update changed problem state', @@ -873,7 +912,7 @@ for my $test ( update_fixed => 0, update_reopen => 0, update_state => 'planned', - user_council => 2504, + user_body => $westminster->id, content => 'Update changed problem state to planned', }, { @@ -883,7 +922,7 @@ for my $test ( update_fixed => 1, update_reopen => 0, update_state => undef, - user_council => undef, + user_body => undef, content => 'Update marked problem as fixed', }, { @@ -893,7 +932,7 @@ for my $test ( update_fixed => 0, update_reopen => 1, update_state => undef, - user_council => undef, + user_body => undef, content => 'Update reopened problem', }, ) { @@ -907,7 +946,7 @@ for my $test ( $update->mark_open( $test->{update_reopen} ); $update->update; - $test->{update_user}->from_body( $test->{user_council} ); + $test->{update_user}->from_body( $test->{user_body} ); $test->{update_user}->update; $mech->get_ok('/admin/update_edit/' . $update->id ); @@ -1033,41 +1072,47 @@ subtest 'report search' => sub { $update->user($report->user); $update->update; - $mech->get_ok('/admin/search_reports'); - $mech->get_ok('/admin/search_reports?search=' . $report->id ); + $mech->get_ok('/admin/reports'); + $mech->get_ok('/admin/reports?search=' . $report->id ); $mech->content_contains( $report->title ); my $r_id = $report->id; - $mech->content_like( qr{href="http://[^/]*[^.]/report/$r_id/">$r_id</a>} ); + $mech->content_like( qr{href="http://[^/]*[^.]/report/$r_id"[^>]*>$r_id</a>} ); - $mech->get_ok('/admin/search_reports?search=' . $report->user->email); + $mech->get_ok('/admin/reports?search=' . $report->external_id); + $mech->content_like( qr{href="http://[^/]*[^.]/report/$r_id"[^>]*>$r_id</a>} ); + + $mech->get_ok('/admin/reports?search=ref:' . $report->external_id); + $mech->content_like( qr{href="http://[^/]*[^.]/report/$r_id"[^>]*>$r_id</a>} ); + + $mech->get_ok('/admin/reports?search=' . $report->user->email); my $u_id = $update->id; - $mech->content_like( qr{href="http://[^/]*[^.]/report/$r_id/">$r_id</a>} ); - $mech->content_like( qr{href="http://[^/]*[^.]/report/$r_id/#update_$u_id">$u_id</a>} ); + $mech->content_like( qr{href="http://[^/]*[^.]/report/$r_id"[^>]*>$r_id</a>} ); + $mech->content_like( qr{href="http://[^/]*[^.]/report/$r_id#update_$u_id"[^>]*>$u_id</a>} ); $update->state('hidden'); $update->update; - $mech->get_ok('/admin/search_reports?search=' . $report->user->email); + $mech->get_ok('/admin/reports?search=' . $report->user->email); $mech->content_like( qr{<tr [^>]*hidden[^>]*> \s* <td> \s* $u_id \s* </td>}xs ); $report->state('hidden'); $report->update; - $mech->get_ok('/admin/search_reports?search=' . $report->user->email); - $mech->content_like( qr{<tr [^>]*hidden[^>]*> \s* <td> \s* $r_id \s* </td>}xs ); + $mech->get_ok('/admin/reports?search=' . $report->user->email); + $mech->content_like( qr{<tr [^>]*hidden[^>]*> \s* <td[^>]*> \s* $r_id \s* </td>}xs ); $report->state('fixed - user'); $report->update; - $mech->get_ok('/admin/search_reports?search=' . $report->user->email); - $mech->content_like( qr{href="http://[^/]*[^.]/report/$r_id/">$r_id</a>} ); + $mech->get_ok('/admin/reports?search=' . $report->user->email); + $mech->content_like( qr{href="http://[^/]*[^.]/report/$r_id"[^>]*>$r_id</a>} ); }; subtest 'search abuse' => sub { - $mech->get_ok( '/admin/search_users?search=example' ); - $mech->content_like(qr/test4\@example.com.*\n.*\n.*Email in abuse table/); + $mech->get_ok( '/admin/users?search=example' ); + $mech->content_like(qr{test4\@example.com.*</td>\s*<td>.*?</td>\s*<td>\(Email in abuse table}s); }; subtest 'show flagged entries' => sub { @@ -1077,27 +1122,53 @@ subtest 'show flagged entries' => sub { $user->flagged( 1 ); $user->update; - $mech->get_ok('/admin/list_flagged'); + $mech->get_ok('/admin/flagged'); $mech->content_contains( $report->title ); $mech->content_contains( $user->email ); }; +my $haringey = $mech->create_body_ok(2509, 'Haringey Borough Council'); + subtest 'user search' => sub { - $mech->get_ok('/admin/search_users'); - $mech->get_ok('/admin/search_users?search=' . $user->name); + $mech->get_ok('/admin/users'); + $mech->get_ok('/admin/users?search=' . $user->name); $mech->content_contains( $user->name); my $u_id = $user->id; $mech->content_like( qr{user_edit/$u_id">Edit</a>} ); - $mech->get_ok('/admin/search_users?search=' . $user->email); + $mech->get_ok('/admin/users?search=' . $user->email); $mech->content_like( qr{user_edit/$u_id">Edit</a>} ); - $user->from_body(2509); + $user->from_body($haringey->id); $user->update; - $mech->get_ok('/admin/search_users?search=2509' ); - $mech->content_contains(2509); + $mech->get_ok('/admin/users?search=' . $haringey->id ); + $mech->content_contains('Haringey'); +}; + +subtest 'search does not show user from another council' => sub { + FixMyStreet::override_config { + ALLOWED_COBRANDS => [ 'oxfordshire' ], + }, sub { + $mech->get_ok('/admin/users'); + $mech->get_ok('/admin/users?search=' . $user->name); + + $mech->content_contains( "Searching found no users." ); + + $mech->get_ok('/admin/users?search=' . $user->email); + $mech->content_contains( "Searching found no users." ); + }; +}; + +subtest 'user_edit does not show user from another council' => sub { + FixMyStreet::override_config { + ALLOWED_COBRANDS => [ 'oxfordshire' ], + }, sub { + $mech->get('/admin/user_edit/' . $user->id); + ok !$mech->res->is_success(), "want a bad response"; + is $mech->res->code, 404, "got 404"; + }; }; $log_entries = FixMyStreet::App->model('DB::AdminLog')->search( @@ -1115,101 +1186,375 @@ is $log_entries->count, 0, 'no admin log entries'; $user->flagged( 0 ); $user->update; -for my $test ( - { - desc => 'edit user name', - fields => { - name => 'Test User', - email => 'test@example.com', - council => 2509, - flagged => undef, - }, - changes => { - name => 'Changed User', - }, - log_count => 1, - log_entries => [qw/edit/], - }, - { - desc => 'edit user email', - fields => { - name => 'Changed User', - email => 'test@example.com', - council => 2509, - flagged => undef, - }, - changes => { - email => 'changed@example.com', +my $southend = $mech->create_body_ok(2607, 'Southend-on-Sea Borough Council'); + +my %default_perms = ( + "permissions[moderate]" => undef, + "permissions[planned_reports]" => undef, + "permissions[report_edit]" => undef, + "permissions[report_edit_category]" => undef, + "permissions[report_edit_priority]" => undef, + "permissions[report_inspect]" => undef, + "permissions[report_instruct]" => undef, + "permissions[contribute_as_another_user]" => undef, + "permissions[contribute_as_body]" => undef, + "permissions[view_body_contribute_details]" => undef, + "permissions[user_edit]" => undef, + "permissions[user_manage_permissions]" => undef, + "permissions[user_assign_body]" => undef, + "permissions[user_assign_areas]" => undef, + "permissions[template_edit]" => undef, + "permissions[responsepriority_edit]" => undef, + "permissions[category_edit]" => undef, + trusted_bodies => undef, +); + +FixMyStreet::override_config { + MAPIT_URL => 'http://mapit.uk/', +}, sub { + for my $test ( + { + desc => 'edit user name', + fields => { + name => 'Test User', + email => 'test@example.com', + body => $haringey->id, + phone => '', + flagged => undef, + is_superuser => undef, + area_id => '', + %default_perms, + }, + changes => { + name => 'Changed User', + }, + log_count => 1, + log_entries => [qw/edit/], }, - log_count => 2, - log_entries => [qw/edit edit/], - }, - { - desc => 'edit user council', - fields => { - name => 'Changed User', - email => 'changed@example.com', - council => 2509, - flagged => undef, + { + desc => 'edit user email', + fields => { + name => 'Changed User', + email => 'test@example.com', + body => $haringey->id, + phone => '', + flagged => undef, + is_superuser => undef, + area_id => '', + %default_perms, + }, + changes => { + email => 'changed@example.com', + }, + log_count => 2, + log_entries => [qw/edit edit/], }, - changes => { - council => 2607, + { + desc => 'edit user body', + fields => { + name => 'Changed User', + email => 'changed@example.com', + body => $haringey->id, + phone => '', + flagged => undef, + is_superuser => undef, + area_id => '', + %default_perms, + }, + changes => { + body => $southend->id, + }, + log_count => 3, + log_entries => [qw/edit edit edit/], }, - log_count => 3, - log_entries => [qw/edit edit edit/], - }, - { - desc => 'edit user flagged', - fields => { - name => 'Changed User', - email => 'changed@example.com', - council => 2607, - flagged => undef, + { + desc => 'edit user flagged', + fields => { + name => 'Changed User', + email => 'changed@example.com', + body => $southend->id, + phone => '', + flagged => undef, + is_superuser => undef, + area_id => '', + %default_perms, + }, + changes => { + flagged => 'on', + }, + log_count => 4, + log_entries => [qw/edit edit edit edit/], }, - changes => { - flagged => 'on', + { + desc => 'edit user remove flagged', + fields => { + name => 'Changed User', + email => 'changed@example.com', + body => $southend->id, + phone => '', + flagged => 'on', + is_superuser => undef, + area_id => '', + %default_perms, + }, + changes => { + flagged => undef, + }, + log_count => 4, + log_entries => [qw/edit edit edit edit/], }, - log_count => 4, - log_entries => [qw/edit edit edit edit/], - }, - { - desc => 'edit user remove flagged', - fields => { - name => 'Changed User', - email => 'changed@example.com', - council => 2607, - flagged => 'on', + { + desc => 'edit user add is_superuser', + fields => { + name => 'Changed User', + email => 'changed@example.com', + body => $southend->id, + phone => '', + flagged => undef, + is_superuser => undef, + area_id => '', + %default_perms, + }, + changes => { + is_superuser => 'on', + }, + removed => [ + keys %default_perms, + ], + log_count => 5, + log_entries => [qw/edit edit edit edit edit/], }, - changes => { - flagged => undef, + { + desc => 'edit user remove is_superuser', + fields => { + name => 'Changed User', + email => 'changed@example.com', + body => $southend->id, + phone => '', + flagged => undef, + is_superuser => 'on', + area_id => '', + }, + changes => { + is_superuser => undef, + }, + added => { + %default_perms, + }, + log_count => 5, + log_entries => [qw/edit edit edit edit edit/], }, - log_count => 4, - log_entries => [qw/edit edit edit edit/], - }, -) { - subtest $test->{desc} => sub { - $mech->get_ok( '/admin/user_edit/' . $user->id ); + ) { + subtest $test->{desc} => sub { + $mech->get_ok( '/admin/user_edit/' . $user->id ); - my $visible = $mech->visible_form_values; - is_deeply $visible, $test->{fields}, 'expected user'; + my $visible = $mech->visible_form_values; + is_deeply $visible, $test->{fields}, 'expected user'; - my $expected = { - %{ $test->{fields} }, - %{ $test->{changes} } + my $expected = { + %{ $test->{fields} }, + %{ $test->{changes} } + }; + + $mech->submit_form_ok( { with_fields => $expected } ); + + # Some actions cause visible fields to be added/removed + foreach my $x (@{ $test->{removed} }) { + delete $expected->{$x}; + } + if ( $test->{added} ) { + $expected = { + %$expected, + %{ $test->{added} } + }; + } + + $visible = $mech->visible_form_values; + is_deeply $visible, $expected, 'user updated'; + + $mech->content_contains( 'Updated!' ); }; + } +}; - $mech->submit_form_ok( { with_fields => $expected } ); +subtest "Test setting a report from unconfirmed to something else doesn't cause a front end error" => sub { + $report->update( { confirmed => undef, state => 'unconfirmed', non_public => 0 } ); + $mech->get_ok("/admin/report_edit/$report_id"); + $mech->submit_form_ok( { with_fields => { state => 'investigating' } } ); + $report->discard_changes; + ok( $report->confirmed, 'report has a confirmed timestamp' ); + $mech->get_ok("/report/$report_id"); +}; + +subtest "Check admin_base_url" => sub { + my $rs = FixMyStreet::App->model('DB::Problem'); + my $cobrand = FixMyStreet::Cobrand->get_class_for_moniker($report->cobrand)->new(); + + is ($report->admin_url($cobrand), + (sprintf 'http://www.example.org/admin/report_edit/%d', $report_id), + 'get_admin_url OK'); +}; + +# Finished with the superuser tests +$mech->log_out_ok; + +subtest "Users without from_body can't access admin" => sub { + $user->from_body( undef ); + $user->update; + + $mech->log_in_ok( $user->email ); - $visible = $mech->visible_form_values; - is_deeply $visible, $expected, 'user updated'; + ok $mech->get('/admin'); + is $mech->res->code, 403, "got 403"; - $mech->content_contains( 'Updated!' ); + $mech->log_out_ok; +}; + +subtest "Users with from_body can access their own council's admin" => sub { + FixMyStreet::override_config { + ALLOWED_COBRANDS => [ 'oxfordshire' ], + }, sub { + $mech->log_in_ok( $oxfordshireuser->email ); + + $mech->get_ok('/admin'); + $mech->content_contains( 'FixMyStreet admin:' ); + + $mech->log_out_ok; }; -} +}; + +subtest "Users with from_body can't access another council's admin" => sub { + FixMyStreet::override_config { + ALLOWED_COBRANDS => [ 'bristol' ], + }, sub { + $mech->log_in_ok( $oxfordshireuser->email ); + + ok $mech->get('/admin'); + is $mech->res->code, 403, "got 403"; -$mech->delete_user( $user ); -$mech->delete_user( $user2 ); -$mech->delete_user( $user3 ); -$mech->delete_user( 'test4@example.com' ); + $mech->log_out_ok; + }; +}; + +subtest "Users with from_body can't access fixmystreet.com admin" => sub { + FixMyStreet::override_config { + ALLOWED_COBRANDS => [ 'fixmystreet' ], + }, sub { + $mech->log_in_ok( $oxfordshireuser->email ); + + ok $mech->get('/admin'); + is $mech->res->code, 403, "got 403"; + + $mech->log_out_ok; + }; +}; + +subtest "response templates can be added" => sub { + is $oxfordshire->response_templates->count, 0, "No response templates yet"; + $mech->log_in_ok( $superuser->email ); + $mech->get_ok( "/admin/templates/" . $oxfordshire->id . "/new" ); + + my $fields = { + title => "Report acknowledgement", + text => "Thank you for your report. We will respond shortly.", + auto_response => undef, + "contacts[".$oxfordshirecontact->id."]" => 1, + }; + $mech->submit_form_ok( { with_fields => $fields } ); + + is $oxfordshire->response_templates->count, 1, "Response template was added"; +}; + +subtest "response templates are included on page" => sub { + FixMyStreet::override_config { + ALLOWED_COBRANDS => [ 'oxfordshire' ], + }, sub { + $report->update({ category => $oxfordshirecontact->category, bodies_str => $oxfordshire->id }); + $mech->log_in_ok( $oxfordshireuser->email ); + + $mech->get_ok("/report/" . $report->id); + $mech->content_contains( $oxfordshire->response_templates->first->text ); + + $mech->log_out_ok; + }; +}; + +$mech->log_in_ok( $superuser->email ); -done_testing(); +subtest "response priorities can be added" => sub { + is $oxfordshire->response_priorities->count, 0, "No response priorities yet"; + $mech->get_ok( "/admin/responsepriorities/" . $oxfordshire->id . "/new" ); + + my $fields = { + name => "Cat 1A", + description => "Fixed within 24 hours", + deleted => undef, + "contacts[".$oxfordshirecontact->id."]" => 1, + }; + $mech->submit_form_ok( { with_fields => $fields } ); + + is $oxfordshire->response_priorities->count, 1, "Response template was added to body"; + is $oxfordshirecontact->response_priorities->count, 1, "Response template was added to contact"; +}; + +subtest "response priorities can be listed" => sub { + $mech->get_ok( "/admin/responsepriorities/" . $oxfordshire->id ); + + $mech->content_contains( $oxfordshire->response_priorities->first->name ); + $mech->content_contains( $oxfordshire->response_priorities->first->description ); +}; + +subtest "response priorities are limited by body" => sub { + my $bromleypriority = $bromley->response_priorities->create( { + deleted => 0, + name => "Bromley Cat 0", + } ); + + is $bromley->response_priorities->count, 1, "Response template was added to Bromley"; + is $oxfordshire->response_priorities->count, 1, "Response template wasn't added to Oxfordshire"; + + $mech->get_ok( "/admin/responsepriorities/" . $oxfordshire->id ); + $mech->content_lacks( $bromleypriority->name ); + + $mech->get_ok( "/admin/responsepriorities/" . $bromley->id ); + $mech->content_contains( $bromleypriority->name ); +}; + +$mech->log_out_ok; + +subtest "response priorities can't be viewed across councils" => sub { + FixMyStreet::override_config { + ALLOWED_COBRANDS => [ 'oxfordshire' ], + }, sub { + $oxfordshireuser->user_body_permissions->create({ + body => $oxfordshire, + permission_type => 'responsepriority_edit', + }); + $mech->log_in_ok( $oxfordshireuser->email ); + $mech->get_ok( "/admin/responsepriorities/" . $oxfordshire->id ); + $mech->content_contains( $oxfordshire->response_priorities->first->name ); + + + $mech->get( "/admin/responsepriorities/" . $bromley->id ); + ok !$mech->res->is_success(), "want a bad response"; + is $mech->res->code, 404, "got 404"; + + my $bromley_priority_id = $bromley->response_priorities->first->id; + $mech->get( "/admin/responsepriorities/" . $bromley->id . "/" . $bromley_priority_id ); + ok !$mech->res->is_success(), "want a bad response"; + is $mech->res->code, 404, "got 404"; + }; +}; + +END { + $mech->delete_user( $user ); + $mech->delete_user( $user2 ); + $mech->delete_user( $user3 ); + $mech->delete_user( $superuser ); + $mech->delete_user( 'test4@example.com' ); + $mech->delete_body( $oxfordshire ); + $mech->delete_body( $oxford ); + $mech->delete_body( $bromley ); + $mech->delete_body( $westminster ); + done_testing(); +} diff --git a/t/app/controller/admin_permissions.t b/t/app/controller/admin_permissions.t new file mode 100644 index 000000000..dd256173d --- /dev/null +++ b/t/app/controller/admin_permissions.t @@ -0,0 +1,205 @@ +use strict; +use warnings; +use Test::More; + +use FixMyStreet::TestMech; + +my $mech = FixMyStreet::TestMech->new; + +my $user = $mech->create_user_ok('test@example.com', name => 'Test User'); +my $user2 = $mech->create_user_ok('test2@example.com', name => 'Test User 2'); +my $superuser = $mech->create_user_ok('superuser@example.com', name => 'Super User', is_superuser => 1); + +my $oxfordshire = $mech->create_body_ok(2237, 'Oxfordshire County Council', id => 2237); +my $oxfordshireuser = $mech->create_user_ok('counciluser@example.com', name => 'Council User', from_body => $oxfordshire); + +my $bromley = $mech->create_body_ok(2482, 'Bromley Council', id => 2482); + +END { + $mech->delete_user( $user ); + $mech->delete_user( $user2 ); + $mech->delete_user( $superuser ); + $mech->delete_user( $oxfordshireuser ); +} + +my $dt = DateTime->new( + year => 2011, + month => 04, + day => 16, + hour => 15, + minute => 47, + second => 23 +); + +my ($report) = $mech->create_problems_for_body(1, $oxfordshire->id, 'Test', { + areas => ',2237,', +}); +my $report_id = $report->id; +ok $report, "created test report - $report_id"; + +$mech->log_in_ok( $oxfordshireuser->email ); + +subtest "Users can't edit report without report_edit permission" => sub { + FixMyStreet::override_config { + ALLOWED_COBRANDS => [ 'oxfordshire' ], + }, sub { + $mech->get("/admin/report_edit/$report_id"); + ok !$mech->res->is_success(), "want a bad response"; + is $mech->res->code, 404, "got 404, can't edit report without report_edit permission"; + }; +}; + +subtest "Users can edit report with report_edit permission" => sub { + FixMyStreet::override_config { + ALLOWED_COBRANDS => [ 'oxfordshire' ], + }, sub { + $oxfordshireuser->user_body_permissions->create({ + body => $oxfordshire, + permission_type => 'report_edit', + }); + + $mech->get_ok("/admin/report_edit/$report_id"); + $mech->content_contains( $report->title ); + }; +}; + +subtest "Users can't edit another council's reports with their own council's report_edit permission" => sub { + FixMyStreet::override_config { + ALLOWED_COBRANDS => [ 'oxfordshire' ], + }, sub { + $report->bodies_str($bromley->id); + $report->cobrand('bromley'); + $report->update; + + $mech->get("/admin/report_edit/$report_id"); + ok !$mech->res->is_success(), "want a bad response"; + is $mech->res->code, 404, "got 404, can't edit report with incorrect body in report_edit permission"; + }; +}; + + +FixMyStreet::override_config { + MAPIT_URL => 'http://mapit.uk/', + ALLOWED_COBRANDS => [ 'oxfordshire' ], +}, sub { + my $user2_id = $user2->id; + $report->update({ bodies_str => $oxfordshire->id }); + + foreach my $perm (0, 1) { + if ($perm) { + $oxfordshireuser->user_body_permissions->find_or_create({ + body => $oxfordshire, + permission_type => 'user_edit', + }); + } + foreach my $report_user ($user, $user2) { + $report->update({ user => $report_user }); + foreach my $from_body (undef, $bromley, $oxfordshire) { + $user2->update({ from_body => $from_body }); + my $result = ($from_body || '') eq $oxfordshire || $report->user eq $user2 ? ($perm ? 200 : 404 ) : 404; + my $u = $result == 200 ? 'can' : 'cannot'; + my $b = $from_body ? $from_body->name : 'no body'; + my $p = $perm ? 'with' : 'without'; + my $r = $report->user eq $user2 ? 'with' : 'without'; + subtest "User $u edit user for $b $p permission, $r cobrand relation" => sub { + $mech->get("/admin/user_edit/$user2_id"); + my $success = $mech->res->is_success(); + ok $result == 200 ? $success : !$success, "got correct response"; + is $mech->res->code, $result, "got $result"; + }; + } + } + } + + $oxfordshireuser->user_body_permissions->create({ + body => $oxfordshire, + permission_type => 'user_assign_body', + }); + + subtest "Users can edit users of their own council" => sub { + $mech->get_ok("/admin/user_edit/$user2_id"); + $mech->content_contains( $user2->name ); + + # We shouldn't be able to see the permissions tick boxes + $mech->content_lacks('Moderate report details'); + + $mech->submit_form_ok( { with_fields => { + name => 'Test Updated User 2', + email => $user2->email, + body => $user2->from_body->id, + phone => '', + flagged => undef, + } } ); + $user2->discard_changes; + is $user2->name, 'Test Updated User 2', 'name changed'; + }; + + $oxfordshireuser->user_body_permissions->create({ + body => $oxfordshire, + permission_type => 'user_manage_permissions', + }); + + subtest "Users can edit permissions" => sub { + is $user2->user_body_permissions->count, 0, 'user2 has no permissions'; + + $mech->get_ok("/admin/user_edit/$user2_id"); + $mech->content_contains('Moderate report details'); + + $mech->submit_form_ok( { with_fields => { + name => $user2->name, + email => $user2->email, + body => $user2->from_body->id, + phone => '', + flagged => undef, + "permissions[moderate]" => 'on', + "permissions[report_edit_category]" => undef, + "permissions[report_edit_priority]" => undef, + "permissions[report_inspect]" => undef, + "permissions[report_instruct]" => undef, + "permissions[contribute_as_another_user]" => undef, + "permissions[contribute_as_body]" => undef, + "permissions[user_edit]" => undef, + "permissions[user_manage_permissions]" => undef, + "permissions[user_assign_areas]" => undef, + } } ); + + ok $user2->has_body_permission_to("moderate"), "user2 has been granted moderate permission"; + }; + + $oxfordshireuser->user_body_permissions->create({ + body => $oxfordshire, + permission_type => 'user_assign_areas', + }); + + subtest "Unsetting user from_body removes all permissions and area " => sub { + is $user2->user_body_permissions->count, 1, 'user2 has 1 permission'; + + $mech->get_ok("/admin/user_edit/$user2_id"); + $mech->content_contains('Moderate report details'); + + $mech->submit_form_ok( { with_fields => { + name => $user2->name, + email => $user2->email, + body => undef, + phone => '', + flagged => undef, + "permissions[moderate]" => 'on', # NB tick box is left on deliberately + "permissions[report_edit_category]" => undef, + "permissions[report_edit_priority]" => undef, + "permissions[report_inspect]" => undef, + "permissions[report_instruct]" => undef, + "permissions[contribute_as_another_user]" => undef, + "permissions[contribute_as_body]" => undef, + "permissions[user_edit]" => undef, + "permissions[user_manage_permissions]" => undef, + "permissions[user_assign_areas]" => undef, + } } ); + + is $user2->user_body_permissions->count, 0, 'user2 has had permissions removed'; + is $user2->area_id, undef, 'user2 has had area removed'; + }; +}; + +$mech->log_out_ok; + +done_testing(); diff --git a/t/app/controller/alert.t b/t/app/controller/alert.t index 3d95bef6d..cb5949b8f 100644 --- a/t/app/controller/alert.t +++ b/t/app/controller/alert.t @@ -1,12 +1,12 @@ use strict; use warnings; use Test::More; +use LWP::Protocol::PSGI; +use FixMyStreet::TestMech; +my $mech = FixMyStreet::TestMech->new; -use Catalyst::Test 'FixMyStreet::App'; -use Test::WWW::Mechanize::Catalyst 'FixMyStreet::App'; - -ok( my $mech = Test::WWW::Mechanize::Catalyst->new, 'Created mech object' ); +use t::Mock::Nominatim; # check that we can get the page $mech->get_ok('/alert'); @@ -15,52 +15,65 @@ $mech->content_contains('Local RSS feeds and email alerts'); $mech->content_contains('html class="no-js" lang="en-gb"'); # check that we can get list page -$mech->get_ok('/alert/list'); -$mech->title_like(qr/^Local RSS feeds and email alerts/); -$mech->content_contains('Local RSS feeds and email alerts'); -$mech->content_contains('html class="no-js" lang="en-gb"'); - -$mech->get_ok('/alert/list?pc=EH99 1SP'); -$mech->title_like(qr/^Local RSS feeds and email alerts/); -$mech->content_contains('Here are the types of local problem alerts for ‘EH99 1SP’'); -$mech->content_contains('html class="no-js" lang="en-gb"'); -$mech->content_contains('Problems within 8.5km'); -$mech->content_contains('rss/pc/EH991SP/2'); -$mech->content_contains('rss/pc/EH991SP/5'); -$mech->content_contains('rss/pc/EH991SP/10'); -$mech->content_contains('rss/pc/EH991SP/20'); -$mech->content_contains('Problems within City of Edinburgh'); -$mech->content_contains('Problems within City Centre ward'); -$mech->content_contains('/rss/reports/City+of+Edinburgh'); -$mech->content_contains('/rss/reports/City+of+Edinburgh/City+Centre'); -$mech->content_contains('council:2651:City_of_Edinburgh'); -$mech->content_contains('ward:2651:20728:City_of_Edinburgh:City_Centre'); - -$mech->get_ok('/alert/list?pc=High Street'); -$mech->content_contains('We found more than one match for that location'); - -$mech->get_ok('/alert/list?pc='); -$mech->content_contains('To find out what local alerts we have for you'); - -$mech->get_ok('/alert/list?pc=GL502PR'); -$mech->content_contains('Problems within the boundary of'); - -$mech->get_ok('/alert/subscribe?rss=1&type=local&pc=ky16+8yg&rss=Give+me+an+RSS+feed&rznvy=' ); -$mech->content_contains('Please select the feed you want'); - -$mech->get_ok('/alert/subscribe?rss=1&feed=invalid:1000:A_Locationtype=local&pc=ky16+8yg&rss=Give+me+an+RSS+feed&rznvy='); -$mech->content_contains('Illegal feed selection'); - -$mech->get_ok('/alert/subscribe?rss=1&feed=area:1000:Birmingham'); -is $mech->uri->path, '/rss/reports/Birmingham'; - -$mech->get_ok('/alert/subscribe?rss=1&feed=area:1000:1001:Cheltenham:Lansdown'); -is $mech->uri->path, '/rss/area/Cheltenham/Lansdown'; - -$mech->get_ok('/alert/subscribe?rss=1&feed=council:1000:Gloucestershire'); -is $mech->uri->path, '/rss/reports/Gloucestershire'; - -$mech->get_ok('/alert/subscribe?rss=1&feed=ward:1000:1001:Cheltenham:Lansdown'); -is $mech->uri->path, '/rss/reports/Cheltenham/Lansdown'; +FixMyStreet::override_config { + ALLOWED_COBRANDS => [ { 'fixmystreet' => '.' } ], + MAPIT_URL => 'http://mapit.uk/', + GEOCODER => '', +}, sub { + $mech->get_ok('/alert/list'); + $mech->title_like(qr/^Local RSS feeds and email alerts/); + $mech->content_contains('Local RSS feeds and email alerts'); + $mech->content_contains('html class="no-js" lang="en-gb"'); + + $mech->get_ok('/alert/list?pc=EH1 1BB'); + $mech->title_like(qr/^Local RSS feeds and email alerts/); + $mech->content_contains('Here are the types of local problem alerts for ‘EH1 1BB’'); + $mech->content_contains('html class="no-js" lang="en-gb"'); + $mech->content_contains('Problems within 10.0km'); + $mech->content_contains('rss/pc/EH11BB/2'); + $mech->content_contains('rss/pc/EH11BB/5'); + $mech->content_contains('rss/pc/EH11BB/10'); + $mech->content_contains('rss/pc/EH11BB/20'); + $mech->content_contains('Problems within Edinburgh City'); + $mech->content_contains('Problems within City Centre ward'); + $mech->content_contains('/rss/reports/Edinburgh'); + $mech->content_contains('/rss/reports/Edinburgh/City+Centre'); + $mech->content_contains('council:2651:Edinburgh'); + $mech->content_contains('ward:2651:20728:Edinburgh:City_Centre'); + + subtest "Test Nominatim lookup" => sub { + LWP::Protocol::PSGI->register(t::Mock::Nominatim->run_if_script, host => 'nominatim.openstreetmap.org'); + $mech->get_ok('/alert/list?pc=High Street'); + $mech->content_contains('We found more than one match for that location'); + }; + + $mech->get_ok('/alert/list?pc='); + $mech->content_contains('To find out what local alerts we have for you'); + + $mech->get_ok('/alert/list?pc=GL502PR'); + $mech->content_contains('Problems within the boundary of'); + + $mech->get_ok('/alert/subscribe?rss=1&type=local&pc=ky16+8yg&rss=Give+me+an+RSS+feed&rznvy=' ); + $mech->content_contains('Please select the feed you want'); + + $mech->get_ok('/alert/subscribe?rss=1&feed=invalid:1000:A_Locationtype=local&pc=ky16+8yg&rss=Give+me+an+RSS+feed&rznvy='); + $mech->content_contains('Illegal feed selection'); + + $mech->create_body_ok(2504, 'Birmingham City Council'); + $mech->create_body_ok(2226, 'Gloucestershire County Council'); + $mech->create_body_ok(2326, 'Cheltenham Borough Council'); + + $mech->get_ok('/alert/subscribe?rss=1&feed=area:1000:Birmingham'); + is $mech->uri->path, '/rss/reports/Birmingham'; + + $mech->get_ok('/alert/subscribe?rss=1&feed=area:1000:1001:Cheltenham:Lansdown'); + is $mech->uri->path, '/rss/area/Cheltenham/Lansdown'; + + $mech->get_ok('/alert/subscribe?rss=1&feed=council:1000:Gloucestershire'); + is $mech->uri->path, '/rss/reports/Gloucestershire'; + + $mech->get_ok('/alert/subscribe?rss=1&feed=ward:1000:1001:Cheltenham:Lansdown'); + is $mech->uri->path, '/rss/reports/Cheltenham/Lansdown'; +}; done_testing(); diff --git a/t/app/controller/alert_new.t b/t/app/controller/alert_new.t index c849b9485..ea38f7c25 100644 --- a/t/app/controller/alert_new.t +++ b/t/app/controller/alert_new.t @@ -3,15 +3,20 @@ use warnings; use Test::More; use FixMyStreet::TestMech; +use FixMyStreet::App; my $mech = FixMyStreet::TestMech->new; +$mech->log_in_ok('test@example.com'); +$mech->get_ok('/alert/subscribe?id=1'); +my ($csrf) = $mech->content =~ /name="token" value="([^"]*)"/; + foreach my $test ( { email => 'test@example.com', type => 'area_problems', - content => 'your alert will not be activated', - email_text => 'confirm the alert', + content => 'Click the link in our confirmation email to activate your alert', + email_text => "confirms that you'd like to receive an email", uri => '/alert/subscribe?type=local&rznvy=test@example.com&feed=area:1000:A_Location', param1 => 1000 @@ -19,8 +24,8 @@ foreach my $test ( { email => 'test@example.com', type => 'council_problems', - content => 'your alert will not be activated', - email_text => 'confirm the alert', + content => 'Click the link in our confirmation email to activate your alert', + email_text => "confirms that you'd like to receive an email", uri => '/alert/subscribe?type=local&rznvy=test@example.com&feed=council:1000:A_Location', param1 => 1000, @@ -29,8 +34,8 @@ foreach my $test ( { email => 'test@example.com', type => 'ward_problems', - content => 'your alert will not be activated', - email_text => 'confirm the alert', + content => 'Click the link in our confirmation email to activate your alert', + email_text => "confirms that you'd like to receive an email", uri => '/alert/subscribe?type=local&rznvy=test@example.com&feed=ward:1000:1001:A_Location:Diff_Location', param1 => 1000, @@ -39,8 +44,8 @@ foreach my $test ( { email => 'test@example.com', type => 'local_problems', - content => 'your alert will not be activated', - email_text => 'confirm the alert', + content => 'Click the link in our confirmation email to activate your alert', + email_text => "confirms that you'd like to receive an email", uri => '/alert/subscribe?type=local&rznvy=test@example.com&feed=local:10.2:20.1', param1 => 20.1, @@ -49,8 +54,8 @@ foreach my $test ( { email => 'test@example.com', type => 'new_updates', - content => 'your alert will not be activated', - email_text => 'confirm the alert', + content => 'Click the link in our confirmation email to activate your alert', + email_text => "confirms that you'd like to receive an email", uri => '/alert/subscribe?type=updates&rznvy=test@example.com&id=1', param1 => 1, } @@ -70,7 +75,7 @@ foreach my $test ( $mech->delete_user($user); } - $mech->get_ok( $test->{uri} ); + $mech->get_ok( $test->{uri} . "&token=$csrf" ); $mech->content_contains( $test->{content} ); $user = @@ -93,9 +98,10 @@ foreach my $test ( my $email = $mech->get_email; ok $email, "got an email"; - like $email->body, qr/$test->{email_text}/i, "Correct email text"; + like $mech->get_text_body_from_email($email), qr/$test->{email_text}/i, "Correct email text"; - my ( $url, $url_token ) = $email->body =~ m{(http://\S+/A/)(\S+)}; + my $url = $mech->get_link_from_email($email); + my ($url_token) = $url =~ m{/A/(\S+)}; ok $url, "extracted confirm url '$url'"; my $token = FixMyStreet::App->model('DB::Token')->find( @@ -112,12 +118,10 @@ foreach my $test ( my $existing_id = $alert->id; my $existing_token = $url_token; - $mech->get_ok( $test->{uri} ); + $mech->get_ok( $test->{uri} . "&token=$csrf" ); - $email = $mech->get_email; - ok $email, 'got a second email'; - - ($url_token) = $email->body =~ m{http://\S+/A/(\S+)}; + $url = $mech->get_link_from_email; + ($url_token) = $url =~ m{/A/(\S+)}; ok $url_token ne $existing_token, 'sent out a new token'; $token = FixMyStreet::App->model('DB::Token')->find( @@ -131,7 +135,7 @@ foreach my $test ( ok $token->data->{id} == $existing_id, 'subscribed to existing alert'; $mech->get_ok("/A/$url_token"); - $mech->content_contains('successfully confirmed'); + $mech->content_contains('alert created'); $alert = FixMyStreet::App->model('DB::Alert')->find( { id => $existing_id, } ); @@ -151,9 +155,7 @@ foreach my $test ( my $type = 'area_problems'; - my $user = - FixMyStreet::App->model('DB::User') - ->find_or_create( { email => 'test-new@example.com' } ); + my $user = $mech->create_user_ok('test-new@example.com'); my $alert = FixMyStreet::App->model('DB::Alert')->find( { @@ -164,7 +166,7 @@ foreach my $test ( # clear existing data so we can be sure we're creating it ok $alert->delete() if $alert && !$test->{exist}; - $mech->get_ok( '/alert/subscribe?type=local&rznvy=test-new@example.com&feed=area:1000:A_Location' ); + $mech->get_ok( '/alert/subscribe?type=local&rznvy=test-new@example.com&feed=area:1000:A_Location&token=' . $csrf ); $alert = FixMyStreet::App->model('DB::Alert')->find( { @@ -199,14 +201,17 @@ foreach my $test ( subtest $test->{desc} => sub { my $type = $test->{type} . '_problems'; - my $user = - FixMyStreet::App->model('DB::User') - ->find_or_create( { email => $test->{email} } ); + my $user = $mech->create_user_ok($test->{email}); $mech->log_in_ok( $test->{email} ); $mech->clear_emails_ok; - $mech->get_ok('/alert/list?pc=EH991SP'); + FixMyStreet::override_config { + ALLOWED_COBRANDS => [ { 'fixmystreet' => '.' } ], + MAPIT_URL => 'http://mapit.uk/', + }, sub { + $mech->get_ok('/alert/list?pc=EH991SP'); + }; $mech->set_visible( [ radio => 'council:2651:City_of_Edinburgh' ] ); $mech->click('alert'); @@ -230,7 +235,7 @@ for my $test ( { email => 'test@example.com', type => 'new_updates', - content => 'your alert will not be activated', + content => 'Click the link in our confirmation email to activate your alert', email_text => 'confirm the alert', uri => '/alert/subscribe?type=updates&rznvy=test@example.com&id=1', param1 => 1, @@ -256,7 +261,7 @@ for my $test ( FixMyStreet::App->model('DB::Abuse') ->find_or_create( { email => $test->{email} } ); - $mech->get_ok( $test->{uri} ); + $mech->get_ok( $test->{uri} . "&token=$csrf" ); $mech->content_contains( $test->{content} ); $user = @@ -277,29 +282,8 @@ for my $test ( ok $alert, "Found the alert"; - my $email = $mech->get_email; - ok $email, "got an email"; - like $email->body, qr/$test->{email_text}/i, "Correct email text"; - - my ( $url, $url_token ) = $email->body =~ m{(http://\S+/A/)(\S+)}; - ok $url, "extracted confirm url '$url'"; - - my $token = FixMyStreet::App->model('DB::Token')->find( - { - token => $url_token, - scope => 'alert' - } - ); - ok $token, 'Token found in database'; - ok $alert->id == $token->data->{id}, 'token alertid matches alert id'; - $mech->clear_emails_ok; - $mech->get_ok("/A/$url_token"); - $mech->content_contains('error confirming'); - - $alert->discard_changes; - ok !$alert->confirmed, 'alert not set to confirmed'; $abuse->delete; @@ -307,43 +291,48 @@ for my $test ( }; } +$mech->create_body_ok(2226, 'Gloucestershire County Council'); +$mech->create_body_ok(2326, 'Cheltenham Borough Council'); + subtest "Test two-tier council alerts" => sub { for my $alert ( { feed => "local:51.896269:-2.093063", result => '/rss/l/51.896269,-2.093063' }, { feed => "area:2326:Cheltenham", result => '/rss/area/Cheltenham' }, { feed => "area:2326:4544:Cheltenham:Lansdown", result => '/rss/area/Cheltenham/Lansdown' }, { feed => "area:2226:Gloucestershire", result => '/rss/area/Gloucestershire' }, - { feed => "area:2226:14949:Gloucestershire:Lansdown%2C_Park_and_Warden_Hill", - result => '/rss/area/Gloucestershire/Lansdown%2C+Park+and+Warden+Hill' + { feed => "area:2226:14949:Gloucestershire:Lansdown_and_Park", + result => '/rss/area/Gloucestershire/Lansdown+and+Park' }, { feed => "council:2326:Cheltenham", result => '/rss/reports/Cheltenham' }, { feed => "ward:2326:4544:Cheltenham:Lansdown", result => '/rss/reports/Cheltenham/Lansdown' }, { feed => "council:2226:Gloucestershire", result => '/rss/reports/Gloucestershire' }, - { feed => "ward:2226:14949:Gloucestershire:Lansdown%2C_Park_and_Warden_Hill", - result => '/rss/reports/Gloucestershire/Lansdown%2C+Park+and+Warden+Hill' + { feed => "ward:2226:14949:Gloucestershire:Lansdown_and_Park", + result => '/rss/reports/Gloucestershire/Lansdown+and+Park' }, ) { - $mech->get_ok( '/alert/list?pc=GL502PR' ); - $mech->submit_form_ok( { - button => 'rss', - with_fields => { - feed => $alert->{feed}, - } - } ); + FixMyStreet::override_config { + ALLOWED_COBRANDS => [ { 'fixmystreet' => '.' } ], + MAPIT_URL => 'http://mapit.uk/', + }, sub { + $mech->get_ok( '/alert/list?pc=GL502PR' ); + $mech->submit_form_ok( { + button => 'rss', + with_fields => { + feed => $alert->{feed}, + } + } ); + }; is $mech->uri->path, $alert->{result}, 'Redirected to right RSS feed'; } }; subtest "Test normal alert signups and that alerts are sent" => sub { - my $user1 = FixMyStreet::App->model('DB::User') - ->find_or_create( { email => 'reporter@example.com', name => 'Reporter User' } ); - ok $user1, "created test user"; - $user1->alerts->delete; + $mech->delete_user( 'reporter@example.com' ); + $mech->delete_user( 'alerts@example.com' ); - my $user2 = FixMyStreet::App->model('DB::User') - ->find_or_create( { email => 'alerts@example.com', name => 'Alert User' } ); - ok $user2, "created test user"; - $user2->alerts->delete; + my $user1 = $mech->create_user_ok('reporter@example.com', name => 'Reporter User' ); + + my $user2 = $mech->create_user_ok('alerts@example.com', name => 'Alert User' ); for my $alert ( { @@ -355,34 +344,41 @@ subtest "Test normal alert signups and that alerts are sent" => sub { }, { fields => { - feed => 'council:2651:City_of_Edinburgh', + feed => 'area:2651:City_of_Edinburgh', } }, ) { $mech->get_ok( '/alert' ); - $mech->submit_form_ok( { with_fields => { pc => 'EH11BB' } } ); - $mech->submit_form_ok( { - button => 'alert', - with_fields => $alert->{fields}, - } ); + FixMyStreet::override_config { + ALLOWED_COBRANDS => [ { 'fixmystreet' => '.' } ], + MAPIT_URL => 'http://mapit.uk/', + }, sub { + $mech->submit_form_ok( { with_fields => { pc => 'EH11BB' } } ); + $mech->submit_form_ok( { + button => 'alert', + with_fields => $alert->{fields}, + } ); + }; if ( $alert->{email_confirm} ) { - my $email = $mech->get_email; + my $url = $mech->get_link_from_email; + my ($url_token) = $url =~ m{/A/(\S+)}; $mech->clear_emails_ok; - my ( $url, $url_token ) = $email->body =~ m{http://\S+(/A/(\S+))}; my $token = FixMyStreet::App->model('DB::Token')->find( { token => $url_token, scope => 'alert' } ); $mech->get_ok( $url ); - $mech->content_contains('successfully confirmed'); + $mech->content_contains('alert created'); } else { - $mech->content_contains('successfully created'); + $mech->content_contains('alert created'); } } - my $dt = DateTime->now()->add( days => 2); + my $dt = DateTime->now(time_zone => 'Europe/London')->add(days => 2); + + my $dt_parser = FixMyStreet::App->model('DB')->schema->storage->datetime_parser; my $report_time = '2011-03-01 12:00:00'; my $report = FixMyStreet::App->model('DB::Problem')->find_or_create( { postcode => 'EH1 1BB', - council => '2651', + bodies_str => '1', areas => ',11808,135007,14419,134935,2651,20728,', category => 'Street lighting', title => 'Testing', @@ -391,9 +387,9 @@ subtest "Test normal alert signups and that alerts are sent" => sub { name => $user1->name, anonymous => 0, state => 'fixed - user', - confirmed => $dt, - lastupdate => $dt, - whensent => $dt->clone->add( minutes => 5 ), + confirmed => $dt_parser->format_datetime($dt), + lastupdate => $dt_parser->format_datetime($dt), + whensent => $dt_parser->format_datetime($dt->clone->add( minutes => 5 )), lang => 'en-gb', service => '', cobrand => 'default', @@ -439,36 +435,149 @@ subtest "Test normal alert signups and that alerts are sent" => sub { $update_id = $update->id; ok $update, "created test update - $update_id"; - FixMyStreet::App->model('DB::AlertType')->email_alerts(); + FixMyStreet::override_config { + MAPIT_URL => 'http://mapit.uk/', + }, sub { + FixMyStreet::App->model('DB::AlertType')->email_alerts(); + }; # TODO Note the below will fail if the db has an existing alert that matches $mech->email_count_is(3); my @emails = $mech->get_email; my $count; for (@emails) { - $count++ if $_->body =~ /The following updates have been left on this problem:/; - $count++ if $_->body =~ /The following new problems have been reported to City of\s*Edinburgh Council:/; - $count++ if $_->body =~ /The following nearby problems have been added:/; - $count++ if $_->body =~ /\s+-\s+Testing/; + my $body = $mech->get_text_body_from_email($_); + $count++ if $body =~ /The following updates have been left on this report:/; + $count++ if $body =~ /The following new FixMyStreet reports have been added in the Area 2651 area:/; + $count++ if $body =~ /The following FixMyStreet reports have been made within the area you\s+specified:/; + $count++ if $body =~ /\s+-\s+Testing/; } is $count, 5, 'Three emails, with five matching lines in them'; my $email = $emails[0]; - like $email->body, qr/Other User/, 'Update name given'; - unlike $email->body, qr/Anonymous User/, 'Update name not given'; + my $body = $mech->get_text_body_from_email($email); + like $body, qr/Other User/, 'Update name given'; + unlike $body, qr/Anonymous User/, 'Update name not given'; - # The update alert was to the problem reporter, so has a login update URL + # The update alert was to the problem reporter, so has a special update URL + $mech->log_out_ok; $mech->get_ok( "/report/$report_id" ); $mech->content_lacks( 'has not been fixed' ); - my ($url) = $email->body =~ m{(http://\S+/M/\S+)}; - ok $url, "extracted update url '$url'"; - $mech->get_ok( $url ); + my @urls = $mech->get_link_from_email($email, 1); + ok $urls[0] =~ m{/R/\S+}, "extracted update url '$urls[0]'"; + $mech->get_ok( $urls[0] ); is $mech->uri->path, "/report/" . $report_id, "redirected to report page"; $mech->content_contains( 'has not been fixed' ); - $mech->logged_in_ok; + $mech->not_logged_in_ok; + + ok $urls[-1] =~ m{/A/\S+}, "unsubscribe URL '$urls[-1]'"; + $mech->get_ok( $urls[-1] ); + $mech->content_contains('alert deleted'); + $mech->not_logged_in_ok; + + $mech->delete_user($user1); + $mech->delete_user($user2); +}; + +subtest "Test signature template is used from cobrand" => sub { + $mech->delete_user( 'reporter@example.com' ); + $mech->delete_user( 'alerts@example.com' ); + + my $user1 = $mech->create_user_ok('reporter@example.com', name => 'Reporter User' ); + + my $user2 = $mech->create_user_ok('alerts@example.com', name => 'Alert User' ); + + my $dt = DateTime->now(time_zone => 'Europe/London')->add(days => 2); + + my $dt_parser = FixMyStreet::App->model('DB')->schema->storage->datetime_parser; + + my $report_time = '2011-03-01 12:00:00'; + my $report = FixMyStreet::App->model('DB::Problem')->find_or_create( { + postcode => 'EH1 1BB', + bodies_str => '2651', + areas => ',11808,135007,14419,134935,2651,20728,', + category => 'Street lighting', + title => 'Testing', + detail => 'Testing Detail', + used_map => 1, + name => $user1->name, + anonymous => 0, + state => 'fixed - user', + confirmed => $dt_parser->format_datetime($dt), + lastupdate => $dt_parser->format_datetime($dt), + whensent => $dt_parser->format_datetime($dt->clone->add( minutes => 5 )), + lang => 'en-gb', + service => '', + cobrand => 'default', + cobrand_data => '', + send_questionnaire => 1, + latitude => '55.951963', + longitude => '-3.189944', + user_id => $user1->id, + } ); + my $report_id = $report->id; + ok $report, "created test report - $report_id"; + + my $alert = FixMyStreet::App->model('DB::Alert')->create( { + parameter => $report_id, + alert_type => 'new_updates', + user => $user1, + cobrand => 'default', + } ); + my $ret = $alert->confirm; + ok $ret, 'created alert for reporter'; + + my $update = FixMyStreet::App->model('DB::Comment')->create( { + problem_id => $report_id, + user_id => $user2->id, + name => 'Other User', + mark_fixed => 'false', + text => 'This is some update text', + state => 'confirmed', + confirmed => $dt->clone->add( hours => 7 ), + anonymous => 'f', + } ); + my $update_id = $update->id; + ok $update, "created test update - $update_id"; - ($url) = $emails[0]->body =~ m{http://\S+(/A/\S+)}; - $mech->get_ok( $url ); - $mech->content_contains('successfully deleted'); + + $mech->clear_emails_ok; + FixMyStreet::override_config { + MAPIT_URL => 'http://mapit.uk/', + ALLOWED_COBRANDS => [ { 'fixmystreet' => '.' } ], + }, sub { + FixMyStreet::App->model('DB::AlertType')->email_alerts(); + }; + + my $email = $mech->get_text_body_from_email; + like $email, qr/All the best/, 'default signature used'; + unlike $email, qr/twitter.com/, 'nothing from fixmystreet signature'; + + $update = FixMyStreet::App->model('DB::Comment')->create( { + problem_id => $report_id, + user_id => $user2->id, + name => 'Anonymous User', + mark_fixed => 'true', + text => 'This is some more update text', + state => 'confirmed', + confirmed => $dt->clone->add( hours => 8 ), + anonymous => 't', + } ); + $update_id = $update->id; + ok $update, "created test update - $update_id"; + + $alert->cobrand('fixmystreet'); + $alert->update; + + $mech->clear_emails_ok; + FixMyStreet::override_config { + MAPIT_URL => 'http://mapit.uk/', + ALLOWED_COBRANDS => [ { 'fixmystreet' => '.' } ], + }, sub { + FixMyStreet::App->model('DB::AlertType')->email_alerts(); + }; + + $email = $mech->get_text_body_from_email; + like $email, qr/twitter.com/, 'fixmystreet signature used'; $mech->delete_user($user1); $mech->delete_user($user2); @@ -509,15 +618,12 @@ for my $test ( }, ) { subtest $test->{desc} => sub { - my $user1 = FixMyStreet::App->model('DB::User') - ->find_or_create( { email => 'reporter@example.com', name => 'Reporter User' } ); - ok $user1, "created test user"; - $user1->alerts->delete; + $mech->delete_user( 'reporter@example.com' ); + $mech->delete_user( 'alerts@example.com' ); + + my $user1 = $mech->create_user_ok('reporter@example.com', name => 'Reporter User'); - my $user2 = FixMyStreet::App->model('DB::User') - ->find_or_create( { email => 'alerts@example.com', name => 'Alert User' } ); - ok $user2, "created test user"; - $user2->alerts->delete; + my $user2 = $mech->create_user_ok('alerts@example.com', name => 'Alert User'); my $dt = DateTime->now->add( minutes => -30 ); my $r_dt = $dt->clone->add( minutes => 20 ); @@ -530,9 +636,11 @@ for my $test ( my $alert_user1 = FixMyStreet::App->model('DB::Alert')->create( $alert_params ); ok $alert_user1, "alert created"; + my $dt_parser = FixMyStreet::App->model('DB')->schema->storage->datetime_parser; + my $report = FixMyStreet::App->model('DB::Problem')->find_or_create( { postcode => 'EH1 1BB', - council => '2651', + bodies_str => '2651', areas => ',11808,135007,14419,134935,2651,20728,', category => 'Street lighting', title => 'Alert test for non public reports', @@ -541,9 +649,9 @@ for my $test ( name => $user2->name, anonymous => 0, state => 'confirmed', - confirmed => $r_dt, - lastupdate => $r_dt, - whensent => $r_dt->clone->add( minutes => 5 ), + confirmed => $dt_parser->format_datetime($r_dt), + lastupdate => $dt_parser->format_datetime($r_dt), + whensent => $dt_parser->format_datetime($r_dt->clone->add( minutes => 5 )), lang => 'en-gb', service => '', cobrand => 'default', @@ -556,14 +664,21 @@ for my $test ( } ); $mech->clear_emails_ok; - FixMyStreet::App->model('DB::AlertType')->email_alerts(); + FixMyStreet::override_config { + MAPIT_URL => 'http://mapit.uk/', + }, sub { + FixMyStreet::App->model('DB::AlertType')->email_alerts(); + }; $mech->email_count_is(0); $report->update( { non_public => 0 } ); - FixMyStreet::App->model('DB::AlertType')->email_alerts(); - $mech->email_count_is(1); - my $email = $mech->get_email; - like $email->body, qr/Alert\s+test\s+for\s+non\s+public\s+reports/, 'alert contains public report'; + FixMyStreet::override_config { + MAPIT_URL => 'http://mapit.uk/', + }, sub { + FixMyStreet::App->model('DB::AlertType')->email_alerts(); + }; + my $email = $mech->get_text_body_from_email; + like $email, qr/Alert\s+test\s+for\s+non\s+public\s+reports/, 'alert contains public report'; $mech->delete_user( $user1 ); $mech->delete_user( $user2 ); @@ -571,26 +686,23 @@ for my $test ( } subtest 'check new updates alerts for non public reports only go to report owner' => sub { - my $user1 = FixMyStreet::App->model('DB::User') - ->find_or_create( { email => 'reporter@example.com', name => 'Reporter User' } ); - ok $user1, "created test user"; - $user1->alerts->delete; + $mech->delete_user( 'reporter@example.com' ); + $mech->delete_user( 'alerts@example.com' ); - my $user2 = FixMyStreet::App->model('DB::User') - ->find_or_create( { email => 'alerts@example.com', name => 'Alert User' } ); - ok $user2, "created test user"; - $user2->alerts->delete; + my $user1 = $mech->create_user_ok('reporter@example.com', name => 'Reporter User'); - my $user3 = FixMyStreet::App->model('DB::User') - ->find_or_create( { email => 'updates@example.com', name => 'Update User' } ); - ok $user3, "created test user"; + my $user2 = $mech->create_user_ok('alerts@example.com', name => 'Alert User'); + + my $user3 = $mech->create_user_ok('updates@example.com', name => 'Update User'); my $dt = DateTime->now->add( minutes => -30 ); my $r_dt = $dt->clone->add( minutes => 20 ); + my $dt_parser = FixMyStreet::App->model('DB')->schema->storage->datetime_parser; + my $report = FixMyStreet::App->model('DB::Problem')->find_or_create( { postcode => 'EH1 1BB', - council => '2651', + bodies_str => '2651', areas => ',11808,135007,14419,134935,2651,20728,', category => 'Street lighting', title => 'Alert test for non public reports', @@ -599,9 +711,9 @@ subtest 'check new updates alerts for non public reports only go to report owner name => $user2->name, anonymous => 0, state => 'confirmed', - confirmed => $r_dt, - lastupdate => $r_dt, - whensent => $r_dt->clone->add( minutes => 5 ), + confirmed => $dt_parser->format_datetime($r_dt), + lastupdate => $dt_parser->format_datetime($r_dt), + whensent => $dt_parser->format_datetime($r_dt->clone->add( minutes => 5 )), lang => 'en-gb', service => '', cobrand => 'default', @@ -648,20 +760,102 @@ subtest 'check new updates alerts for non public reports only go to report owner ok $alert_user2, "alert created"; FixMyStreet::App->model('DB::AlertType')->email_alerts(); - $mech->email_count_is(1); - my $email = $mech->get_email; - like $email->body, qr/This is some more update text/, 'alert contains update text'; + my $email = $mech->get_text_body_from_email; + like $email, qr/This is some more update text/, 'alert contains update text'; $mech->clear_emails_ok; $report->update( { non_public => 0 } ); FixMyStreet::App->model('DB::AlertType')->email_alerts(); - $mech->email_count_is(1); - $email = $mech->get_email; - like $email->body, qr/This is some more update text/, 'alert contains update text'; + $email = $mech->get_text_body_from_email; + like $email, qr/This is some more update text/, 'alert contains update text'; + + $mech->delete_user( $user1 ); + $mech->delete_user( $user2 ); + $mech->delete_user( $user3 ); +}; + +subtest 'check setting inlude dates in new updates cobrand option' => sub { + my $include_date_in_alert_override= Sub::Override->new( + "FixMyStreet::Cobrand::Default::include_time_in_update_alerts", + sub { return 1; } + ); + $mech->delete_user( 'reporter@example.com' ); + $mech->delete_user( 'alerts@example.com' ); + + my $user1 = $mech->create_user_ok('reporter@example.com', name => 'Reporter User'); + + my $user2 = $mech->create_user_ok('alerts@example.com', name => 'Alert User'); + + my $user3 = $mech->create_user_ok('updates@example.com', name => 'Update User'); + + my $dt = DateTime->now->add( minutes => -30 ); + my $r_dt = $dt->clone->add( minutes => 20 ); + + my $dt_parser = FixMyStreet::App->model('DB')->schema->storage->datetime_parser; + + my $report = FixMyStreet::App->model('DB::Problem')->find_or_create( { + postcode => 'EH1 1BB', + bodies_str => '2651', + areas => ',11808,135007,14419,134935,2651,20728,', + category => 'Street lighting', + title => 'Alert test for non public reports', + detail => 'Testing Detail', + used_map => 1, + name => $user2->name, + anonymous => 0, + state => 'confirmed', + confirmed => $dt_parser->format_datetime($r_dt), + lastupdate => $dt_parser->format_datetime($r_dt), + whensent => $dt_parser->format_datetime($r_dt->clone->add( minutes => 5 )), + lang => 'en-gb', + service => '', + cobrand => 'default', + cobrand_data => '', + send_questionnaire => 1, + latitude => '55.951963', + longitude => '-3.189944', + user_id => $user2->id, + } ); + + my $update = FixMyStreet::App->model('DB::Comment')->create( { + problem_id => $report->id, + user_id => $user3->id, + name => 'Anonymous User', + mark_fixed => 'false', + text => 'This is some more update text', + state => 'confirmed', + confirmed => $r_dt->clone->add( minutes => 8 ), + anonymous => 't', + } ); + + my $alert_user1 = FixMyStreet::App->model('DB::Alert')->create( { + user => $user1, + alert_type => 'new_updates', + parameter => $report->id, + confirmed => 1, + whensubscribed => $dt, + } ); + ok $alert_user1, "alert created"; + + + $mech->clear_emails_ok; + FixMyStreet::App->model('DB::AlertType')->email_alerts(); + + # if we don't do this then we're applying the date inflation code and + # it's timezone munging to the DateTime object above and not the DateTime + # object that's inflated from the database value and these turn out to be + # different as the one above has a UTC timezone and not the floating one + # that those from the DB do. + $update->discard_changes(); + + my $date_in_alert = Utils::prettify_dt( $update->confirmed ); + my $email = $mech->get_text_body_from_email; + like $email, qr/$date_in_alert/, 'alert contains date'; $mech->delete_user( $user1 ); $mech->delete_user( $user2 ); $mech->delete_user( $user3 ); + $include_date_in_alert_override->restore(); }; done_testing(); diff --git a/t/app/controller/around.t b/t/app/controller/around.t index d973543ce..c8aca04aa 100644 --- a/t/app/controller/around.t +++ b/t/app/controller/around.t @@ -7,14 +7,18 @@ my $mech = FixMyStreet::TestMech->new; subtest "check that if no query we get sent back to the homepage" => sub { $mech->get_ok('/around'); - is $mech->uri->path, '/around', "still at '/around'"; + is $mech->uri->path, '/', "redirected to '/'"; }; # x,y requests were generated by the old map code. We keep the behavior for # historic links subtest "redirect x,y requests to lat/lon (301 - permanent)" => sub { - $mech->get_ok('/around?x=3281&y=1113'); + FixMyStreet::override_config { + MAPIT_URL => 'http://mapit.uk/', + }, sub { + $mech->get_ok('/around?x=3281&y=1113'); + }; # did we redirect to lat,lon? is $mech->uri->path, '/around', "still on /around"; @@ -50,8 +54,12 @@ foreach my $test ( { subtest "test bad pc value '$test->{pc}'" => sub { $mech->get_ok('/'); - $mech->submit_form_ok( { with_fields => { pc => $test->{pc} } }, - "bad location" ); + FixMyStreet::override_config { + GEOCODER => '', + }, sub { + $mech->submit_form_ok( { with_fields => { pc => $test->{pc} } }, + "bad location" ); + }; is_deeply $mech->page_errors, $test->{errors}, "expected errors for pc '$test->{pc}'"; is_deeply $mech->pc_alternatives, $test->{pc_alternatives}, @@ -63,8 +71,8 @@ foreach my $test ( foreach my $test ( { pc => 'SW1A 1AA', - latitude => '51.50101', - longitude => '-0.141587', + latitude => '51.501009', + longitude => '-0.141588', }, { pc => 'TQ 388 773', @@ -75,8 +83,13 @@ foreach my $test ( { subtest "check lat/lng for '$test->{pc}'" => sub { $mech->get_ok('/'); - $mech->submit_form_ok( { with_fields => { pc => $test->{pc} } }, - "good location" ); + FixMyStreet::override_config { + ALLOWED_COBRANDS => [ { 'fixmystreet' => '.' } ], + MAPIT_URL => 'http://mapit.uk/', + }, sub { + $mech->submit_form_ok( { with_fields => { pc => $test->{pc} } }, + "good location" ); + }; is_deeply $mech->page_errors, [], "no errors for pc '$test->{pc}'"; is_deeply $mech->extract_location, $test, "got expected location for pc '$test->{pc}'"; @@ -85,16 +98,21 @@ foreach my $test ( subtest 'check non public reports are not displayed on around page' => sub { my $params = { - postcode => 'EH99 1SP', + postcode => 'EH1 1BB', latitude => 55.9519637512, longitude => -3.17492254484, }; my @edinburgh_problems = - $mech->create_problems_for_council( 5, 2651, 'Around page', $params ); + $mech->create_problems_for_body( 5, 2651, 'Around page', $params ); $mech->get_ok('/'); - $mech->submit_form_ok( { with_fields => { pc => 'EH99 1SP' } }, - "good location" ); + FixMyStreet::override_config { + ALLOWED_COBRANDS => [ { 'fixmystreet' => '.' } ], + MAPIT_URL => 'http://mapit.uk/', + }, sub { + $mech->submit_form_ok( { with_fields => { pc => 'EH1 1BB' } }, + "good location" ); + }; $mech->content_contains( 'Around page Test 3 for 2651', 'problem to be marked non public visible' ); @@ -102,11 +120,55 @@ subtest 'check non public reports are not displayed on around page' => sub { ok $private->update( { non_public => 1 } ), 'problem marked non public'; $mech->get_ok('/'); - $mech->submit_form_ok( { with_fields => { pc => 'EH99 1SP' } }, - "good location" ); + FixMyStreet::override_config { + ALLOWED_COBRANDS => [ { 'fixmystreet' => '.' } ], + MAPIT_URL => 'http://mapit.uk/', + }, sub { + $mech->submit_form_ok( { with_fields => { pc => 'EH1 1BB' } }, + "good location" ); + }; $mech->content_lacks( 'Around page Test 3 for 2651', 'problem marked non public is not visible' ); }; +subtest 'check category and status filtering works on /ajax' => sub { + my $categories = [ 'Pothole', 'Vegetation', 'Flytipping' ]; + my $params = { + postcode => 'OX1 1ND', + latitude => 51.7435918829363, + longitude => -1.23201966270446, + }; + my $bbox = ($params->{longitude} - 0.01) . ',' . ($params->{latitude} - 0.01) + . ',' . ($params->{longitude} + 0.01) . ',' . ($params->{latitude} + 0.01); + + # Create one open and one fixed report in each category + foreach my $category ( @$categories ) { + foreach my $state ( 'confirmed', 'fixed' ) { + my %report_params = ( + %$params, + category => $category, + state => $state, + ); + $mech->create_problems_for_body( 1, 2237, 'Around page', \%report_params ); + } + } + + my $json = $mech->get_ok_json( '/ajax?bbox=' . $bbox ); + my $pins = $json->{pins}; + is scalar @$pins, 6, 'correct number of reports when no filters'; + + $json = $mech->get_ok_json( '/ajax?filter_category=Pothole&bbox=' . $bbox ); + $pins = $json->{pins}; + is scalar @$pins, 2, 'correct number of Pothole reports'; + + $json = $mech->get_ok_json( '/ajax?status=open&bbox=' . $bbox ); + $pins = $json->{pins}; + is scalar @$pins, 3, 'correct number of open reports'; + + $json = $mech->get_ok_json( '/ajax?status=fixed&filter_category=Vegetation&bbox=' . $bbox ); + $pins = $json->{pins}; + is scalar @$pins, 1, 'correct number of fixed Vegetation reports'; +}; + done_testing(); diff --git a/t/app/controller/auth.t b/t/app/controller/auth.t index 67466e959..3a11cfc4a 100644 --- a/t/app/controller/auth.t +++ b/t/app/controller/auth.t @@ -2,16 +2,19 @@ use strict; use warnings; use Test::More; +use Test::MockModule; use FixMyStreet::TestMech; my $mech = FixMyStreet::TestMech->new; my $test_email = 'test@example.com'; +my $test_email2 = 'test@example.net'; my $test_password = 'foobar'; $mech->delete_user($test_email); END { $mech->delete_user($test_email); + $mech->delete_user($test_email2); done_testing(); } @@ -31,6 +34,13 @@ for my $test ( ) { my ( $email, $error_message ) = @$test; + + my $resolver = Test::MockModule->new('Net::DNS::Resolver'); + $resolver->mock('send', sub { + my ($self, $domain, $type) = @_; + return Net::DNS::Packet->new; + }); + pass "--- testing bad email '$email' gives error '$error_message'"; $mech->get_ok('/auth'); is_deeply $mech->page_errors, [], 'no errors initially'; @@ -46,6 +56,10 @@ for my $test ( is_deeply $mech->page_errors, [ $error_message ], 'errors match'; } +# Email address parsing should pass from here +my $resolver = Test::MockModule->new('Email::Valid'); +$resolver->mock('address', sub { $_[1] }); + # create a new account $mech->clear_emails_ok; $mech->get_ok('/auth'); @@ -63,16 +77,14 @@ $mech->not_logged_in_ok; # check that we got one email { - $mech->email_count_is(1); my $email = $mech->get_email; $mech->clear_emails_ok; - is $email->header('Subject'), "Your FixMyStreet.com account details", + is $email->header('Subject'), "Your FixMyStreet account details", "subject is correct"; is $email->header('To'), $test_email, "to is correct"; # extract the link - my ($link) = $email->body =~ m{(http://\S+)}; - ok $link, "Found a link in email '$link'"; + my $link = $mech->get_link_from_email($email); # check that the user does not exist sub get_user { @@ -91,13 +103,8 @@ $mech->not_logged_in_ok; is $mech->uri->path, '/my', "redirected to the 'my' section of site"; $mech->logged_in_ok; - # logout and try to use the token again + # logout $mech->log_out_ok; - $mech->get_ok($link); - is $mech->uri, $link, "not logged in"; - $mech->content_contains( 'Link too old or already used', - 'token now invalid' ); - $mech->not_logged_in_ok; } # get a sign in email and change password @@ -121,10 +128,7 @@ $mech->not_logged_in_ok; # follow link and change password - check not prompted for old password $mech->not_logged_in_ok; - $mech->email_count_is(1); - my $email = $mech->get_email; - $mech->clear_emails_ok; - my ($link) = $email->body =~ m{(http://\S+)}; + my $link = $mech->get_link_from_email; $mech->get_ok($link); is $mech->uri->path, '/faq', "redirected to the Help page"; @@ -133,7 +137,7 @@ $mech->not_logged_in_ok; ok my $form = $mech->form_name('change_password'), "found change password form"; is_deeply [ sort grep { $_ } map { $_->name } $form->inputs ], # - [ 'confirm', 'new_password' ], + [ 'confirm', 'new_password', 'token' ], "check we got expected fields (ie not old_password)"; # check the various ways the form can be wrong @@ -180,6 +184,48 @@ $mech->not_logged_in_ok; ok $user->password, "user now has a password"; } +subtest "Test change email page" => sub { + # Still signed in from the above test + $mech->get_ok('/my'); + $mech->follow_link_ok({url => '/auth/change_email'}); + $mech->submit_form_ok( + { with_fields => { email => "" } }, + "submit blank change email form" + ); + $mech->content_contains( 'Please enter your email', "found expected error" ); + $mech->submit_form_ok({ with_fields => { email => $test_email2 } }, "change_email to $test_email2"); + is $mech->uri->path, '/auth/change_email', "still on change email page"; + $mech->content_contains( 'Now check your email', "found check your email" ); + my $link = $mech->get_link_from_email; + $mech->get_ok($link); + is $mech->uri->path, '/auth/change_email/success', "redirected to the change_email page"; + $mech->content_contains('successfully confirmed'); + ok(FixMyStreet::App->model('DB::User')->find( { email => $test_email2 } ), "got a user"); + + ok(FixMyStreet::App->model('DB::User')->create( { email => $test_email } ), "created old user"); + $mech->submit_form_ok({ with_fields => { email => $test_email } }, + "change_email back to $test_email" + ); + is $mech->uri->path, '/auth/change_email', "still on change email page"; + $mech->content_contains( 'Now check your email', "found check your email" ); + $link = $mech->get_link_from_email; + $mech->get_ok($link); + is $mech->uri->path, '/auth/change_email/success', "redirected to the change_email page"; + $mech->content_contains('successfully confirmed'); + + # Test you can't click the link if logged out + $mech->submit_form_ok({ with_fields => { email => $test_email } }, + "change_email back to $test_email" + ); + is $mech->uri->path, '/auth/change_email', "still on change email page"; + $mech->content_contains( 'Now check your email', "found check your email" ); + $link = $mech->get_link_from_email; + $mech->log_out_ok; + $mech->get_ok($link); + isnt $mech->uri->path, '/auth/change_email/success', "not redirected to the change_email page"; + $mech->content_contains('Sorry'); +}; + foreach my $remember_me ( '1', '0' ) { subtest "sign in using valid details (remember_me => '$remember_me')" => sub { $mech->get_ok('/auth'); @@ -193,7 +239,7 @@ foreach my $remember_me ( '1', '0' ) { }, button => 'sign_in', }, - "sign in with '$test_email' & '$test_password" + "sign in with '$test_email' & '$test_password'" ); is $mech->uri->path, '/my', "redirected to correct page"; @@ -218,10 +264,28 @@ $mech->submit_form_ok( }, button => 'sign_in', }, - "sign in with '$test_email' & '$test_password" + "sign in with '$test_email' & 'not the password'" ); is $mech->uri->path, '/auth', "redirected to correct page"; $mech->content_contains( 'problem with your email/password combination', 'found error message' ); +subtest "sign in but have email form autofilled" => sub { + $mech->get_ok('/auth'); + $mech->submit_form_ok( + { + form_name => 'general_auth', + fields => { + email => $test_email, + password_sign_in => $test_password, + name => 'Auto-completed from elsewhere', + }, + button => 'sign_in', + }, + "sign in with '$test_email' and auto-completed name" + ); + is $mech->uri->path, '/my', "redirected to correct page"; +}; + + # more test: # TODO: test that email are always lowercased diff --git a/t/app/controller/auth_social.t b/t/app/controller/auth_social.t new file mode 100644 index 000000000..09fdf22d3 --- /dev/null +++ b/t/app/controller/auth_social.t @@ -0,0 +1,259 @@ +use strict; +use warnings; +use Test::More; +use Test::MockModule; +use LWP::Protocol::PSGI; +use LWP::Simple; +use JSON::MaybeXS; + +use t::Mock::Facebook; +use t::Mock::Twitter; + +use FixMyStreet::TestMech; +my $mech = FixMyStreet::TestMech->new; + +# disable info logs for this test run +FixMyStreet::App->log->disable('info'); +END { FixMyStreet::App->log->enable('info'); } + +my ($report) = $mech->create_problems_for_body(1, '2345', 'Test'); + +FixMyStreet::override_config { + FACEBOOK_APP_ID => 'facebook-app-id', + TWITTER_KEY => 'twitter-key', + ALLOWED_COBRANDS => [ { fixmystreet => '.' } ], + MAPIT_URL => 'http://mapit.uk/', +}, sub { + +my $fb_email = 'facebook@example.org'; +my $fb_uid = 123456789; + +my $resolver = Test::MockModule->new('Email::Valid'); +$resolver->mock('address', sub { 'facebook@example.org' }); + +for my $fb_state ( 'refused', 'no email', 'existing UID', 'okay' ) { + for my $page ( 'my', 'report', 'update' ) { + subtest "test FB '$fb_state' login for page '$page'" => sub { + # Lots of user changes happening here, make sure we don't confuse + # Catalyst with a cookie session user that no longer exists + $mech->log_out_ok; + $mech->cookie_jar({}); + if ($fb_state eq 'existing UID') { + my $user = $mech->create_user_ok($fb_email); + $user->update({ facebook_id => $fb_uid }); + } else { + $mech->delete_user($fb_email); + } + + # Set up a mock to catch (most, see below) requests to Facebook + my $fb = t::Mock::Facebook->new; + $fb->returns_email(0) if $fb_state eq 'no email' || $fb_state eq 'existing UID'; + LWP::Protocol::PSGI->register($fb->to_psgi_app, host => 'www.facebook.com'); + LWP::Protocol::PSGI->register($fb->to_psgi_app, host => 'graph.facebook.com'); + + # Due to https://metacpan.org/pod/Test::WWW::Mechanize::Catalyst#External-Redirects-and-allow_external + # the redirect to Facebook's OAuth page can mess up the session + # cookie. So let's pretend we always on www.facebook.com, which + # sorts that out. + $mech->host('www.facebook.com'); + + # Fetch the page with the form via which we wish to log in + my $fields; + if ($page eq 'my') { + $mech->get_ok('/my'); + } elsif ($page eq 'report') { + $mech->get_ok('/'); + $mech->submit_form_ok( { with_fields => { pc => 'SW1A1AA' } }, "submit location" ); + $mech->follow_link_ok( { text_regex => qr/skip this step/i, }, "follow 'skip this step' link" ); + $fields = { + title => 'Test title', + detail => 'Test detail', + }; + } else { + $mech->get_ok('/report/' . $report->id); + $fields = { + update => 'Test update', + }; + } + $mech->submit_form(with_fields => $fields, button => 'facebook_sign_in'); + + # As well as the cookie issue above, caused by this external + # redirect rewriting the host, the redirect gets handled directly + # by Catalyst, not our mocked handler, so will be a 404. Check + # the redirect happened instead. + is $mech->res->previous->code, 302, 'FB button redirected'; + like $mech->res->previous->header('Location'), qr{facebook\.com.*dialog/oauth.*facebook-app-id}, 'FB redirect to oauth URL'; + + # Okay, now call the callback Facebook would send us to + if ($fb_state eq 'refused') { + $mech->get_ok('/auth/Facebook?error_code=ERROR'); + } else { + $mech->get_ok('/auth/Facebook?code=response-code'); + } + + # Check we're showing the right form, regardless of what came back + if ($page eq 'report') { + $mech->content_contains('/report/new'); + } elsif ($page eq 'update') { + $mech->content_contains('/report/update'); + } + + if ($fb_state eq 'refused') { + $mech->content_contains('Sorry, we could not log you in. Please fill in the form below.'); + $mech->not_logged_in_ok; + } elsif ($fb_state eq 'no email') { + $mech->content_contains('We need your email address, please give it below.'); + # We don't have an email, so check that we can still submit it, + # and the ID carries through the confirmation + if ($page eq 'update') { + $fields->{rznvy} = $fb_email; + } else { + $fields->{email} = $fb_email; + } + $fields->{name} = 'Ffion Tester'; + $mech->submit_form(with_fields => $fields); + $mech->content_contains('Nearly done! Now check your email'); + + my $url = $mech->get_link_from_email; + $mech->clear_emails_ok; + ok $url, "extracted confirm url '$url'"; + + my $user = FixMyStreet::App->model( 'DB::User' )->find( { email => $fb_email } ); + if ($page eq 'my') { + is $user, undef, 'No user yet exists'; + } else { + is $user->facebook_id, undef, 'User has no facebook ID'; + } + $mech->get_ok( $url ); + $user = FixMyStreet::App->model( 'DB::User' )->find( { email => $fb_email } ); + is $user->facebook_id, $fb_uid, 'User now has correct facebook ID'; + + } elsif ($page ne 'my') { + # /my auth login goes directly there, no message like this + $mech->content_contains('You have successfully signed in; please check and confirm your details are accurate'); + $mech->logged_in_ok; + } else { + is $mech->uri->path, '/my', 'Successfully on /my page'; + } + } + } +} + +$resolver->mock('address', sub { 'twitter@example.org' }); + +my $tw_email = 'twitter@example.org'; +my $tw_uid = 987654321; + +# Twitter has no way of getting the email, so no "okay" state here +for my $tw_state ( 'refused', 'existing UID', 'no email' ) { + for my $page ( 'my', 'report', 'update' ) { + subtest "test Twitter '$tw_state' login for page '$page'" => sub { + # Lots of user changes happening here, make sure we don't confuse + # Catalyst with a cookie session user that no longer exists + $mech->log_out_ok; + $mech->cookie_jar({}); + if ($tw_state eq 'existing UID') { + my $user = $mech->create_user_ok($tw_email); + $user->update({ twitter_id => $tw_uid }); + } else { + $mech->delete_user($tw_email); + } + + # Set up a mock to catch (most, see below) requests to Twitter + my $tw = t::Mock::Twitter->new; + LWP::Protocol::PSGI->register($tw->to_psgi_app, host => 'api.twitter.com'); + + # Due to https://metacpan.org/pod/Test::WWW::Mechanize::Catalyst#External-Redirects-and-allow_external + # the redirect to Twitter's OAuth page can mess up the session + # cookie. So let's pretend we always on api.twitter.com, which + # sorts that out. + $mech->host('api.twitter.com'); + + # Fetch the page with the form via which we wish to log in + my $fields; + if ($page eq 'my') { + $mech->get_ok('/my'); + } elsif ($page eq 'report') { + $mech->get_ok('/'); + $mech->submit_form_ok( { with_fields => { pc => 'SW1A1AA' } }, "submit location" ); + $mech->follow_link_ok( { text_regex => qr/skip this step/i, }, "follow 'skip this step' link" ); + $fields = { + title => 'Test title', + detail => 'Test detail', + }; + } else { + $mech->get_ok('/report/' . $report->id); + $fields = { + update => 'Test update', + }; + } + $mech->submit_form(with_fields => $fields, button => 'twitter_sign_in'); + + # As well as the cookie issue above, caused by this external + # redirect rewriting the host, the redirect gets handled directly + # by Catalyst, not our mocked handler, so will be a 404. Check + # the redirect happened instead. + is $mech->res->previous->code, 302, 'Twitter button redirected'; + like $mech->res->previous->header('Location'), qr{api\.twitter\.com/oauth/authenticate\?oauth_token=request-token}, 'Twitter redirect to oauth URL'; + + # Okay, now call the callback Facebook would send us to + if ($tw_state eq 'refused') { + $mech->get_ok('/auth/Twitter?denied=token'); + } else { + $mech->get_ok('/auth/Twitter?oauth_token=request-token&oauth_verifier=verifier'); + } + + # Check we're showing the right form, regardless of what came back + if ($page eq 'report') { + $mech->content_contains('/report/new'); + } elsif ($page eq 'update') { + $mech->content_contains('/report/update'); + } + + if ($tw_state eq 'refused') { + $mech->content_contains('Sorry, we could not log you in. Please fill in the form below.'); + $mech->not_logged_in_ok; + } elsif ($tw_state eq 'no email') { + $mech->content_contains('We need your email address, please give it below.'); + # We don't have an email, so check that we can still submit it, + # and the ID carries through the confirmation + if ($page eq 'update') { + $fields->{rznvy} = $tw_email; + } else { + $fields->{email} = $tw_email; + } + $fields->{name} = 'Ffion Tester'; + $mech->submit_form(with_fields => $fields); + $mech->content_contains('Nearly done! Now check your email'); + + my $url = $mech->get_link_from_email; + $mech->clear_emails_ok; + ok $url, "extracted confirm url '$url'"; + + my $user = FixMyStreet::App->model( 'DB::User' )->find( { email => $tw_email } ); + if ($page eq 'my') { + is $user, undef, 'No user yet exists'; + } else { + is $user->twitter_id, undef, 'User has no twitter ID'; + } + $mech->get_ok( $url ); + $user = FixMyStreet::App->model( 'DB::User' )->find( { email => $tw_email } ); + is $user->twitter_id, $tw_uid, 'User now has correct twitter ID'; + + } elsif ($page ne 'my') { + # /my auth login goes directly there, no message like this + $mech->content_contains('You have successfully signed in; please check and confirm your details are accurate'); + $mech->logged_in_ok; + } else { + is $mech->uri->path, '/my', 'Successfully on /my page'; + } + } + } +} + +}; + +END { + $mech->delete_problems_for_body('2345'); + done_testing(); +} diff --git a/t/app/controller/contact.t b/t/app/controller/contact.t index 11e0d30cf..7c2769b9c 100644 --- a/t/app/controller/contact.t +++ b/t/app/controller/contact.t @@ -8,7 +8,7 @@ my $mech = FixMyStreet::TestMech->new; $mech->get_ok('/contact'); $mech->title_like(qr/Contact Us/); -$mech->content_contains("We'd love to hear what you think about this site"); +$mech->content_contains("It's often quickest to "); my $problem_main; @@ -52,12 +52,7 @@ for my $test ( ) { subtest 'check reporting a problem displays correctly' => sub { - my $user = FixMyStreet::App->model('DB::User')->find_or_create( - { - name => $test->{name}, - email => $test->{email} - } - ); + my $user = $mech->create_user_ok($test->{email}, name => $test->{name}); my $problem = FixMyStreet::App->model('DB::Problem')->create( { @@ -80,12 +75,8 @@ for my $test ( if ( $test->{update} ) { my $update_info = $test->{update}; - my $update_user = FixMyStreet::App->model('DB::User')->find_or_create( - { - name => $update_info->{name}, - email => $update_info->{email} - } - ); + my $update_user = $mech->create_user_ok($update_info->{email}, + name => $update_info->{name}); $update = FixMyStreet::App->model('DB::Comment')->create( { @@ -93,7 +84,7 @@ for my $test ( user => $update_user, state => 'confirmed', text => $update_info->{text}, - confirmed => \'ms_current_timestamp()', + confirmed => \'current_timestamp', mark_fixed => 'f', anonymous => 'f', } @@ -259,17 +250,17 @@ for my $test ( $mech->get_ok('/contact'); } $mech->submit_form_ok( { with_fields => $test->{fields} } ); - $mech->content_contains('Thanks for your feedback'); - $mech->email_count_is(1); + $mech->content_contains('Thank you for your enquiry'); my $email = $mech->get_email; is $email->header('Subject'), 'FMS message: ' . $test->{fields}->{subject}, 'subject'; is $email->header('From'), "\"$test->{fields}->{name}\" <$test->{fields}->{em}>", 'from'; - like $email->body, qr/$test->{fields}->{message}/, 'body'; - like $email->body, qr/Sent by contact.cgi on \S+. IP address (?:\d{1,3}\.){3,}\d{1,3}/, 'body footer'; + my $body = $mech->get_text_body_from_email($email); + like $body, qr/$test->{fields}->{message}/, 'body'; + like $body, qr/Sent by contact.cgi on \S+. IP address (?:\d{1,3}\.){3,}\d{1,3}/, 'body footer'; my $problem_id = $test->{fields}{id}; - like $email->body, qr/Complaint about report $problem_id/, 'reporting a report' + like $body, qr/Complaint about report $problem_id/, 'reporting a report' if $test->{fields}{id}; $problem_main->discard_changes; @@ -282,6 +273,115 @@ for my $test ( }; } +for my $test ( + { + fields => { + em => 'test@example.com', + name => 'A name', + subject => 'A subject', + message => 'A message', + dest => undef, + }, + page_errors => + [ 'There were problems with your report. Please see below.', + 'Please enter who your message is for', + ] + }, + { + fields => { + em => 'test@example.com', + name => 'A name', + subject => 'A subject', + message => 'A message', + dest => 'council', + }, + page_errors => + [ 'There were problems with your report. Please see below.', + 'You can only contact the team behind FixMyStreet using our contact form', + ] + }, + { + fields => { + em => 'test@example.com', + name => 'A name', + subject => 'A subject', + message => 'A message', + dest => 'update', + }, + page_errors => + [ 'There were problems with your report. Please see below.', + 'You can only contact the team behind FixMyStreet using our contact form', + ] + }, + ) +{ + subtest 'check submit page incorrect destination handling' => sub { + FixMyStreet::override_config { + ALLOWED_COBRANDS => [ 'fixmystreet' ], + }, sub { + $mech->host('www.fixmystreet.com'); + $mech->get_ok( $test->{url} ? $test->{url} : '/contact' ); + $mech->submit_form_ok( { with_fields => $test->{fields} } ); + is_deeply $mech->page_errors, $test->{page_errors}, 'page errors'; + + # we santise this when we submit so need to remove it + delete $test->{fields}->{id} + if $test->{fields}->{id} and $test->{fields}->{id} eq 'invalid'; + is_deeply $mech->visible_form_values, $test->{fields}, 'form values'; + + if ( $test->{fields}->{dest} and $test->{fields}->{dest} eq 'update' ) { + $mech->content_contains( 'www.writetothem.com', 'includes link to WTT if trying to update report' ); + } elsif ( $test->{fields}->{dest} and $test->{fields}->{dest} eq 'council' ) { + $mech->content_lacks( 'www.writetothem.com', 'does not include link to WTT if trying to contact council' ); + $mech->content_contains( 'should find contact details', 'mentions checking council website for contact details' ); + } + } + }; +} + +for my $test ( + { + fields => { + em => 'test@example.com', + name => 'A name', + subject => 'A subject', + message => 'A message', + dest => 'help', + }, + }, + { + fields => { + em => 'test@example.com', + name => 'A name', + subject => 'A subject', + message => 'A message', + dest => 'feedback', + }, + }, + { + fields => { + em => 'test@example.com', + name => 'A name', + subject => 'A subject', + message => 'A message', + dest => 'from_council', + }, + }, + ) +{ + subtest 'check email sent correctly with dest field set to us' => sub { + FixMyStreet::override_config { + ALLOWED_COBRANDS => [ 'fixmystreet' ], + }, sub { + $mech->clear_emails_ok; + $mech->get_ok('/contact'); + $mech->submit_form_ok( { with_fields => $test->{fields} } ); + $mech->content_contains('Thank you for your enquiry'); + $mech->email_count_is(1); + } + }; +} + $problem_main->delete; done_testing(); diff --git a/t/app/controller/dashboard.t b/t/app/controller/dashboard.t index 307943abd..903affdcf 100644 --- a/t/app/controller/dashboard.t +++ b/t/app/controller/dashboard.t @@ -1,6 +1,7 @@ use strict; use warnings; use Test::More; +use Test::MockTime ':all'; use FixMyStreet::TestMech; use Web::Scraper; @@ -12,559 +13,613 @@ my $test_pass = 'password'; my $test_council = 2651; my $test_ward = 20723; -$mech->delete_user( $test_user ); -my $user = FixMyStreet::App->model('DB::User')->create( { - email => $test_user, - password => $test_pass, -} ); +my $body = $mech->create_body_ok($test_council, 'City of Edinburgh Council'); -my $p_user = FixMyStreet::App->model('DB::User')->find_or_create( { - email => 'p_user@example.com' -} ); +$mech->delete_user( $test_user ); +my $user = $mech->create_user_ok($test_user, password => $test_pass); -$mech->not_logged_in_ok; -$mech->get_ok('/dashboard'); +my $p_user = $mech->create_user_ok('p_user@example.com'); -$mech->content_contains( 'sign in' ); +# Dashboard tests assume we are not too early in year, to allow reporting +# within same year, as a convenience. +set_absolute_time('2014-03-01T12:00:00'); +FixMyStreet::override_config { + ALLOWED_COBRANDS => [ { fixmystreet => '.' } ], + MAPIT_URL => 'http://mapit.uk/', +}, sub { -$mech->submit_form( - with_fields => { email => $test_user, password_sign_in => $test_pass } -); + $mech->not_logged_in_ok; + $mech->get_ok('/dashboard'); -is $mech->status, '404', 'If not council user get 404'; + $mech->content_contains( 'sign in' ); -$user->from_body( $test_council ); -$user->update; + $mech->submit_form( + with_fields => { email => $test_user, password_sign_in => $test_pass } + ); -$mech->log_out_ok; -$mech->get_ok('/dashboard'); -$mech->submit_form_ok( { - with_fields => { email => $test_user, password_sign_in => $test_pass } -} ); + is $mech->status, '404', 'If not council user get 404'; -$mech->content_contains( 'City of Edinburgh' ); + $user->from_body( $body->id ); + $user->update; -FixMyStreet::App->model('DB::Contact')->search( { body_id => $test_council } ) - ->delete; + $mech->log_out_ok; + $mech->get_ok('/dashboard'); + $mech->submit_form_ok( { + with_fields => { email => $test_user, password_sign_in => $test_pass } + } ); -delete_problems(); + $mech->content_contains( 'Area 2651' ); -my @cats = qw( Grafitti Litter Potholes Other ); -for my $contact ( @cats ) { - FixMyStreet::App->model('DB::Contact')->create( - { - body_id => $test_council, - category => $contact, - email => "$contact\@example.org", - confirmed => 1, - whenedited => DateTime->now, - deleted => 0, - editor => 'test', - note => 'test', - } - ); -} - -$mech->get_ok('/dashboard'); - -my $categories = scraper { - process "select[name=category] > option", 'cats[]' => 'TEXT', - process "select[name=ward] > option", 'wards[]' => 'TEXT', - process "table[id=overview] > tr", 'rows[]' => scraper { - process 'td', 'cols[]' => 'TEXT' - }, - process "tr[id=total] > td", 'totals[]' => 'TEXT', - process "tr[id=fixed_council] > td", 'council[]' => 'TEXT', - process "tr[id=fixed_user] > td", 'user[]' => 'TEXT', - process "tr[id=total_fixed] > td", 'total_fixed[]' => 'TEXT', - process "tr[id=in_progress] > td", 'in_progress[]' => 'TEXT', - process "tr[id=planned] > td", 'planned[]' => 'TEXT', - process "tr[id=investigating] > td", 'investigating[]' => 'TEXT', - process "tr[id=marked] > td", 'marked[]' => 'TEXT', - process "tr[id=avg_marked] > td", 'avg_marked[]' => 'TEXT', - process "tr[id=avg_fixed] > td", 'avg_fixed[]' => 'TEXT', - process "tr[id=not_marked] > td", 'not_marked[]' => 'TEXT', - process "tr[id=closed] > td", 'closed[]' => 'TEXT', - process "table[id=reports] > tr > td", 'report_lists[]' => scraper { - process 'ul > li', 'reports[]' => 'TEXT' - }, -}; + FixMyStreet::App->model('DB::Contact')->search( { body_id => $body->id } ) + ->delete; -my $expected_cats = [ 'All', '-- Pick a category --', @cats ]; -my $res = $categories->scrape( $mech->content ); -is_deeply( $res->{cats}, $expected_cats, 'correct list of categories' ); + delete_problems(); -foreach my $row ( @{ $res->{rows} }[1 .. 11] ) { - foreach my $col ( @{ $row->{cols} } ) { - is $col, 0; + my @cats = qw( Grafitti Litter Potholes Other ); + for my $contact ( @cats ) { + FixMyStreet::App->model('DB::Contact')->create( + { + body_id => $body->id, + category => $contact, + email => "$contact\@example.org", + confirmed => 1, + whenedited => DateTime->now, + deleted => 0, + editor => 'test', + note => 'test', + } + ); } -} -for my $reports ( @{ $res->{report_lists} } ) { - is_deeply $reports, {}, 'No reports'; -} + $mech->get_ok('/dashboard'); -foreach my $test ( - { - desc => 'confirmed today with no state', - dt => DateTime->now, - counts => [1,1,1,1], - report_counts => [1, 0, 0], - }, - { - desc => 'confirmed last 7 days with no state', - dt => DateTime->now->subtract( days => 6, hours => 23 ), - counts => [1,2,2,2], - report_counts => [2, 0, 0], - }, - { - desc => 'confirmed last 8 days with no state', - dt => DateTime->now->subtract( days => 8 ), - counts => [1,2,3,3], - report_counts => [2, 1, 0], - }, - { - desc => 'confirmed last 4 weeks with no state', - dt => DateTime->now->subtract( weeks => 2 ), - counts => [1,2,4,4], - report_counts => [2, 1, 1], - }, - { - desc => 'confirmed this year with no state', - dt => DateTime->now->subtract( weeks => 7 ), - counts => [1,2,4,5], - report_counts => [2, 1, 1], - }, -) { - subtest $test->{desc} => sub { - make_problem( { state => 'confirmed', conf_dt => $test->{dt} } ); - - $mech->get_ok('/dashboard'); - $res = $categories->scrape( $mech->content ); - - check_row( $res, 'totals', $test->{counts} ); - check_row( $res, 'not_marked', $test->{counts} ); - - check_report_counts( $res, $test->{report_counts} ); + my $categories = scraper { + process "select[name=category] > option", 'cats[]' => 'TEXT', + process "select[name=ward] > option", 'wards[]' => 'TEXT', + process "table[id=overview] > tr", 'rows[]' => scraper { + process 'td', 'cols[]' => 'TEXT' + }, + process "tr[id=total] > td", 'totals[]' => 'TEXT', + process "tr[id=fixed_council] > td", 'council[]' => 'TEXT', + process "tr[id=fixed_user] > td", 'user[]' => 'TEXT', + process "tr[id=total_fixed] > td", 'total_fixed[]' => 'TEXT', + process "tr[id=in_progress] > td", 'in_progress[]' => 'TEXT', + process "tr[id=action_scheduled] > td", 'action_scheduled[]' => 'TEXT', + process "tr[id=investigating] > td", 'investigating[]' => 'TEXT', + process "tr[id=marked] > td", 'marked[]' => 'TEXT', + process "tr[id=avg_marked] > td", 'avg_marked[]' => 'TEXT', + process "tr[id=avg_fixed] > td", 'avg_fixed[]' => 'TEXT', + process "tr[id=not_marked] > td", 'not_marked[]' => 'TEXT', + process "tr[id=closed] > td", 'closed[]' => 'TEXT', + process "table[id=reports] > tr > td", 'report_lists[]' => scraper { + process 'ul > li', 'reports[]' => 'TEXT' + }, }; -} - -delete_problems(); - -my $is_monday = DateTime->now->day_of_week == 1 ? 1 : 0; - -foreach my $test ( - { - desc => 'user fixed today', - confirm_dt => DateTime->now->subtract( days => 1 ), - mark_dt => DateTime->now, - state => 'fixed - user', - counts => { - totals => $is_monday ? [0,1,1,1] : [1,1,1,1], - user => [1,1,1,1], - council => [0,0,0,0], - avg_fixed => [0,0,0,0], - total_fixed => [1,1,1,1], - } - }, - { - desc => 'council fixed today', - confirm_dt => DateTime->now->subtract( days => 1 ), - mark_dt => DateTime->now, - state => 'fixed - council', - counts => { - totals => $is_monday ? [0,2,2,2] : [2,2,2,2], - user => [1,1,1,1], - council => [1,1,1,1], - avg_fixed => [1,1,1,1], - total_fixed => [2,2,2,2], - } - }, - { - desc => 'marked investigating today', - confirm_dt => DateTime->now->subtract( days => 1 ), - mark_dt => DateTime->now, - state => 'investigating', - counts => { - totals => $is_monday ? [0,3,3,3] : [3,3,3,3], - user => [1,1,1,1], - council => [1,1,1,1], - total_fixed => [2,2,2,2], - avg_marked => [1,1,1,1], - investigating => [1,1,1,1], - marked => [1,1,1,1] - } - }, - { - desc => 'marked in progress today', - confirm_dt => DateTime->now->subtract( days => 1 ), - mark_dt => DateTime->now, - state => 'in progress', - counts => { - totals => $is_monday ? [0,4,4,4] : [4,4,4,4], - user => [1,1,1,1], - council => [1,1,1,1], - total_fixed => [2,2,2,2], - avg_marked => [1,1,1,1], - investigating => [1,1,1,1], - in_progress => [1,1,1,1], - marked => [2,2,2,2] - } - }, - { - desc => 'marked as planned today', - confirm_dt => DateTime->now->subtract( days => 1 ), - mark_dt => DateTime->now, - state => 'planned', - counts => { - totals => $is_monday ? [ 0,5,5,5] : [5,5,5,5], - user => [1,1,1,1], - council => [1,1,1,1], - total_fixed => [2,2,2,2], - avg_marked => [1,1,1,1], - investigating => [1,1,1,1], - in_progress => [1,1,1,1], - planned => [1,1,1,1], - marked => [3,3,3,3] - } - }, - { - desc => 'marked as planned today, confirmed a week ago', - confirm_dt => DateTime->now->subtract( days => 8 ), - mark_dt => DateTime->now, - state => 'planned', - counts => { - totals => $is_monday ? [0,5,6,6] : [5,5,6,6], - user => [1,1,1,1], - council => [1,1,1,1], - total_fixed => [2,2,2,2], - avg_marked => [3,3,3,3], - investigating => [1,1,1,1], - in_progress => [1,1,1,1], - planned => [2,2,2,2], - marked => [4,4,4,4] - } - }, - { - desc => 'marked as council fixed today, confirmed a week ago', - confirm_dt => DateTime->now->subtract( days => 8 ), - mark_dt => DateTime->now, - state => 'fixed - council', - counts => { - totals => $is_monday ? [0,5,7,7] : [5,5,7,7], - user => [1,1,1,1], - council => [2,2,2,2], - total_fixed => [3,3,3,3], - avg_fixed => [5,5,5,5], - avg_marked => [3,3,3,3], - investigating => [1,1,1,1], - in_progress => [1,1,1,1], - planned => [2,2,2,2], - marked => [4,4,4,4] - } - }, - { - desc => 'marked as council fixed a week ago, confirmed 3 weeks ago', - confirm_dt => DateTime->now->subtract( days => 21), - mark_dt => DateTime->now->subtract( days => 8 ), - state => 'fixed - council', - counts => { - totals => $is_monday ? [0,5,8,8] : [5,5,8,8], - user => [1,1,1,1], - council => [2,2,3,3], - total_fixed => [3,3,4,4], - avg_fixed => [5,5,7,7], - avg_marked => [3,3,3,3], - investigating => [1,1,1,1], - in_progress => [1,1,1,1], - planned => [2,2,2,2], - marked => [4,4,4,4] - } - }, - { - desc => 'marked as user fixed 6 weeks ago, confirmed 7 weeks ago', - confirm_dt => DateTime->now->subtract( weeks => 6 ), - mark_dt => DateTime->now->subtract( weeks => 7 ), - state => 'fixed - user', - counts => { - totals => $is_monday ? [0,5,8,9] : [5,5,8,9], - user => [1,1,1,2], - council => [2,2,3,3], - total_fixed => [3,3,4,5], - avg_fixed => [5,5,7,7], - avg_marked => [3,3,3,3], - investigating => [1,1,1,1], - in_progress => [1,1,1,1], - planned => [2,2,2,2], - marked => [4,4,4,4] - } - }, - { - desc => 'marked as closed', - confirm_dt => DateTime->now->subtract( days => 1 ), - mark_dt => DateTime->now, - state => 'closed', - counts => { - totals => $is_monday ? [0,6,9,10] : [6,6,9,10], - user => [1,1,1,2], - council => [2,2,3,3], - total_fixed => [3,3,4,5], - avg_fixed => [5,5,7,7], - avg_marked => [2,2,2,2], - investigating => [1,1,1,1], - in_progress => [1,1,1,1], - planned => [2,2,2,2], - closed => [1,1,1,1], - marked => [5,5,5,5] - } - }, -) { - subtest $test->{desc} => sub { - make_problem( - { - state => $test->{state}, - conf_dt => $test->{confirm_dt}, - mark_dt => $test->{mark_dt}, - } - ); - $mech->get_ok('/dashboard'); - $res = $categories->scrape( $mech->content ); + my $expected_cats = [ 'All', '-- Pick a category --', @cats ]; + my $res = $categories->scrape( $mech->content ); + is_deeply( $res->{cats}, $expected_cats, 'correct list of categories' ); - foreach my $row ( keys %{ $test->{counts} } ) { - check_row( $res, $row, $test->{counts}->{$row} ); + foreach my $row ( @{ $res->{rows} }[1 .. 11] ) { + foreach my $col ( @{ $row->{cols} } ) { + is $col, 0; } - }; -} + } -delete_problems(); + for my $reports ( @{ $res->{report_lists} } ) { + is_deeply $reports, {}, 'No reports'; + } -for my $test ( - { - desc => 'Selecting no category does nothing', - p1 => { - state => 'confirmed', - conf_dt => DateTime->now(), - category => 'Potholes', + my $now = DateTime->now(time_zone => 'local'); + foreach my $test ( + { + desc => 'confirmed today with no state', + dt => $now, + counts => [1,1,1,1], + report_counts => [1, 0, 0], + }, + { + desc => 'confirmed last 7 days with no state', + dt => $now->clone->subtract( days => 6, hours => 23 ), + counts => [1,2,2,2], + report_counts => [2, 0, 0], }, - p2 => { - state => 'confirmed', - conf_dt => DateTime->now(), - category => 'Litter', + { + desc => 'confirmed last 8 days with no state', + dt => $now->clone->subtract( days => 8 ), + counts => [1,2,3,3], + report_counts => [2, 1, 0], }, - category => '', - counts => { - totals => [2,2,2,2], + { + desc => 'confirmed last 2 weeks with no state', + dt => $now->clone->subtract( weeks => 2 ), + counts => [1,2,4,4], + report_counts => [2, 1, 1], }, - counts_after => { - totals => [2,2,2,2], + { + desc => 'confirmed this year with no state', + dt => $now->clone->subtract( weeks => 7 ), + counts => [1,2,4,5], + report_counts => [2, 1, 1], + }, + ) { + subtest $test->{desc} => sub { + make_problem( { state => 'confirmed', conf_dt => $test->{dt} } ); + + $mech->get_ok('/dashboard'); + $res = $categories->scrape( $mech->content ); + + check_row( $res, 'totals', $test->{counts} ); + check_row( $res, 'not_marked', $test->{counts} ); + + check_report_counts( $res, $test->{report_counts} ); + }; + } + + delete_problems(); + + my $is_monday = DateTime->now->day_of_week == 1 ? 1 : 0; + + foreach my $test ( + { + desc => 'user fixed today', + confirm_dt => DateTime->now->subtract( days => 1 ), + mark_dt => DateTime->now, + state => 'fixed - user', + counts => { + totals => $is_monday ? [0,1,1,1] : [1,1,1,1], + user => [1,1,1,1], + council => [0,0,0,0], + avg_fixed => [0,0,0,0], + total_fixed => [1,1,1,1], + } }, - report_counts => [2,0,0], - report_counts_after => [2,0,0], - }, - { - desc => 'Limit display by category', - category => 'Potholes', - counts => { - totals => [2,2,2,2], + { + desc => 'council fixed today', + confirm_dt => DateTime->now->subtract( days => 1 ), + mark_dt => DateTime->now, + state => 'fixed - council', + counts => { + totals => $is_monday ? [0,2,2,2] : [2,2,2,2], + user => [1,1,1,1], + council => [1,1,1,1], + avg_fixed => [1,1,1,1], + total_fixed => [2,2,2,2], + } }, - counts_after => { - totals => [1,1,1,1], + { + desc => 'marked investigating today', + confirm_dt => DateTime->now->subtract( days => 1 ), + mark_dt => DateTime->now, + state => 'investigating', + counts => { + totals => $is_monday ? [0,3,3,3] : [3,3,3,3], + user => [1,1,1,1], + council => [1,1,1,1], + total_fixed => [2,2,2,2], + avg_marked => [1,1,1,1], + investigating => [1,1,1,1], + marked => [1,1,1,1] + } }, - report_counts => [2,0,0], - report_counts_after => [1,0,0], - }, - { - desc => 'Limit display for category with no entries', - category => 'Grafitti', - counts => { - totals => [2,2,2,2], + { + desc => 'marked in progress today', + confirm_dt => DateTime->now->subtract( days => 1 ), + mark_dt => DateTime->now, + state => 'in progress', + counts => { + totals => $is_monday ? [0,4,4,4] : [4,4,4,4], + user => [1,1,1,1], + council => [1,1,1,1], + total_fixed => [2,2,2,2], + avg_marked => [1,1,1,1], + investigating => [1,1,1,1], + in_progress => [1,1,1,1], + marked => [2,2,2,2] + } }, - counts_after => { - totals => [0,0,0,0], + { + desc => 'marked as action scheduled today', + confirm_dt => DateTime->now->subtract( days => 1 ), + mark_dt => DateTime->now, + state => 'action scheduled', + counts => { + totals => $is_monday ? [ 0,5,5,5] : [5,5,5,5], + user => [1,1,1,1], + council => [1,1,1,1], + total_fixed => [2,2,2,2], + avg_marked => [1,1,1,1], + investigating => [1,1,1,1], + in_progress => [1,1,1,1], + action_scheduled => [1,1,1,1], + marked => [3,3,3,3] + } }, - report_counts => [2,0,0], - report_counts_after => [0,0,0], - }, - { - desc => 'Limit display by category for council fixed', - p1 => { - state => 'fixed - council', - conf_dt => DateTime->now()->subtract( weeks => 1 ), - mark_dt => DateTime->now()->subtract( weeks => 1 ), - category => 'Potholes', + { + desc => 'marked as action scheduled today, confirmed a week ago', + confirm_dt => DateTime->now->subtract( days => 8 ), + mark_dt => DateTime->now, + state => 'action scheduled', + counts => { + totals => $is_monday ? [0,5,6,6] : [5,5,6,6], + user => [1,1,1,1], + council => [1,1,1,1], + total_fixed => [2,2,2,2], + avg_marked => [3,3,3,3], + investigating => [1,1,1,1], + in_progress => [1,1,1,1], + action_scheduled => [2,2,2,2], + marked => [4,4,4,4] + } }, - p2 => { - state => 'fixed - council', - conf_dt => DateTime->now()->subtract( weeks => 1 ), - mark_dt => DateTime->now()->subtract( weeks => 1 ), - category => 'Litter', + { + desc => 'marked as council fixed today, confirmed a week ago', + confirm_dt => DateTime->now->subtract( days => 8 ), + mark_dt => DateTime->now, + state => 'fixed - council', + counts => { + totals => $is_monday ? [0,5,7,7] : [5,5,7,7], + user => [1,1,1,1], + council => [2,2,2,2], + total_fixed => [3,3,3,3], + avg_fixed => [5,5,5,5], + avg_marked => [3,3,3,3], + investigating => [1,1,1,1], + in_progress => [1,1,1,1], + action_scheduled => [2,2,2,2], + marked => [4,4,4,4] + } }, - category => 'Potholes', - counts => { - council => [0,0,2,2], - totals => [2,2,4,4], + { + desc => 'marked as council fixed a week ago, confirmed 3 weeks ago', + confirm_dt => DateTime->now->subtract( days => 21), + mark_dt => DateTime->now->subtract( days => 8 ), + state => 'fixed - council', + counts => { + totals => $is_monday ? [0,5,8,8] : [5,5,8,8], + user => [1,1,1,1], + council => [2,2,3,3], + total_fixed => [3,3,4,4], + avg_fixed => [5,5,7,7], + avg_marked => [3,3,3,3], + investigating => [1,1,1,1], + in_progress => [1,1,1,1], + action_scheduled => [2,2,2,2], + marked => [4,4,4,4] + } }, - counts_after => { - council => [0,0,1,1], - totals => [1,1,2,2], + { + desc => 'marked as user fixed 6 weeks ago, confirmed 7 weeks ago', + confirm_dt => DateTime->now->subtract( weeks => 6 ), + mark_dt => DateTime->now->subtract( weeks => 7 ), + state => 'fixed - user', + counts => { + totals => $is_monday ? [0,5,8,9] : [5,5,8,9], + user => [1,1,1,2], + council => [2,2,3,3], + total_fixed => [3,3,4,5], + avg_fixed => [5,5,7,7], + avg_marked => [3,3,3,3], + investigating => [1,1,1,1], + in_progress => [1,1,1,1], + action_scheduled => [2,2,2,2], + marked => [4,4,4,4] + } }, - report_counts => [2,2,0], - report_counts_after => [1,1,0], - }, - { - desc => 'Limit display by category for user fixed', - p1 => { - state => 'fixed - user', - conf_dt => DateTime->now()->subtract( weeks => 1 ), - mark_dt => DateTime->now()->subtract( weeks => 1 ), - category => 'Potholes', + { + desc => 'marked as closed', + confirm_dt => DateTime->now->subtract( days => 1 ), + mark_dt => DateTime->now, + state => 'closed', + counts => { + totals => $is_monday ? [0,6,9,10] : [6,6,9,10], + user => [1,1,1,2], + council => [2,2,3,3], + total_fixed => [3,3,4,5], + avg_fixed => [5,5,7,7], + avg_marked => [2,2,2,2], + investigating => [1,1,1,1], + in_progress => [1,1,1,1], + action_scheduled => [2,2,2,2], + closed => [1,1,1,1], + marked => [5,5,5,5] + } }, - p2 => { - state => 'fixed - user', - conf_dt => DateTime->now()->subtract( weeks => 1 ), - mark_dt => DateTime->now()->subtract( weeks => 1 ), - category => 'Litter', + { + desc => 'marked as planned', + confirm_dt => DateTime->now->subtract( days => 1 ), + mark_dt => DateTime->now, + state => 'planned', + counts => { + totals => $is_monday ? [0,7,10,11] : [7,7,10,11], + user => [1,1,1,2], + council => [2,2,3,3], + total_fixed => [3,3,4,5], + avg_fixed => [5,5,7,7], + avg_marked => [2,2,2,2], + investigating => [1,1,1,1], + in_progress => [1,1,1,1], + action_scheduled => [3,3,3,3], + closed => [1,1,1,1], + marked => [6,6,6,6] + } }, - category => 'Potholes', - counts => { - user => [0,0,2,2], - council => [0,0,2,2], - totals => [2,2,6,6], + ) { + subtest $test->{desc} => sub { + make_problem( + { + state => $test->{state}, + conf_dt => $test->{confirm_dt}, + mark_dt => $test->{mark_dt}, + } + ); + + $mech->get_ok('/dashboard'); + $res = $categories->scrape( $mech->content ); + + foreach my $row ( keys %{ $test->{counts} } ) { + check_row( $res, $row, $test->{counts}->{$row} ); + } + }; + } + + delete_problems(); + + for my $test ( + { + desc => 'Selecting no category does nothing', + p1 => { + state => 'confirmed', + conf_dt => DateTime->now(), + category => 'Potholes', + }, + p2 => { + state => 'confirmed', + conf_dt => DateTime->now(), + category => 'Litter', + }, + category => '', + counts => { + totals => [2,2,2,2], + }, + counts_after => { + totals => [2,2,2,2], + }, + report_counts => [2,0,0], + report_counts_after => [2,0,0], }, - counts_after => { - user => [0,0,1,1], - council => [0,0,1,1], - totals => [1,1,3,3], + { + desc => 'Limit display by category', + category => 'Potholes', + counts => { + totals => [2,2,2,2], + }, + counts_after => { + totals => [1,1,1,1], + }, + report_counts => [2,0,0], + report_counts_after => [1,0,0], }, - report_counts => [2,4,0], - report_counts_after => [1,2,0], - }, - { - desc => 'Limit display by ward', - p1 => { - state => 'confirmed', - conf_dt => DateTime->now()->subtract( weeks => 1 ), - category => 'Potholes', - # in real life it has commas around it and the search - # uses them - areas => ',20720,', + { + desc => 'Limit display for category with no entries', + category => 'Grafitti', + counts => { + totals => [2,2,2,2], + }, + counts_after => { + totals => [0,0,0,0], + }, + report_counts => [2,0,0], + report_counts_after => [0,0,0], }, - p2 => { - state => 'fixed - council', - conf_dt => DateTime->now()->subtract( weeks => 1 ), - mark_dt => DateTime->now()->subtract( weeks => 1 ), - category => 'Litter', - areas => ',20720,', + { + desc => 'Limit display by category for council fixed', + p1 => { + state => 'fixed - council', + conf_dt => DateTime->now()->subtract( weeks => 1 ), + mark_dt => DateTime->now()->subtract( weeks => 1 ), + category => 'Potholes', + }, + p2 => { + state => 'fixed - council', + conf_dt => DateTime->now()->subtract( weeks => 1 ), + mark_dt => DateTime->now()->subtract( weeks => 1 ), + category => 'Litter', + }, + category => 'Potholes', + counts => { + council => [0,0,2,2], + totals => [2,2,4,4], + }, + counts_after => { + council => [0,0,1,1], + totals => [1,1,2,2], + }, + report_counts => [2,2,0], + report_counts_after => [1,1,0], }, - ward => 20720, - counts => { - user => [0,0,2,2], - council => [0,0,3,3], - totals => [2,2,8,8], + { + desc => 'Limit display by category for user fixed', + p1 => { + state => 'fixed - user', + conf_dt => DateTime->now()->subtract( weeks => 1 ), + mark_dt => DateTime->now()->subtract( weeks => 1 ), + category => 'Potholes', + }, + p2 => { + state => 'fixed - user', + conf_dt => DateTime->now()->subtract( weeks => 1 ), + mark_dt => DateTime->now()->subtract( weeks => 1 ), + category => 'Litter', + }, + category => 'Potholes', + counts => { + user => [0,0,2,2], + council => [0,0,2,2], + totals => [2,2,6,6], + }, + counts_after => { + user => [0,0,1,1], + council => [0,0,1,1], + totals => [1,1,3,3], + }, + report_counts => [2,4,0], + report_counts_after => [1,2,0], }, - counts_after => { - user => [0,0,0,0], - council => [0,0,1,1], - totals => [0,0,2,2], + { + desc => 'Limit display by ward', + p1 => { + state => 'confirmed', + conf_dt => DateTime->now()->subtract( weeks => 1 ), + category => 'Potholes', + # in real life it has commas around it and the search + # uses them + areas => ',20720,', + }, + p2 => { + state => 'fixed - council', + conf_dt => DateTime->now()->subtract( weeks => 1 ), + mark_dt => DateTime->now()->subtract( weeks => 1 ), + category => 'Litter', + areas => ',20720,', + }, + ward => 20720, + counts => { + user => [0,0,2,2], + council => [0,0,3,3], + totals => [2,2,8,8], + }, + counts_after => { + user => [0,0,0,0], + council => [0,0,1,1], + totals => [0,0,2,2], + }, + report_counts => [2,6,0], + report_counts_after => [0,2,0], }, - report_counts => [2,6,0], - report_counts_after => [0,2,0], - }, -) { - subtest $test->{desc} => sub { - make_problem( $test->{p1} ) if $test->{p1}; - make_problem( $test->{p2} ) if $test->{p2}; + ) { + subtest $test->{desc} => sub { + make_problem( $test->{p1} ) if $test->{p1}; + make_problem( $test->{p2} ) if $test->{p2}; - $mech->get_ok('/dashboard'); + $mech->get_ok('/dashboard'); - $res = $categories->scrape( $mech->content ); + $res = $categories->scrape( $mech->content ); - foreach my $row ( keys %{ $test->{counts} } ) { - check_row( $res, $row, $test->{counts}->{$row} ); - } + foreach my $row ( keys %{ $test->{counts} } ) { + check_row( $res, $row, $test->{counts}->{$row} ); + } - check_report_counts( $res, $test->{report_counts} ); + check_report_counts( $res, $test->{report_counts} ); - $mech->submit_form_ok( { - with_fields => { - category => $test->{category}, - ward => $test->{ward}, - } - } ); + $mech->submit_form_ok( { + with_fields => { + category => $test->{category}, + ward => $test->{ward}, + } + } ); - $res = $categories->scrape( $mech->content ); + $res = $categories->scrape( $mech->content ); - foreach my $row ( keys %{ $test->{counts_after} } ) { - check_row( $res, $row, $test->{counts_after}->{$row} ); - } - check_report_counts( $res, $test->{report_counts_after} ); - }; -} + foreach my $row ( keys %{ $test->{counts_after} } ) { + check_row( $res, $row, $test->{counts_after}->{$row} ); + } + check_report_counts( $res, $test->{report_counts_after} ); + }; + } -delete_problems(); + delete_problems(); -for my $test ( - { - desc => 'Selecting no state does nothing', - p1 => { - state => 'fixed - user', - conf_dt => DateTime->now(), - category => 'Potholes', + for my $test ( + { + desc => 'Selecting no state does nothing', + p1 => { + state => 'fixed - user', + conf_dt => DateTime->now(), + category => 'Potholes', + }, + p2 => { + state => 'confirmed', + conf_dt => DateTime->now(), + category => 'Litter', + }, + state => '', + report_counts => [2,0,0], + report_counts_after => [2,0,0], }, - p2 => { - state => 'confirmed', - conf_dt => DateTime->now(), - category => 'Litter', + { + desc => 'limit by state works', + state => 'fixed', + report_counts => [2,0,0], + report_counts_after => [1,0,0], }, - state => '', - report_counts => [2,0,0], - report_counts_after => [2,0,0], - }, - { - desc => 'limit by state works', - state => 'fixed', - report_counts => [2,0,0], - report_counts_after => [1,0,0], - }, - { - desc => 'All fixed states count as fixed', - p1 => { - state => 'fixed - council', - conf_dt => DateTime->now(), - category => 'Potholes', + { + desc => 'planned counted as action scheduled', + p1 => { + state => 'planned', + conf_dt => DateTime->now(), + category => 'Potholes', + }, + state => 'action scheduled', + report_counts => [3,0,0], + report_counts_after => [1,0,0], }, - p2 => { - state => 'fixed', - conf_dt => DateTime->now(), - category => 'Potholes', + { + desc => 'All fixed states count as fixed', + p1 => { + state => 'fixed - council', + conf_dt => DateTime->now(), + category => 'Potholes', + }, + p2 => { + state => 'fixed', + conf_dt => DateTime->now(), + category => 'Potholes', + }, + state => 'fixed', + report_counts => [5,0,0], + report_counts_after => [3,0,0], }, - state => 'fixed', - report_counts => [4,0,0], - report_counts_after => [3,0,0], - }, -) { - subtest $test->{desc} => sub { - make_problem( $test->{p1} ) if $test->{p1}; - make_problem( $test->{p2} ) if $test->{p2}; + ) { + subtest $test->{desc} => sub { + make_problem( $test->{p1} ) if $test->{p1}; + make_problem( $test->{p2} ) if $test->{p2}; - $mech->get_ok('/dashboard'); + $mech->get_ok('/dashboard'); - $res = $categories->scrape( $mech->content ); + $res = $categories->scrape( $mech->content ); - check_report_counts( $res, $test->{report_counts} ); + check_report_counts( $res, $test->{report_counts} ); - $mech->submit_form_ok( { - with_fields => { - state => $test->{state}, - } - } ); + $mech->submit_form_ok( { + with_fields => { + state => $test->{state}, + } + } ); - $res = $categories->scrape( $mech->content ); + $res = $categories->scrape( $mech->content ); - check_report_counts( $res, $test->{report_counts_after} ); + check_report_counts( $res, $test->{report_counts_after} ); + }; + } + + subtest 'export as csv' => sub { + make_problem( { + detail => "this report\nis split across\nseveral lines", + state => "confirmed", + conf_dt => DateTime->now(), + } ); + $mech->get_ok('/dashboard?export=1'); + open my $data_handle, '<', \$mech->content; + my $csv = Text::CSV->new( { binary => 1 } ); + my @rows; + while ( my $row = $csv->getline( $data_handle ) ) { + push @rows, $row; + } + is scalar @rows, 7, '1 (header) + 6 (reports) = 7 lines'; }; -} +}; +restore_time; sub make_problem { my $args = shift; @@ -573,12 +628,12 @@ sub make_problem { title => 'a problem', name => 'a user', anonymous => 1, - detail => 'some detail', + detail => $args->{detail} || 'some detail', state => $args->{state}, confirmed => $args->{conf_dt}, whensent => $args->{conf_dt}, lastupdate => $args->{mark_dt} || $args->{conf_dt}, - council => $test_council, + bodies_str => $body->id, postcode => 'EH99 1SP', latitude => '51', longitude => '1', @@ -632,10 +687,10 @@ sub check_report_counts { sub delete_problems { FixMyStreet::App->model('DB::Comment') - ->search( { 'problem.council' => $test_council }, { join => 'problem' } ) + ->search( { 'problem.bodies_str' => $body->id }, { join => 'problem' } ) ->delete; FixMyStreet::App->model('DB::Problem') - ->search( { council => $test_council } )->delete(); + ->search( { bodies_str => $body->id } )->delete(); } done_testing; diff --git a/t/app/controller/index.t b/t/app/controller/index.t index 462b21064..6b28a03d2 100644 --- a/t/app/controller/index.t +++ b/t/app/controller/index.t @@ -12,7 +12,6 @@ subtest "check that the form goes to /around" => sub { $mech->get_ok('/'); is $mech->uri->path, '/', "still on '/'"; - # submit form $mech->submit_form_ok( { with_fields => { pc => 'SW1A 1AA', } } ); # check that we are at /around @@ -47,7 +46,11 @@ subtest "does pc, (x,y), (e,n) or (lat,lon) go to /around" => sub { $uri->query_form( $test->{in} ); # get the uri and check for 302 - $mech->get_ok($uri); + FixMyStreet::override_config { + MAPIT_URL => 'http://mapit.uk/', + }, sub { + $mech->get_ok($uri); + }; # check that we are at /around is $mech->uri->path, '/around', "Got to /around"; @@ -55,12 +58,10 @@ subtest "does pc, (x,y), (e,n) or (lat,lon) go to /around" => sub { } }; -$mech->delete_problems_for_council( 2651 ); - my $problem_rs = FixMyStreet::App->model('DB::Problem'); my $num = $problem_rs->count; -my @edinburgh_problems = $mech->create_problems_for_council(5, 2651, 'Front page'); +my @edinburgh_problems = $mech->create_problems_for_body(5, 2651, 'Front page'); is scalar @edinburgh_problems, 5, 'correct number of edinburgh problems created'; $mech->get_ok('/report/' . $edinburgh_problems[2]->id); @@ -74,4 +75,24 @@ ok $mech->get('/report/' . $edinburgh_problems[2]->id); is $mech->res->code, 403, 'page forbidden'; is $problem_rs->count, $num+5; -done_testing(); +my $oxon = $mech->create_body_ok(2237, 'Oxfordshire County Council', id => 2237); +subtest "prefilters /around if user has categories" => sub { + my $user = $mech->log_in_ok('test@example.com'); + my $categories = [ + $mech->create_contact_ok( body_id => $oxon->id, category => 'Cows', email => 'cows@example.net' )->id, + $mech->create_contact_ok( body_id => $oxon->id, category => 'Potholes', email => 'potholes@example.net' )->id, + ]; + $user->from_body($oxon); + $user->set_extra_metadata('categories', $categories); + $user->update; + + $mech->get_ok('/'); + # NB can't use visible_form_values because categories field is hidden + $mech->content_contains("Cows,Potholes"); +}; + +END { + $mech->delete_problems_for_body( 2651 ); + $mech->delete_body($oxon); + done_testing(); +} diff --git a/t/app/controller/json.t b/t/app/controller/json.t index 468fa5b31..b2cea674f 100644 --- a/t/app/controller/json.t +++ b/t/app/controller/json.t @@ -45,9 +45,11 @@ is_deeply # # put an entry in the database for this test my $user = $mech->create_user_ok('test@example.com'); +my $body = $mech->create_body_ok(2501, 'Wandsworth Borough Council'); + my $problem_args = { postcode => 'sw1a 1aa', - council => '2501', + bodies_str => $body->id, areas => ',105164,11806,11827,2247,2501,34817,42011,66045,70786,8519,', category => 'test category', title => 'Test title', @@ -86,7 +88,7 @@ is_deeply # 'category' => 'test category', 'confirmed' => '2000-01-01 12:01:00', 'lastupdate' => '2000-01-01 12:00:00', - 'council' => 'Wandsworth Borough Council', + 'bodies_str' => 'Wandsworth Borough Council', 'detail' => 'Test detail', 'id' => $problem->id, 'name' => 'Test Name', @@ -103,7 +105,7 @@ is_deeply # 'category' => 'test category', 'confirmed' => '2000-01-01 12:02:00', 'lastupdate' => '2000-01-01 12:00:00', - 'council' => 'Wandsworth Borough Council', + 'bodies_str' => 'Wandsworth Borough Council', 'detail' => 'Test detail', 'id' => $anon_problem->id, 'name' => '', diff --git a/t/app/controller/moderate.t b/t/app/controller/moderate.t new file mode 100644 index 000000000..10287ab22 --- /dev/null +++ b/t/app/controller/moderate.t @@ -0,0 +1,358 @@ +use strict; +use warnings; +use Test::More; +use utf8; + +use FixMyStreet::TestMech; +use FixMyStreet::App; +use Data::Dumper; + +my $mech = FixMyStreet::TestMech->new; +$mech->host('www.example.org'); + +my $BROMLEY_ID = 2482; +my $body = $mech->create_body_ok( $BROMLEY_ID, 'Bromley Council' ); + +my $dt = DateTime->now; + +my $user = $mech->create_user_ok('test-moderation@example.com', name => 'Test User'); +$user->user_body_permissions->delete_all; +$user->discard_changes; + +my $user2 = $mech->create_user_ok('test-moderation2@example.com', name => 'Test User 2'); + +sub create_report { + FixMyStreet::App->model('DB::Problem')->create( + { + postcode => 'BR1 3SB', + bodies_str => $body->id, + areas => ",$BROMLEY_ID,", + category => 'Other', + title => 'Good bad good', + detail => 'Good bad bad bad good bad', + used_map => 't', + name => 'Test User 2', + anonymous => 'f', + state => 'confirmed', + confirmed => $dt->ymd . ' ' . $dt->hms, + lang => 'en-gb', + service => '', + cobrand => 'default', + cobrand_data => '', + send_questionnaire => 't', + latitude => '51.4129', + longitude => '0.007831', + user_id => $user2->id, + photo => $mech->get_photo_data, + }); +} +my $report = create_report(); +my $report2 = create_report(); + +my $REPORT_URL = '/report/' . $report->id ; + +subtest 'Auth' => sub { + + subtest 'Unaffiliated user cannot see moderation' => sub { + $mech->get_ok($REPORT_URL); + $mech->content_lacks('Moderat'); + + $mech->log_in_ok( $user->email ); + + $mech->get_ok($REPORT_URL); + $mech->content_lacks('Moderat'); + + $user->update({ from_body => $body->id }); + + $mech->get_ok($REPORT_URL); + $mech->content_lacks('Moderat'); + + $mech->get('/contact?m=1&id=' . $report->id); + is $mech->res->code, 400; + $mech->content_lacks('Good bad bad bad'); + }; + + subtest 'Affiliated and permissioned user can see moderation' => sub { + # login and from_body are done in previous test. + $user->user_body_permissions->create({ + body => $body, + permission_type => 'moderate', + }); + + $mech->get_ok($REPORT_URL); + $mech->content_contains('Moderat'); + }; +}; + +my %problem_prepopulated = ( + problem_show_name => 1, + problem_show_photo => 1, + problem_title => 'Good bad good', + problem_detail => 'Good bad bad bad good bad', +); + +subtest 'Problem moderation' => sub { + + subtest 'Post modify title and text' => sub { + $mech->get_ok($REPORT_URL); + $mech->submit_form_ok({ with_fields => { + %problem_prepopulated, + problem_title => 'Good good', + problem_detail => 'Good good improved', + }}); + $mech->base_like( qr{\Q$REPORT_URL\E} ); + $mech->content_like(qr/Moderated by Bromley Council/); + + $report->discard_changes; + is $report->title, 'Good [...] good'; + is $report->detail, 'Good [...] good [...]improved'; + }; + + subtest 'Revert title and text' => sub { + $mech->submit_form_ok({ with_fields => { + %problem_prepopulated, + problem_revert_title => 1, + problem_revert_detail => 1, + }}); + $mech->base_like( qr{\Q$REPORT_URL\E} ); + + $report->discard_changes; + is $report->title, 'Good bad good'; + is $report->detail, 'Good bad bad bad good bad'; + }; + + subtest 'Make anonymous' => sub { + $mech->content_lacks('Reported anonymously'); + + $mech->submit_form_ok({ with_fields => { + %problem_prepopulated, + problem_show_name => 0, + }}); + $mech->base_like( qr{\Q$REPORT_URL\E} ); + + $mech->content_contains('Reported anonymously'); + + $mech->submit_form_ok({ with_fields => { + %problem_prepopulated, + problem_show_name => 1, + }}); + $mech->base_like( qr{\Q$REPORT_URL\E} ); + + $mech->content_lacks('Reported anonymously'); + }; + + subtest 'Hide photo' => sub { + $mech->content_contains('Photo of this report'); + + $mech->submit_form_ok({ with_fields => { + %problem_prepopulated, + problem_show_photo => 0, + }}); + $mech->base_like( qr{\Q$REPORT_URL\E} ); + + $mech->content_lacks('Photo of this report'); + + $mech->submit_form_ok({ with_fields => { + %problem_prepopulated, + problem_show_photo => 1, + }}); + $mech->base_like( qr{\Q$REPORT_URL\E} ); + + $mech->content_contains('Photo of this report'); + }; + + subtest 'Hide report' => sub { + $mech->clear_emails_ok; + + $mech->submit_form_ok({ with_fields => { + %problem_prepopulated, + problem_hide => 1, + }}); + $mech->base_unlike( qr{/report/}, 'redirected to front page' ); + + $report->discard_changes; + is $report->state, 'hidden', 'Is hidden'; + + my $email = $mech->get_email; + is $email->header('To'), '"Test User 2" <test-moderation2@example.com>', 'Sent to correct email'; + my $url = $mech->get_link_from_email($email); + ok $url, "extracted complain url '$url'"; + + $mech->get_ok($url); + $mech->content_contains('Good bad bad bad'); + + # reset + $report->update({ state => 'confirmed' }); + }; +}; + +$mech->content_lacks('Posted anonymously', 'sanity check'); + +subtest 'Problem 2' => sub { + my $REPORT2_URL = '/report/' . $report2->id ; + $mech->get_ok($REPORT2_URL); + $mech->submit_form_ok({ with_fields => { + %problem_prepopulated, + problem_title => 'Good good', + problem_detail => 'Good good improved', + }}); + $mech->base_like( qr{\Q$REPORT2_URL\E} ); + + $report2->discard_changes; + is $report2->title, 'Good [...] good'; + is $report2->detail, 'Good [...] good [...]improved'; + + $mech->submit_form_ok({ with_fields => { + %problem_prepopulated, + problem_revert_title => 1, + problem_revert_detail => 1, + }}); + $mech->base_like( qr{\Q$REPORT2_URL\E} ); + + $report2->discard_changes; + is $report2->title, 'Good bad good'; + is $report2->detail, 'Good bad bad bad good bad'; +}; + +sub create_update { + $report->comments->create({ + user => $user2, + name => 'Test User 2', + anonymous => 'f', + photo => $mech->get_photo_data, + text => 'update good good bad good', + state => 'confirmed', + mark_fixed => 0, + }); +} +my %update_prepopulated = ( + update_show_name => 1, + update_show_photo => 1, + update_detail => 'update good good bad good', +); + +my $update = create_update(); + +subtest 'updates' => sub { + + subtest 'Update modify text' => sub { + $mech->get_ok($REPORT_URL); + $mech->submit_form_ok({ with_fields => { + %update_prepopulated, + update_detail => 'update good good good', + }}) or die $mech->content; + $mech->base_like( qr{\Q$REPORT_URL\E} ); + + $update->discard_changes; + is $update->text, 'update good good [...] good', + }; + + subtest 'Revert text' => sub { + $mech->submit_form_ok({ with_fields => { + %update_prepopulated, + update_revert_detail => 1, + }}); + $mech->base_like( qr{\Q$REPORT_URL\E} ); + + $update->discard_changes; + $update->discard_changes; + is $update->text, 'update good good bad good', + }; + + subtest 'Make anonymous' => sub { + $mech->content_lacks('Posted anonymously') + or die sprintf '%d (%d)', $update->id, $report->comments->count; + + $mech->submit_form_ok({ with_fields => { + %update_prepopulated, + update_show_name => 0, + }}); + $mech->base_like( qr{\Q$REPORT_URL\E} ); + + $mech->content_contains('Posted anonymously'); + + $mech->submit_form_ok({ with_fields => { + %update_prepopulated, + update_show_name => 1, + }}); + $mech->base_like( qr{\Q$REPORT_URL\E} ); + + $mech->content_lacks('Posted anonymously'); + }; + + subtest 'Hide photo' => sub { + $report->update({ photo => undef }); # hide the main photo so we can just look for text in comment + + $mech->get_ok($REPORT_URL); + + $mech->content_contains('Photo of this report') + or die $mech->content; + + $mech->submit_form_ok({ with_fields => { + %update_prepopulated, + update_show_photo => 0, + }}); + $mech->base_like( qr{\Q$REPORT_URL\E} ); + + $mech->content_lacks('Photo of this report'); + + $mech->submit_form_ok({ with_fields => { + %update_prepopulated, + update_show_photo => 1, + }}); + $mech->base_like( qr{\Q$REPORT_URL\E} ); + + $mech->content_contains('Photo of this report'); + }; + + subtest 'Hide comment' => sub { + $mech->content_contains('update good good bad good'); + + $mech->submit_form_ok({ with_fields => { + %update_prepopulated, + update_hide => 1, + }}); + $mech->content_lacks('update good good bad good'); + }; + + $update->moderation_original_data->delete; +}; + +my $update2 = create_update(); + +subtest 'Update 2' => sub { + $mech->get_ok($REPORT_URL); + $mech->submit_form_ok({ with_fields => { + %update_prepopulated, + update_detail => 'update good good good', + }}) or die $mech->content; + + $update2->discard_changes; + is $update2->text, 'update good good [...] good', +}; + +subtest 'Now stop being a staff user' => sub { + $user->update({ from_body => undef }); + $mech->get_ok($REPORT_URL); + $mech->content_contains('Moderated by Bromley Council'); +}; + +subtest 'And do it as a superuser' => sub { + $user->update({ is_superuser => 1 }); + $mech->get_ok($REPORT_URL); + $mech->submit_form_ok({ with_fields => { + %problem_prepopulated, + problem_title => 'Good good', + problem_detail => 'Good good improved', + }}); + $mech->content_contains('Moderated by a FixMyStreet administrator'); +}; + +$update->delete; +$update2->delete; +$report->moderation_original_data->delete; +$report->delete; +$report2->delete; +$mech->delete_user($user); + +done_testing(); diff --git a/t/app/controller/my.t b/t/app/controller/my.t index da509e8ed..00070ed81 100644 --- a/t/app/controller/my.t +++ b/t/app/controller/my.t @@ -1,7 +1,7 @@ use strict; use warnings; -use Test::More tests => 11; +use Test::More; use FixMyStreet::TestMech; my $mech = FixMyStreet::TestMech->new; @@ -9,11 +9,20 @@ my $mech = FixMyStreet::TestMech->new; $mech->get_ok('/my'); is $mech->uri->path, '/auth', "got sent to the sign in page"; -# sign in +$mech->create_problems_for_body(1, 1234, 'Test Title'); +my $other_user = FixMyStreet::DB->resultset('User')->find_or_create({ email => 'another@example.com' }); +$mech->create_problems_for_body(1, 1234, 'Another Title', { user => $other_user }); + my $user = $mech->log_in_ok( 'test@example.com' ); $mech->get_ok('/my'); -is $mech->uri->path, '/my', "stayed on '/my/' page"; +is $mech->uri->path, '/my', "stayed on '/my' page"; + +$mech->content_contains('Test Title'); +$mech->content_lacks('Another Title'); -# cleanup -$mech->delete_user( $user ); +done_testing(); +END { + $mech->delete_user($user); + $mech->delete_user($other_user); +} diff --git a/t/app/controller/my_planned.t b/t/app/controller/my_planned.t new file mode 100644 index 000000000..fa463e61e --- /dev/null +++ b/t/app/controller/my_planned.t @@ -0,0 +1,64 @@ +use strict; +use warnings; + +use Test::More; + +use FixMyStreet::TestMech; +my $mech = FixMyStreet::TestMech->new; + +$mech->get_ok('/my/planned'); +is $mech->uri->path, '/auth', "got sent to the sign in page"; + +my $body = $mech->create_body_ok(2237, 'Oxfordshire'); +my ($problem) = $mech->create_problems_for_body(1, $body->id, 'Test Title'); + +$mech->get_ok($problem->url); +$mech->content_lacks('Shortlist'); +$mech->content_lacks('Shortlisted'); + +my $user = $mech->log_in_ok( 'test@example.com' ); +$user->update({ from_body => $body }); +$user->user_body_permissions->find_or_create({ + body => $body, + permission_type => 'planned_reports', +}); + +$mech->get_ok('/my/planned'); +$mech->content_lacks('Test Title'); + +$user->add_to_planned_reports($problem); +$mech->get_ok('/my/planned'); +$mech->content_contains('Test Title'); + +$user->remove_from_planned_reports($problem); +$mech->get_ok('/my/planned'); +$mech->content_lacks('Test Title'); + +$user->add_to_planned_reports($problem); +$mech->get_ok('/my/planned'); +$mech->content_contains('Test Title'); + +$mech->get_ok($problem->url); +$mech->content_contains('Shortlisted'); +$mech->submit_form_ok({ with_fields => { 'shortlist-remove' => 1 } }); +$mech->content_contains('Shortlist'); +$mech->submit_form_ok({ with_fields => { 'shortlist-add' => 1 } }); +$mech->content_contains('Shortlisted'); + +$mech->get_ok('/my/planned?sort=shortlist&ajax=1'); +$mech->content_contains('shortlist-up'); +$mech->content_contains('shortlist-down'); + +$mech->get_ok('/my/planned?sort=created-desc&ajax=1'); +$mech->content_lacks('shortlist-up'); +$mech->content_lacks('shortlist-down'); + +$mech->get_ok('/my/planned?ajax=1'); +$mech->content_contains('shortlist-up'); +$mech->content_contains('shortlist-down'); + +done_testing(); + +END { + $mech->delete_user($user); +} diff --git a/t/app/controller/page_not_found.t b/t/app/controller/page_not_found.t index 05e983109..3c2bc3c3d 100644 --- a/t/app/controller/page_not_found.t +++ b/t/app/controller/page_not_found.t @@ -1,5 +1,3 @@ -#!/usr/bin/perl - use strict; use warnings; diff --git a/t/app/controller/photo.t b/t/app/controller/photo.t new file mode 100644 index 000000000..ad857b5e3 --- /dev/null +++ b/t/app/controller/photo.t @@ -0,0 +1,79 @@ +use strict; +use utf8; # sign in error message has – in it +use warnings; +use feature 'say'; +use Test::More; +use utf8; + +use FixMyStreet::TestMech; +use FixMyStreet::App; +use Web::Scraper; +use Path::Tiny; +use File::Temp 'tempdir'; + +# disable info logs for this test run +FixMyStreet::App->log->disable('info'); +END { FixMyStreet::App->log->enable('info'); } + +my $mech = FixMyStreet::TestMech->new; + +my $sample_file = path(__FILE__)->parent->child("sample.jpg"); +ok $sample_file->exists, "sample file $sample_file exists"; + +my $westminster = $mech->create_body_ok(2527, 'Liverpool City Council'); + +subtest "Check multiple upload worked" => sub { + $mech->get_ok('/around'); + + my $UPLOAD_DIR = tempdir( CLEANUP => 1 ); + + # submit initial pc form + FixMyStreet::override_config { + ALLOWED_COBRANDS => [ { fixmystreet => '.' } ], + MAPIT_URL => 'http://mapit.uk/', + UPLOAD_DIR => $UPLOAD_DIR, + }, sub { + + $mech->log_in_ok('test@example.com'); + + + # submit the main form + # can't post_ok as we lose the Content_Type header + # (TODO rewrite with HTTP::Request::Common and request_ok) + $mech->get_ok('/report/new?lat=53.4031156&lon=-2.9840579'); + my ($csrf) = $mech->content =~ /name="token" value="([^"]*)"/; + + $mech->post( '/report/new', + Content_Type => 'form-data', + Content => + { + submit_problem => 1, + token => $csrf, + title => 'Test', + lat => 53.4031156, lon => -2.9840579, # in Liverpool + pc => 'L1 4LN', + detail => 'Detail', + photo1 => [ $sample_file, undef, Content_Type => 'application/octet-stream' ], + photo2 => [ $sample_file, undef, Content_Type => 'application/octet-stream' ], + photo3 => [ $sample_file, undef, Content_Type => 'application/octet-stream' ], + name => 'Bob Jones', + may_show_name => '1', + email => 'test@example.com', + phone => '', + category => 'Street lighting', + } + ); + ok $mech->success, 'Made request with multiple photo upload'; + $mech->base_is('http://localhost/report/new'); + $mech->content_like( + qr[(<img align="right" src="/photo/temp.74e3362283b6ef0c48686fb0e161da4043bbcc97.jpeg" alt="">\s*){3}], + 'Three uploaded pictures are all shown, safe'); + $mech->content_contains( + 'name="upload_fileid" value="74e3362283b6ef0c48686fb0e161da4043bbcc97.jpeg,74e3362283b6ef0c48686fb0e161da4043bbcc97.jpeg,74e3362283b6ef0c48686fb0e161da4043bbcc97.jpeg"', + 'Returned upload_fileid contains expected hash, 3 times'); + my $image_file = path($UPLOAD_DIR, '74e3362283b6ef0c48686fb0e161da4043bbcc97.jpeg'); + ok $image_file->exists, 'File uploaded to temp'; + }; +}; + +done_testing(); diff --git a/t/app/controller/questionnaire.t b/t/app/controller/questionnaire.t index 3a6a3d6ad..f42908a3e 100644 --- a/t/app/controller/questionnaire.t +++ b/t/app/controller/questionnaire.t @@ -17,10 +17,7 @@ $mech->clear_emails_ok; # create a test user and report $mech->delete_user('test@example.com'); -my $user = - FixMyStreet::App->model('DB::User') - ->find_or_create( { email => 'test@example.com', name => 'Test User' } ); -ok $user, "created test user"; +my $user = $mech->create_user_ok('test@example.com', name => 'Test User'); my $dt = DateTime->now()->subtract( weeks => 5 ); my $report_time = $dt->ymd . ' ' . $dt->hms; @@ -30,7 +27,7 @@ my $sent_time = $sent->ymd . ' ' . $sent->hms; my $report = FixMyStreet::App->model('DB::Problem')->find_or_create( { postcode => 'EH1 1BB', - council => '2651', + bodies_str => '2651', areas => ',11808,135007,14419,134935,2651,20728,', category => 'Street lighting', title => 'Testing', @@ -61,12 +58,14 @@ FixMyStreet::App->model('DB::Questionnaire')->send_questionnaires( { } ); my $email = $mech->get_email; ok $email, "got an email"; -like $email->body, qr/fill in our short questionnaire/i, "got questionnaire email"; +my $plain = $mech->get_text_body_from_email($email, 1); +like $plain->body, qr/fill in our short questionnaire/i, "got questionnaire email"; -like $email->body, qr/Testing =96 Detail/, 'email contains encoded character'; -is $email->header('Content-Type'), 'text/plain; charset="windows-1252"', 'in the right character set'; +like $plain->body_str, qr/Testing \x{2013} Detail/, 'email contains encoded character'; +is $plain->header('Content-Type'), 'text/plain; charset="utf-8"', 'in the right character set'; -my ($token) = $email->body =~ m{http://.*?/Q/(\S+)}; +my $url = $mech->get_link_from_email($email); +my ($token) = $url =~ m{/Q/(\S+)}; ok $token, "extracted questionnaire token '$token'"; $mech->clear_emails_ok; @@ -87,17 +86,20 @@ foreach my $test ( { desc => 'User goes to questionnaire URL with a bad token', token_extra => 'BAD', - content => "we couldn't validate that token", + content => "Sorry, that wasn’t a valid link", + code => 400, }, { desc => 'User goes to questionnaire URL for a now-hidden problem', state => 'hidden', content => "we couldn't locate your problem", + code => 400, }, { desc => 'User goes to questionnaire URL for an already answered questionnaire', - answered => \'ms_current_timestamp()', + answered => \'current_timestamp', content => 'already answered this questionnaire', + code => 400, }, ) { subtest $test->{desc} => sub { @@ -107,7 +109,8 @@ foreach my $test ( $questionnaire->update; (my $token = $token->token); $token .= $test->{token_extra} if $test->{token_extra}; - $mech->get_ok("/Q/$token"); + $mech->get("/Q/$token"); + is $mech->res->code, $test->{code}, "Right status received"; $mech->content_contains( $test->{content} ); # Reset, no matter what test did $report->state( 'confirmed' ); @@ -191,6 +194,16 @@ foreach my $test ( }, }, { + desc => 'Fixed report, reopened, reported before, blank update, no further questionnaire', + problem_state => 'fixed', + fields => { + been_fixed => 'No', + reported => 'Yes', + another => 'No', + update => ' ', + }, + }, + { desc => 'Closed report, said fixed, reported before, no update, no further questionnaire', problem_state => 'closed', fields => { @@ -252,13 +265,13 @@ foreach my $test ( # Check the right HTML page has been returned $mech->content_like( qr/<title>[^<]*Questionnaire/m ); - $mech->content_contains( 'glad to hear it’s been fixed' ) + $mech->content_contains( 'Glad to hear' ) if $result =~ /fixed/; - $mech->content_lacks( 'glad to hear it’s been fixed' ) + $mech->content_lacks( 'Glad to hear' ) if $result !~ /fixed/; $mech->content_contains( 'get some more information about the status of your problem' ) if $result eq 'unknown'; - $mech->content_contains( "sorry to hear that" ) + $mech->content_contains( "sorry to hear" ) if $result eq 'confirmed' || $result eq 'closed'; # Check the database has the right information @@ -266,7 +279,7 @@ foreach my $test ( $questionnaire->discard_changes; is $report->state, $result eq 'unknown' ? $test->{problem_state} : $result; is $report->send_questionnaire, $another; - ok DateTime::Format::Pg->format_datetime( $report->lastupdate) gt $report_time, 'lastupdate changed' + ok (DateTime::Format::Pg->format_datetime( $report->lastupdate) gt $report_time, 'lastupdate changed') unless $test->{fields}{been_fixed} eq 'Unknown' || $test->{lastupdate_static}; is $questionnaire->old_state, $test->{problem_state}; is $questionnaire->new_state, $result; @@ -315,11 +328,11 @@ my $comment = FixMyStreet::App->model('DB::Comment')->find_or_create( ); subtest 'Check updates are shown correctly on questionnaire page' => sub { $mech->get_ok("/Q/" . $token->token); - $mech->content_contains( 'updates that have been left' ); + $mech->content_contains( 'Show all updates' ); $mech->content_contains( 'This is some update text' ); }; -for my $test ( +for my $test ( { state => 'confirmed', fixed => 0 @@ -329,6 +342,10 @@ for my $test ( fixed => 0 }, { + state => 'action scheduled', + fixed => 0 + }, + { state => 'in progress', fixed => 0 }, @@ -337,6 +354,18 @@ for my $test ( fixed => 0 }, { + state => 'duplicate', + fixed => 0 + }, + { + state => 'not responsible', + fixed => 0 + }, + { + state => 'unable to fix', + fixed => 0 + }, + { state => 'closed', fixed => 0 }, @@ -367,32 +396,25 @@ for my $test ( }; } -SKIP: { - skip( "Need 'emptyhomes' in ALLOWED_COBRANDS config", 18 ) - unless FixMyStreet::Cobrand->exists('emptyhomes'); - - # EHA extra checking - ok $mech->host("reportemptyhomes.com"), 'change host to reportemptyhomes'; - - # Reset, and all the questionaire sending function - FIXME should it detect site itself somehow? +FixMyStreet::override_config { + ALLOWED_COBRANDS => [ 'fixmystreet' ], +}, sub { + $report->discard_changes; $report->send_questionnaire( 1 ); $report->update; $questionnaire->delete; - FixMyStreet::App->model('DB::Questionnaire')->send_questionnaires( { - site => 'emptyhomes' - } ); - $email = $mech->get_email; - ok $email, "got an email"; - $mech->clear_emails_ok; + FixMyStreet::App->model('DB::Questionnaire')->send_questionnaires(); - like $email->body, qr/fill in this short questionnaire/i, "got questionnaire email"; - ($token) = $email->body =~ m{http://.*?/Q/(\S+)}; + my $email = $mech->get_email; + my $body = $mech->get_text_body_from_email($email); + $mech->clear_emails_ok; + $body =~ s/\s+/ /g; + like $body, qr/fill in our short questionnaire/i, "got questionnaire email"; + my $url = $mech->get_link_from_email($email); + ($token) = $url =~ m{/Q/(\S+)}; ok $token, "extracted questionnaire token '$token'"; - $mech->get_ok("/Q/" . $token); - $mech->content_contains( 'should have reported what they have done' ); - # Test already answered the ever reported question, so not shown again $dt = $dt->add( weeks => 4 ); my $questionnaire2 = FixMyStreet::App->model('DB::Questionnaire')->find_or_create( @@ -403,43 +425,38 @@ SKIP: { } ); ok $questionnaire2, 'added another questionnaire'; - ok $mech->host("fixmystreet.com"), 'change host to fixmystreet'; $mech->get_ok("/Q/" . $token); $mech->title_like( qr/Questionnaire/ ); $mech->content_contains( 'Has this problem been fixed?' ); $mech->content_lacks( 'ever reported' ); - # EHA extra checking - ok $mech->host("reportemptyhomes.com"), 'change host to reportemptyhomes'; - $mech->get_ok("/Q/" . $token); - $mech->content_contains( 'made a lot of progress' ); - $token = FixMyStreet::App->model("DB::Token")->find( { scope => 'questionnaire', token => $token } ); ok $token, 'found token for questionnaire'; $questionnaire = FixMyStreet::App->model('DB::Questionnaire')->find( { id => $token->data } ); ok $questionnaire, 'found questionnaire'; $questionnaire2->delete; -} - -SKIP: { - skip( "Need 'fiksgatami' in ALLOWED_COBRANDS config", 5 ) - unless FixMyStreet::Cobrand->exists('fiksgatami'); +}; +FixMyStreet::override_config { + ALLOWED_COBRANDS => [ 'fiksgatami' ], +}, sub { # I18N Unicode extra testing using FiksGataMi + $report->discard_changes; $report->send_questionnaire( 1 ); $report->cobrand( 'fiksgatami' ); $report->update; $questionnaire->delete; - FixMyStreet::App->model('DB::Questionnaire')->send_questionnaires( { site => 'fixmystreet' } ); # It's either fixmystreet or emptyhomes + FixMyStreet::App->model('DB::Questionnaire')->send_questionnaires(); $email = $mech->get_email; ok $email, "got an email"; $mech->clear_emails_ok; - like $email->body, qr/Testing =96 Detail/, 'email contains encoded character from user'; - like $email->body, qr/sak p=E5 FiksGataMi/, 'email contains encoded character from template'; - is $email->header('Content-Type'), 'text/plain; charset="windows-1252"', 'email is in right encoding'; -} + my $plain = $mech->get_text_body_from_email($email, 1); + like $plain->body_str, qr/Testing \x{2013} Detail/, 'email contains encoded character from user'; + like $plain->body_str, qr/sak p\xe5 FiksGataMi/, 'email contains encoded character from template'; + is $plain->header('Content-Type'), 'text/plain; charset="utf-8"', 'email is in right encoding'; +}; $mech->delete_user('test@example.com'); done_testing(); diff --git a/t/app/controller/report_as_other.t b/t/app/controller/report_as_other.t new file mode 100644 index 000000000..551a59481 --- /dev/null +++ b/t/app/controller/report_as_other.t @@ -0,0 +1,190 @@ +use strict; +use warnings; +use Test::More; + +use FixMyStreet::TestMech; +use FixMyStreet::App; + +# disable info logs for this test run +FixMyStreet::App->log->disable('info'); +END { FixMyStreet::App->log->enable('info'); } + +my $mech = FixMyStreet::TestMech->new; + +my $body = $mech->create_body_ok(2237, 'Oxfordshire County Council'); +my $contact1 = $mech->create_contact_ok( body_id => $body->id, category => 'Street lighting', email => 'highways@example.com' ); +my $contact2 = $mech->create_contact_ok( body_id => $body->id, category => 'Potholes', email => 'potholes@example.com' ); + +my $test_email = 'body-user@example.net'; +my $user = $mech->log_in_ok($test_email); +$user->update({ from_body => $body->id, name => 'Body User' }); + +my ($report_to_update) = $mech->create_problems_for_body(1, $body->id, 'Title'); + +subtest "Body user, no permissions, no special reporting tools shown" => sub { + start_report(); + dropdown_shown(0); + start_update(); + dropdown_shown(0, 'updateForm'); +}; + +subtest "Body user, has permission to add report as council" => sub { + my $report = add_report( + 'contribute_as_body', + form_as => 'body', + title => "Test Report", + detail => 'Test report details.', + category => 'Street lighting', + ); + is $report->name, 'Oxfordshire County Council', 'report name is body'; + is $report->user->name, 'Body User', 'user name unchanged'; + is $report->user->id, $user->id, 'user matches'; + is $report->anonymous, 0, 'report not anonymous'; +}; + +my @users; +subtest "Body user, has permission to add report as another user" => sub { + my $report = add_report( + 'contribute_as_another_user', + form_as => 'another_user', + title => "Test Report", + detail => 'Test report details.', + category => 'Potholes', + name => 'Another User', + email => 'another@example.net', + ); + is $report->name, 'Another User', 'report name is given name'; + is $report->user->name, 'Another User', 'user name matches'; + is $report->user->email, 'another@example.net', 'user email correct'; + isnt $report->user->id, $user->id, 'user does not match'; + like $mech->get_text_body_from_email, qr/Your report to Oxfordshire County Council has been logged/; + push @users, $report->user; +}; + +subtest "Body user, has permission to add report as another (existing) user" => sub { + $mech->create_user_ok('existing@example.net', name => 'Existing User'); + my $report = add_report( + 'contribute_as_another_user', + form_as => 'another_user', + title => "Test Report", + detail => 'Test report details.', + category => 'Potholes', + name => 'Existing Yooser', + email => 'existing@example.net', + ); + is $report->name, 'Existing Yooser', 'report name is given name'; + is $report->user->name, 'Existing User', 'user name remains same'; + is $report->user->email, 'existing@example.net', 'user email correct'; + isnt $report->user->id, $user->id, 'user does not match'; + like $mech->get_text_body_from_email, qr/Your report to Oxfordshire County Council has been logged/; + push @users, $report->user; +}; + +subtest "Body user, has permission to add update as council" => sub { + my $update = add_update( + 'contribute_as_body', + form_as => 'body', + update => 'Test Update', + ); + is $update->name, 'Oxfordshire County Council', 'update name is body'; + is $update->user->name, 'Body User', 'user name unchanged'; + is $update->user->id, $user->id, 'user matches'; + is $update->anonymous, 0, 'update not anonymous'; +}; + +subtest "Body user, has permission to add update as another user" => sub { + my $update = add_update( + 'contribute_as_another_user', + form_as => 'another_user', + update => 'Test Update', + name => 'Another User', + rznvy => 'another2@example.net', + ); + is $update->name, 'Another User', 'update name is given name'; + is $update->user->name, 'Another User', 'user name matches'; + is $update->user->email, 'another2@example.net', 'user email correct'; + isnt $update->user->id, $user->id, 'user does not match'; + like $mech->get_text_body_from_email, qr/Your update has been logged/; + push @users, $update->user; +}; + +subtest "Body user, has permission to add update as another (existing) user" => sub { + my $update = add_update( + 'contribute_as_another_user', + form_as => 'another_user', + update => 'Test Update', + name => 'Existing Yooser', + rznvy => 'existing@example.net', + ); + is $update->name, 'Existing Yooser', 'update name is given name'; + is $update->user->name, 'Existing User', 'user name remains same'; + is $update->user->email, 'existing@example.net', 'user email correct'; + isnt $update->user->id, $user->id, 'user does not match'; + like $mech->get_text_body_from_email, qr/Your update has been logged/; +}; + +done_testing(); + +END { + $mech->delete_body($body); + $mech->delete_user($_) for @users; +} + +sub start_report { + my $permission = shift; + $_->delete for $user->user_body_permissions; + $user->user_body_permissions->create({ body => $body, permission_type => $permission }) + if $permission; + FixMyStreet::override_config { + ALLOWED_COBRANDS => [ 'fixmystreet' ], + MAPIT_URL => 'http://mapit.uk/', + }, sub { + $mech->get_ok('/report/new?latitude=51.7549262252&longitude=-1.25617899435'); + }; +} + +sub add_report { + my ($permission, %fields) = @_; + start_report($permission); + FixMyStreet::override_config { + ALLOWED_COBRANDS => [ 'fixmystreet' ], + MAPIT_URL => 'http://mapit.uk/', + }, sub { + dropdown_shown(1); + $mech->submit_form_ok({ + with_fields => \%fields, + }, "submit details"); + }; + $mech->content_contains('Thank you for reporting this issue'); + my $report = FixMyStreet::DB->resultset("Problem")->search(undef, { order_by => { -desc => 'id' } })->first; + ok $report, "Found the report"; + is $report->state, 'confirmed', "report is now confirmed"; + return $report; +} + +sub start_update { + my $permission = shift; + $_->delete for $user->user_body_permissions; + $user->user_body_permissions->create({ body => $body, permission_type => $permission }) + if $permission; + $mech->get_ok('/report/' . $report_to_update->id); +} + +sub add_update { + my ($permission, %fields) = @_; + start_update($permission); + dropdown_shown(1, 'updateForm'); + $mech->submit_form_ok({ + with_fields => \%fields, + }, "submit details"); + $mech->content_contains('Thank you for updating this issue'); + my $update = FixMyStreet::DB->resultset("Comment")->search(undef, { order_by => { -desc => 'id' } })->first; + ok $update, "Found the update"; + is $update->state, 'confirmed', "update is now confirmed"; + return $update; +} + +sub dropdown_shown { + my ($shown, $name) = @_; + is grep({ $_ eq 'form_as' } keys %{$mech->visible_form_values($name)}), $shown, "Dropdown shown = $shown"; +} diff --git a/t/app/controller/report_display.t b/t/app/controller/report_display.t index 39f1b59a7..b35a4a026 100644 --- a/t/app/controller/report_display.t +++ b/t/app/controller/report_display.t @@ -5,21 +5,16 @@ use Test::More; use FixMyStreet::TestMech; use Web::Scraper; use Path::Class; +use Test::LongString; use DateTime; my $mech = FixMyStreet::TestMech->new; # create a test user and report $mech->delete_user('test@example.com'); -my $user = - FixMyStreet::App->model('DB::User') - ->find_or_create( { email => 'test@example.com', name => 'Test User' } ); -ok $user, "created test user"; +my $user = $mech->create_user_ok('test@example.com', name => 'Test User'); -my $user2 = - FixMyStreet::App->model('DB::User') - ->find_or_create( { email => 'test2@example.com', name => 'Other User' } ); -ok $user2, "created test user"; +my $user2 = $mech->create_user_ok('test2@example.com', name => 'Other User'); my $dt = DateTime->new( year => 2011, @@ -30,31 +25,16 @@ my $dt = DateTime->new( second => 23 ); -my $report = FixMyStreet::App->model('DB::Problem')->find_or_create( - { - postcode => 'SW1A 1AA', - council => '2504', - areas => ',105255,11806,11828,2247,2504,', - category => 'Other', - title => 'Test 2', - detail => 'Test 2 Detail', - used_map => 't', - name => 'Test User', - anonymous => 'f', - state => 'confirmed', - confirmed => $dt->ymd . ' ' . $dt->hms, - lang => 'en-gb', - service => '', - cobrand => 'default', - cobrand_data => '', - send_questionnaire => 't', - latitude => '51.5016605453401', - longitude => '-0.142497580865087', - user_id => $user->id, - } -); +my $westminster = $mech->create_body_ok(2504, 'Westminster City Council'); +my ($report, $report2) = $mech->create_problems_for_body(2, $westminster->id, "Example", { + user => $user, + confirmed => $dt->ymd . ' ' . $dt->hms, +}); +$report->update({ + title => 'Test 2', + detail => 'Test 2 Detail' +}); my $report_id = $report->id; -ok $report, "created test report - $report_id"; subtest "check that no id redirects to homepage" => sub { $mech->get_ok('/report'); @@ -94,6 +74,7 @@ subtest "change report to unconfirmed and check for 404 status" => sub { ok $report->update( { state => 'confirmed' } ), 'confirm report again'; }; + subtest "change report to hidden and check for 410 status" => sub { ok $report->update( { state => 'hidden' } ), 'hide report'; ok $mech->get("/report/$report_id"), "get '/report/$report_id'"; @@ -129,6 +110,22 @@ subtest "check owner of report can view non public reports" => sub { ok $report->update( { non_public => 0 } ), 'make report public'; }; +subtest "duplicate reports are signposted correctly" => sub { + $report2->set_extra_metadata(duplicate_of => $report->id); + $report2->state('duplicate'); + $report2->update; + + my $report2_id = $report2->id; + ok $mech->get("/report/$report2_id"), "get '/report/$report2_id'"; + $mech->content_contains('This report is a duplicate'); + $mech->content_contains($report->title); + $mech->log_out_ok; + + $report2->unset_extra_metadata('duplicate_of'); + $report2->state('confirmed'); + $report2->update; +}; + subtest "test a good report" => sub { $mech->get_ok("/report/$report_id"); is $mech->uri->path, "/report/$report_id", "at /report/$report_id"; @@ -169,14 +166,14 @@ foreach my $meta ( category => '', service => 'Transport service', meta => -'Reported by Transport service by Test User at 15:47, Sat 16 April 2011' +'Reported via Transport service by Test User at 15:47, Sat 16 April 2011' }, { anonymous => 'f', category => 'Roads', service => 'Transport service', meta => -'Reported by Transport service in the Roads category by Test User at 15:47, Sat 16 April 2011' +'Reported via Transport service in the Roads category by Test User at 15:47, Sat 16 April 2011' }, { anonymous => 't', @@ -196,14 +193,14 @@ foreach my $meta ( category => '', service => 'Transport service', meta => -'Reported by Transport service anonymously at 15:47, Sat 16 April 2011' +'Reported via Transport service anonymously at 15:47, Sat 16 April 2011' }, { anonymous => 't', category => 'Roads', service => 'Transport service', meta => -'Reported by Transport service in the Roads category anonymously at 15:47, Sat 16 April 2011' +'Reported via Transport service in the Roads category anonymously at 15:47, Sat 16 April 2011' }, ) { @@ -213,13 +210,13 @@ foreach my $meta ( $report->update; subtest "test correct problem meta information" => sub { $mech->get_ok("/report/$report_id"); - + is $mech->extract_problem_meta, $meta->{meta}; }; } -for my $test ( +for my $test ( { description => 'new report', date => DateTime->now, @@ -283,6 +280,38 @@ for my $test ( fixed => 1 }, { + description => 'duplicate report', + date => DateTime->now, + state => 'duplicate', + banner_id => 'closed', + banner_text => 'closed', + fixed => 0 + }, + { + description => 'not responsible report', + date => DateTime->now, + state => 'not responsible', + banner_id => 'closed', + banner_text => 'closed', + fixed => 0 + }, + { + description => 'unable to fix report', + date => DateTime->now, + state => 'unable to fix', + banner_id => 'closed', + banner_text => 'closed', + fixed => 0 + }, + { + description => 'internal referral report', + date => DateTime->now, + state => 'internal referral', + banner_id => 'closed', + banner_text => 'closed', + fixed => 0 + }, + { description => 'closed report', date => DateTime->now, state => 'closed', @@ -299,6 +328,14 @@ for my $test ( fixed => 0 }, { + description => 'action scheduled report', + date => DateTime->now, + state => 'action scheduled', + banner_id => 'progress', + banner_text => 'progress', + fixed => 0 + }, + { description => 'planned report', date => DateTime->now, state => 'planned', @@ -307,7 +344,7 @@ for my $test ( fixed => 0 }, { - description => 'in progressreport', + description => 'in progress report', date => DateTime->now, state => 'in progress', banner_id => 'progress', @@ -345,30 +382,33 @@ for my $test ( }; } -for my $test ( +my $body_westminster = $mech->create_body_ok(2504, 'Westminster City Council'); +my $body_camden = $mech->create_body_ok(2505, 'Camden Borough Council'); + +for my $test ( { desc => 'no state dropdown if user not from authority', - from_body => 0, + from_body => undef, no_state => 1, - report_council => '2504', + report_body => $body_westminster->id, }, { desc => 'state dropdown if user from authority', - from_body => 2504, + from_body => $body_westminster->id, no_state => 0, - report_council => '2504', + report_body => $body_westminster->id, }, { - desc => 'no state dropdown if user not from same council as problem', - from_body => 2505, + desc => 'no state dropdown if user not from same body as problem', + from_body => $body_camden->id, no_state => 1, - report_council => '2504', + report_body => $body_westminster->id, }, { - desc => 'state dropdown if user from authority and problem sent to multiple councils', - from_body => 2504, + desc => 'state dropdown if user from authority and problem sent to multiple bodies', + from_body => $body_westminster->id, no_state => 0, - report_council => '2504,2506', + report_body => $body_westminster->id . ',2506', }, ) { subtest $test->{desc} => sub { @@ -377,7 +417,7 @@ for my $test ( $user->update; $report->discard_changes; - $report->council( $test->{report_council} ); + $report->bodies_str( $test->{report_body} ); $report->update; $mech->get_ok("/report/$report_id"); @@ -390,10 +430,160 @@ for my $test ( }; } -$report->discard_changes; -$report->council( 2504 ); -$report->update; +subtest "Zurich unconfirmeds are 200" => sub { + FixMyStreet::override_config { + ALLOWED_COBRANDS => [ 'zurich' ], + MAP_TYPE => 'Zurich,OSM', + }, sub { + $mech->host( 'zurich.example.com' ); + ok $report->update( { state => 'unconfirmed' } ), 'unconfirm report'; + $mech->get_ok("/report/$report_id"); + $mech->content_contains( 'Überprüfung ausstehend' ); + ok $report->update( { state => 'confirmed' } ), 'confirm report again'; + $mech->host( 'www.fixmystreet.com' ); + }; +}; + +subtest "Zurich banners are displayed correctly" => sub { + FixMyStreet::override_config { + ALLOWED_COBRANDS => [ 'zurich' ], + MAP_TYPE => 'Zurich,OSM', + }, sub { + $mech->host( 'zurich.example.com' ); -# tidy up -$mech->delete_user('test@example.com'); -done_testing(); + for my $test ( + { + description => 'new report', + state => 'unconfirmed', + banner_id => 'closed', + banner_text => 'Erfasst' + }, + { + description => 'confirmed report', + state => 'confirmed', + banner_id => 'closed', + banner_text => 'Aufgenommen', + }, + { + description => 'fixed report', + state => 'fixed - council', + banner_id => 'fixed', + banner_text => 'Beantwortet', + }, + { + description => 'closed report', + state => 'closed', + banner_id => 'closed', + banner_text => _('Extern'), + }, + { + description => 'in progress report', + state => 'in progress', + banner_id => 'progress', + banner_text => 'In Bearbeitung', + }, + { + description => 'planned report', + state => 'planned', + banner_id => 'progress', + banner_text => 'In Bearbeitung', + }, + { + description => 'planned report', + state => 'planned', + banner_id => 'progress', + banner_text => 'In Bearbeitung', + }, + { + description => 'jurisdiction unknown', + state => 'unable to fix', + banner_id => 'fixed', + # We can't use _('Jurisdiction Unknown') here because + # TestMech::extract_problem_banner decodes the HTML entities before + # the string is passed back. + banner_text => 'Zust\x{e4}ndigkeit unbekannt', + }, + ) { + subtest "banner for $test->{description}" => sub { + $report->state( $test->{state} ); + $report->update; + + $mech->get_ok("/report/$report_id"); + is $mech->uri->path, "/report/$report_id", "at /report/$report_id"; + my $banner = $mech->extract_problem_banner; + if ( $banner->{text} ) { + $banner->{text} =~ s/^ //g; + $banner->{text} =~ s/ $//g; + } + + is $banner->{id}, $test->{banner_id}, 'banner id'; + if ($test->{banner_text}) { + like_string( $banner->{text}, qr/$test->{banner_text}/i, 'banner text is ' . $test->{banner_text} ); + } else { + is $banner->{text}, $test->{banner_text}, 'banner text'; + } + + }; + } + + $mech->host( 'www.fixmystreet.com' ); + }; +}; + +my $oxfordshire = $mech->create_body_ok(2237, 'Oxfordshire County Council', id => 2237); +my $oxfordshireuser = $mech->create_user_ok('counciluser@example.com', name => 'Council User', from_body => $oxfordshire); + +subtest "check user details show when a user has correct permissions" => sub { + $report->update( { + name => 'Oxfordshire County Council', + user_id => $oxfordshireuser->id, + service => '', + anonymous => 'f', + bodies_str => $oxfordshire->id, + confirmed => '2012-01-10 15:17:00' + }); + + ok $oxfordshireuser->user_body_permissions->create({ + body => $oxfordshire, + permission_type => 'view_body_contribute_details', + }); + + $mech->log_in_ok( $oxfordshireuser->email ); + ok $mech->get("/report/$report_id"), "get '/report/$report_id'"; + is $mech->extract_problem_meta, + 'Reported in the Roads category by Oxfordshire County Council (Council User) at 15:17, Tue 10 January 2012', + 'correct problem meta information'; + + ok $oxfordshireuser->user_body_permissions->delete_all, "Remove view_body_contribute_details permissions"; + + ok $mech->get("/report/$report_id"), "get '/report/$report_id'"; + is $mech->extract_problem_meta, + 'Reported in the Roads category by Oxfordshire County Council at 15:17, Tue 10 January 2012', + 'correct problem meta information for user without relevant permissions'; + + $mech->log_out_ok; + + ok $mech->get("/report/$report_id"), "get '/report/$report_id'"; + is $mech->extract_problem_meta, + 'Reported in the Roads category by Oxfordshire County Council at 15:17, Tue 10 January 2012', + 'correct problem meta information for logged out user'; + +}; + +subtest "check brackets don't appear when username and report name are the same" => sub { + $report->update( { + name => 'Council User' + }); + + $mech->log_in_ok( $oxfordshireuser->email ); + ok $mech->get("/report/$report_id"), "get '/report/$report_id'"; + is $mech->extract_problem_meta, + 'Reported in the Roads category by Council User at 15:17, Tue 10 January 2012', + 'correct problem meta information'; +}; + +END { + $mech->delete_user('test@example.com'); + $mech->delete_body($westminster); + done_testing(); +} diff --git a/t/app/controller/report_import.t b/t/app/controller/report_import.t index eb686b44e..b956b61ae 100644 --- a/t/app/controller/report_import.t +++ b/t/app/controller/report_import.t @@ -3,8 +3,13 @@ use warnings; use Test::More; use FixMyStreet::TestMech; +use FixMyStreet::App; use Web::Scraper; use Path::Class; +use LWP::Protocol::PSGI; +use t::Mock::MapItZurich; + +LWP::Protocol::PSGI->register(t::Mock::MapItZurich->to_psgi_app, host => 'mapit.zurich'); my $mech = FixMyStreet::TestMech->new; $mech->get_ok('/import'); @@ -12,6 +17,22 @@ $mech->get_ok('/import'); my $sample_file = file(__FILE__)->parent->file("sample.jpg")->stringify; ok -e $sample_file, "sample file $sample_file exists"; +# disable info logs for this test run +FixMyStreet::App->log->disable('info'); +END { FixMyStreet::App->log->enable('info'); } + +my $body = $mech->create_body_ok(2245, 'Wiltshire Council'); +$mech->create_contact_ok( + body_id => $body->id, + category => 'Street lighting', + email => 'streetlighting@example.com', +); +$mech->create_contact_ok( + body_id => $body->id, + category => 'Potholes', + email => 'highways@example.com', +); + # submit an empty report to import - check we get all errors subtest "Test creating bad partial entries" => sub { @@ -58,10 +79,14 @@ subtest "Test creating bad partial entries" => sub { { $mech->get_ok('/import'); - $mech->submit_form_ok( # - { with_fields => $test->{fields} }, - "fill in form" - ); + FixMyStreet::override_config { + ALLOWED_COBRANDS => [ { 'fixmystreet' => '.' } ], + }, sub { + $mech->submit_form_ok( # + { with_fields => $test->{fields} }, + "fill in form" + ); + }; is_deeply( $mech->import_errors, $test->{errors}, "expected errors" ); } @@ -69,7 +94,6 @@ subtest "Test creating bad partial entries" => sub { }; subtest "Submit a correct entry" => sub { - $mech->get_ok('/import'); $mech->submit_form_ok( # @@ -90,15 +114,16 @@ subtest "Submit a correct entry" => sub { is $mech->content, 'SUCCESS', "Got success response"; # check that we have received the email - $mech->email_count_is(1); - my $email = $mech->get_email; + my $token_url = $mech->get_link_from_email; $mech->clear_emails_ok; - - my ($token_url) = $email->body =~ m{(http://\S+)}; ok $token_url, "Found a token url $token_url"; # go to the token url - $mech->get_ok($token_url); + FixMyStreet::override_config { + MAPIT_URL => 'http://mapit.uk/', + }, sub { + $mech->get_ok($token_url); + }; # check that we are on '/around' is $mech->uri->path, '/around', "sent to /around"; @@ -107,10 +132,15 @@ subtest "Submit a correct entry" => sub { is_deeply $mech->visible_form_values, { pc => '' }, "check only pc field is shown"; - $mech->submit_form_ok( # - { with_fields => { pc => 'SW1A 1AA' } }, - "fill in postcode" - ); + FixMyStreet::override_config { + ALLOWED_COBRANDS => [ 'fixmystreet' ], + MAPIT_URL => 'http://mapit.uk/', + }, sub { + $mech->submit_form_ok( + { with_fields => { pc => 'SN15 5NG' } }, + "fill in postcode" + ); + }; is $mech->uri->path, '/report/new', "sent to report page"; @@ -120,7 +150,9 @@ subtest "Submit a correct entry" => sub { name => 'Test User', title => 'Test report', detail => 'This is a test report', - photo => '', + photo1 => '', + photo2 => '', + photo3 => '', phone => '', may_show_name => '1', category => '-- Pick a category --', @@ -129,19 +161,24 @@ subtest "Submit a correct entry" => sub { # Check photo present, and still there after map submission (testing bug #18) $mech->content_contains( '<img align="right" src="/photo/' ); - $mech->content_contains('latitude" value="51.50101"', 'Check latitude'); - $mech->content_contains('longitude" value="-0.141587"', 'Check longitude'); - $mech->submit_form_ok( - { - button => 'tile_32742.21793', - x => 10, - y => 10, - }, - "New map location" - ); + $mech->content_contains('latitude" value="51.5"', 'Check latitude'); + $mech->content_contains('longitude" value="-2.1"', 'Check longitude'); + FixMyStreet::override_config { + ALLOWED_COBRANDS => [ { 'fixmystreet' => '.' } ], + MAPIT_URL => 'http://mapit.uk/', + }, sub { + $mech->submit_form_ok( + { + button => 'tile_16192.10896', + x => 10, + y => 10, + }, + "New map location" + ); + }; $mech->content_contains( '<img align="right" src="/photo/' ); - $mech->content_contains('latitude" value="51.50519"', 'Check latitude'); - $mech->content_contains('longitude" value="-0.142608"', 'Check longitude'); + $mech->content_contains('latitude" value="51.508475"', 'Check latitude'); + $mech->content_contains('longitude" value="-2.108946"', 'Check longitude'); # check that fields haven't changed at all is_deeply $mech->visible_form_values, @@ -149,7 +186,9 @@ subtest "Submit a correct entry" => sub { name => 'Test User', title => 'Test report', detail => 'This is a test report', - photo => '', + photo1 => '', + photo2 => '', + photo3 => '', phone => '', may_show_name => '1', category => '-- Pick a category --', @@ -157,19 +196,24 @@ subtest "Submit a correct entry" => sub { "check imported fields are shown"; # change the details - $mech->submit_form_ok( # - { - with_fields => { - name => 'New Test User', - title => 'New Test report', - detail => 'This is a test report', - phone => '01234 567 890', - may_show_name => '1', - category => 'Street lighting', - } - }, - "Update details and save" - ); + FixMyStreet::override_config { + ALLOWED_COBRANDS => [ { 'fixmystreet' => '.' } ], + MAPIT_URL => 'http://mapit.uk/', + }, sub { + $mech->submit_form_ok( + { + with_fields => { + name => 'New Test User', + title => 'New Test report', + detail => 'This is a test report', + phone => '01234 567 890', + may_show_name => '1', + category => 'Street lighting', + } + }, + "Update details and save" + ); + }; # check that report has been created my $user = @@ -192,8 +236,8 @@ subtest "Submit a correct entry (with location)" => sub { { with_fields => { service => 'test-script', - lat => '51.5010096115539', # SW1A 1AA - lon => '-0.141587067110009', + lat => '51.5', + lon => '-2.1', name => 'Test User ll', email => 'test-ll@example.com', subject => 'Test report ll', @@ -208,15 +252,17 @@ subtest "Submit a correct entry (with location)" => sub { is $mech->content, 'SUCCESS', "Got success response"; # check that we have received the email - $mech->email_count_is(1); - my $email = $mech->get_email; + my $token_url = $mech->get_link_from_email; $mech->clear_emails_ok; - - my ($token_url) = $email->body =~ m{(http://\S+)}; ok $token_url, "Found a token url $token_url"; # go to the token url - $mech->get_ok($token_url); + FixMyStreet::override_config { + ALLOWED_COBRANDS => [ { 'fixmystreet' => '.' } ], + MAPIT_URL => 'http://mapit.uk/', + }, sub { + $mech->get_ok($token_url); + }; # check that we are on '/report/new' is $mech->uri->path, '/report/new', "sent to /report/new"; @@ -227,7 +273,9 @@ subtest "Submit a correct entry (with location)" => sub { name => 'Test User ll', title => 'Test report ll', detail => 'This is a test report ll', - photo => '', + photo1 => '', + photo2 => '', + photo3 => '', phone => '', may_show_name => '1', category => '-- Pick a category --', @@ -235,19 +283,24 @@ subtest "Submit a correct entry (with location)" => sub { "check imported fields are shown"; # change the details - $mech->submit_form_ok( # - { - with_fields => { - name => 'New Test User ll', - title => 'New Test report ll', - detail => 'This is a test report ll', - phone => '01234 567 890', - may_show_name => '1', - category => 'Street lighting', - } - }, - "Update details and save" - ); + FixMyStreet::override_config { + ALLOWED_COBRANDS => [ { 'fixmystreet' => '.' } ], + MAPIT_URL => 'http://mapit.uk/', + }, sub { + $mech->submit_form_ok( # + { + with_fields => { + name => 'New Test User ll', + title => 'New Test report ll', + detail => 'This is a test report ll', + phone => '01234 567 890', + may_show_name => '1', + category => 'Street lighting', + } + }, + "Update details and save" + ); + }; # check that report has been created my $user = @@ -263,72 +316,77 @@ subtest "Submit a correct entry (with location)" => sub { }; subtest "Submit a correct entry (with location) to cobrand" => sub { + FixMyStreet::override_config { + ALLOWED_COBRANDS => [ 'zurich' ], + MAPIT_URL => 'http://mapit.zurich/', + MAPIT_TYPES => [ 'O08' ], + MAPIT_ID_WHITELIST => [], + MAP_TYPE => 'Zurich,OSM', + }, sub { + ok $mech->host("zurich.example.org"), 'change host to zurich'; - SKIP: { - skip( "Need 'fiksgatami' in ALLOWED_COBRANDS config", 20 ) - unless FixMyStreet::Cobrand->exists('fiksgatami'); - mySociety::MaPit::configure('http://mapit.nuug.no/'); - ok $mech->host("fiksgatami.no"), 'change host to fiksgatami'; + $mech->get_ok('/import'); - $mech->get_ok('/import'); + $mech->submit_form_ok( # + { + with_fields => { + service => 'test-script', + lat => '47.4', + lon => '8.5', + name => 'Test User ll', + email => 'test-ll@example.com', + subject => 'Test report ll', + detail => 'This is a test report ll', + photo => $sample_file, + } + }, + "fill in form" + ); - $mech->submit_form_ok( # - { - with_fields => { - service => 'test-script', - lat => '59', - lon => '10', - name => 'Test User ll', - email => 'test-ll@example.com', - subject => 'Test report ll', - detail => 'This is a test report ll', - photo => $sample_file, - } - }, - "fill in form" - ); + is_deeply( $mech->import_errors, [], "got no errors" ); + is $mech->content, 'SUCCESS', "Got success response"; - is_deeply( $mech->import_errors, [], "got no errors" ); - is $mech->content, 'SUCCESS', "Got success response"; + # check that we have received the email + my $token_url = $mech->get_link_from_email; + $mech->clear_emails_ok; + ok $token_url, "Found a token url $token_url"; - # check that we have received the email - $mech->email_count_is(1); - my $email = $mech->get_email; - $mech->clear_emails_ok; + # go to the token url + $mech->get_ok($token_url); - my ($token_url) = $email->body =~ m{(http://\S+)}; - ok $token_url, "Found a token url $token_url"; + # check that we are on '/report/new' + is $mech->uri->path, '/report/new', "sent to /report/new"; - # go to the token url - $mech->get_ok($token_url); + # check that fields are prefilled for us + is_deeply $mech->visible_form_values, + { + name => 'Test User ll', + detail => 'This is a test report ll', + photo1 => '', + photo2 => '', + photo3 => '', + phone => '', + email => 'test-ll@example.com', + }, + "check imported fields are shown" + or diag Dumper( $mech->visible_form_values ); use Data::Dumper; - # check that we are on '/report/new' - is $mech->uri->path, '/report/new', "sent to /report/new"; - - # check that fields are prefilled for us - is_deeply $mech->visible_form_values, - { - name => 'Test User ll', - title => 'Test report ll', - detail => 'This is a test report ll', - photo => '', - phone => '', - may_show_name => '1', - }, - "check imported fields are shown"; - - my $user = - FixMyStreet::App->model('DB::User') - ->find( { email => 'test-ll@example.com' } ); - ok $user, "Found a user"; - - my $report = $user->problems->first; - is $report->state, 'partial', 'is still partial'; - is $report->title, 'Test report ll', 'title is correct'; - is $report->lang, 'nb', 'language is correct'; - - $mech->delete_user($user); - } + my $user = + FixMyStreet::App->model('DB::User') + ->find( { email => 'test-ll@example.com' } ); + ok $user, "Found a user"; + + my $report = $user->problems->first; + is $report->state, 'partial', 'is still partial'; + is $report->title, 'Test report ll', 'title is correct'; + is $report->lang, 'de-ch', 'language is correct'; + + $mech->delete_user($user); + }; }; done_testing(); + +END { + $mech->delete_body($body); +} diff --git a/t/app/controller/report_inspect.t b/t/app/controller/report_inspect.t new file mode 100644 index 000000000..69e43ad99 --- /dev/null +++ b/t/app/controller/report_inspect.t @@ -0,0 +1,248 @@ +use strict; +use warnings; +use Test::More; + +use FixMyStreet::TestMech; + +my $mech = FixMyStreet::TestMech->new; + +my $brum = $mech->create_body_ok(2514, 'Birmingham City Council', id => 2514); +my $oxon = $mech->create_body_ok(2237, 'Oxfordshire County Council', id => 2237); +my $contact = $mech->create_contact_ok( body_id => $oxon->id, category => 'Cows', email => 'cows@example.net' ); +my $rp = FixMyStreet::DB->resultset("ResponsePriority")->create({ + body => $oxon, + name => 'High Priority', +}); +FixMyStreet::DB->resultset("ContactResponsePriority")->create({ + contact => $contact, + response_priority => $rp, +}); +my $wodc = $mech->create_body_ok(2420, 'West Oxfordshire District Council', id => 2420); +$mech->create_contact_ok( body_id => $wodc->id, category => 'Horses', email => 'horses@example.net' ); + + +my ($report, $report2) = $mech->create_problems_for_body(2, $oxon->id, 'Test', { + category => 'Cows', cobrand => 'fixmystreet', areas => ',2237,2420', + whensent => \'current_timestamp', + latitude => 51.847693, longitude => -1.355908, +}); +my $report_id = $report->id; +my $report2_id = $report2->id; + + +my $user = $mech->log_in_ok('test@example.com'); +$user->update( { from_body => $oxon } ); + +FixMyStreet::override_config { + MAPIT_URL => 'http://mapit.uk/', + ALLOWED_COBRANDS => 'fixmystreet', +}, sub { + subtest "test inspect page" => sub { + $mech->get_ok("/report/$report_id"); + $mech->content_lacks('Save changes'); + $mech->content_lacks('Priority'); + $mech->content_lacks('Traffic management'); + + $user->user_body_permissions->create({ body => $oxon, permission_type => 'report_edit_priority' }); + $mech->get_ok("/report/$report_id"); + $mech->content_contains('Save changes'); + $mech->content_contains('Priority'); + $mech->content_lacks('Traffic management'); + + $user->user_body_permissions->create({ body => $oxon, permission_type => 'report_inspect' }); + $mech->get_ok("/report/$report_id"); + $mech->content_contains('Save changes'); + $mech->content_contains('Priority'); + $mech->content_contains('Traffic management'); + }; + + subtest "test basic inspect submission" => sub { + $mech->submit_form_ok({ button => 'save', with_fields => { traffic_information => 'Yes', state => 'Action Scheduled', include_update => undef } }); + $report->discard_changes; + is $report->state, 'action scheduled', 'report state changed'; + is $report->get_extra_metadata('traffic_information'), 'Yes', 'report data changed'; + }; + + subtest "test inspect & instruct submission" => sub { + $report->unset_extra_metadata('inspected'); + $report->state('confirmed'); + $report->update; + $report->inspection_log_entry->delete; + my $reputation = $report->user->get_extra_metadata("reputation"); + $mech->get_ok("/report/$report_id"); + $mech->submit_form_ok({ button => 'save', with_fields => { public_update => "This is a public update.", include_update => "1", state => 'action scheduled' } }); + $report->discard_changes; + is $report->comments->first->text, "This is a public update.", 'Update was created'; + is $report->get_extra_metadata('inspected'), 1, 'report marked as inspected'; + is $report->user->get_extra_metadata('reputation'), $reputation, "User reputation wasn't changed"; + }; + + subtest "test update is required when instructing" => sub { + $report->unset_extra_metadata('inspected'); + $report->update; + $report->inspection_log_entry->delete; + $report->comments->delete_all; + $mech->get_ok("/report/$report_id"); + $mech->submit_form_ok({ button => 'save', with_fields => { public_update => undef, include_update => "1" } }); + is_deeply $mech->page_errors, [ "Please provide a public update for this report." ], 'errors match'; + $report->discard_changes; + is $report->comments->count, 0, "Update wasn't created"; + is $report->get_extra_metadata('inspected'), undef, 'report not marked as inspected'; + }; + + subtest "test location changes" => sub { + $mech->get_ok("/report/$report_id"); + $mech->submit_form_ok({ button => 'save', with_fields => { latitude => 55, longitude => -2 } }); + $mech->content_contains('Invalid location'); + $mech->submit_form_ok({ button => 'save', with_fields => { latitude => 51.754926, longitude => -1.256179 } }); + $mech->content_lacks('Invalid location'); + }; + + subtest "test duplicate reports are shown" => sub { + my $old_state = $report->state; + $report->set_extra_metadata('duplicate_of' => $report2->id); + $report->state('duplicate'); + $report->update; + + $mech->get_ok("/report/$report_id"); + $mech->content_contains($report2->title); + + $mech->get_ok("/report/$report2_id"); + $mech->content_contains($report->title); + + $report->unset_extra_metadata('duplicate_of'); + $report->state($old_state); + $report->update; + }; + + subtest "marking a report as a duplicate with update correctly sets update status" => sub { + my $old_state = $report->state; + $report->comments->delete_all; + + $mech->get_ok("/report/$report_id"); + $mech->submit_form_ok({ button => 'save', with_fields => { state => 'Duplicate', duplicate_of => $report2->id, public_update => "This is a duplicate.", include_update => "1" } }); + $report->discard_changes; + + is $report->state, 'duplicate', 'report marked as duplicate'; + is $report->comments->search({ problem_state => 'duplicate' })->count, 1, 'update marking report as duplicate was left'; + + $report->update({ state => $old_state }); + }; + + subtest "marking a report as a duplicate doesn't clobber user-provided update" => sub { + my $old_state = $report->state; + $report->comments->delete_all; + + $mech->get_ok("/report/$report_id"); + my $update_text = "This text was entered as an update by the user."; + $mech->submit_form_ok({ button => 'save', with_fields => { + state => 'Duplicate', + duplicate_of => $report2->id, + public_update => $update_text, + include_update => "1", + }}); + $report->discard_changes; + + is $report->state, 'duplicate', 'report marked as duplicate'; + is $report->comments->search({ problem_state => 'duplicate' })->count, 1, 'update marked report as duplicate'; + $mech->content_contains($update_text); + $mech->content_lacks("Thank you for your report. This problem has already been reported."); + + $report->update({ state => $old_state }); + }; + + foreach my $test ( + { type => 'report_edit_priority', priority => 1 }, + { type => 'report_edit_category', category => 1 }, + { type => 'report_inspect', priority => 1, category => 1, detailed => 1 }, + ) { + subtest "test $test->{type} permission" => sub { + $user->user_body_permissions->delete; + $user->user_body_permissions->create({ body => $oxon, permission_type => $test->{type} }); + $mech->get_ok("/report/$report_id"); + $mech->contains_or_lacks($test->{priority}, 'Priority</label>'); + $mech->contains_or_lacks($test->{priority}, '>High'); + $mech->contains_or_lacks($test->{category}, 'Category'); + $mech->contains_or_lacks($test->{detailed}, 'Extra details'); + $mech->submit_form_ok({ + button => 'save', + with_fields => { + $test->{priority} ? (priority => 1) : (), + $test->{category} ? (category => 'Cows') : (), + $test->{detailed} ? (detailed_information => 'Highland ones') : (), + } + }); + }; + } +}; + +FixMyStreet::override_config { + ALLOWED_COBRANDS => 'oxfordshire', +}, sub { + subtest "test negative reputation" => sub { + my $reputation = $report->user->get_extra_metadata("reputation") || 0; + + $mech->get_ok("/report/$report_id"); + $mech->submit_form( button => 'remove_from_site' ); + + $report->discard_changes; + is $report->user->get_extra_metadata('reputation'), $reputation-1, "User reputation was decreased"; + $report->update({ state => 'confirmed' }); + }; + + subtest "test positive reputation" => sub { + $report->unset_extra_metadata('inspected'); + $report->update; + $report->inspection_log_entry->delete if $report->inspection_log_entry; + my $reputation = $report->user->get_extra_metadata("reputation") || 0; + $mech->get_ok("/report/$report_id"); + $mech->submit_form_ok({ button => 'save', with_fields => { state => 'action scheduled', include_update => undef } }); + $report->discard_changes; + is $report->get_extra_metadata('inspected'), 1, 'report marked as inspected'; + is $report->user->get_extra_metadata('reputation'), $reputation+1, "User reputation was increased"; + }; + + subtest "Oxfordshire-specific traffic management options are shown" => sub { + $report->update({ state => 'confirmed' }); + $mech->get_ok("/report/$report_id"); + $mech->submit_form_ok({ button => 'save', with_fields => { traffic_information => 'Signs and Cones', state => 'Action Scheduled', include_update => undef } }); + $report->discard_changes; + is $report->state, 'action scheduled', 'report state changed'; + is $report->get_extra_metadata('traffic_information'), 'Signs and Cones', 'report data changed'; + }; + +}; + +FixMyStreet::override_config { + ALLOWED_COBRANDS => [ 'oxfordshire', 'fixmystreet' ], + BASE_URL => 'http://fixmystreet.site', +}, sub { + subtest "test category/body changes" => sub { + $mech->host('oxfordshire.fixmystreet.site'); + $report->update({ state => 'confirmed' }); + $mech->get_ok("/report/$report_id"); + # Then change the category to the other council in this location, + # which should cause it to be resent. We clear the host because + # otherwise testing stays on host() above. + $mech->clear_host; + $mech->submit_form(button => 'save', with_fields => { category => 'Horses', include_update => undef, }); + + $report->discard_changes; + is $report->category, "Horses", "Report in correct category"; + is $report->whensent, undef, "Report marked as unsent"; + is $report->bodies_str, $wodc->id, "Reported to WODC"; + + is $mech->uri->path, "/report/$report_id", "redirected to correct page"; + is $mech->res->code, 200, "got 200 for final destination"; + is $mech->res->previous->code, 302, "got 302 for redirect"; + # Extra check given host weirdness + is $mech->res->previous->header('Location'), "http://fixmystreet.site/report/$report_id"; + }; +}; + + +END { + $mech->delete_body($oxon); + $mech->delete_body($brum); + done_testing(); +} diff --git a/t/app/controller/report_interest_count.t b/t/app/controller/report_interest_count.t index 0a8a5ac0b..3cb80ea5f 100644 --- a/t/app/controller/report_interest_count.t +++ b/t/app/controller/report_interest_count.t @@ -1,5 +1,16 @@ use strict; use warnings; + +package FixMyStreet::Cobrand::Tester; + +use parent 'FixMyStreet::Cobrand::Default'; + +sub can_support_problems { + return 1; +} + +package main; + use Test::More; use FixMyStreet::TestMech; @@ -11,15 +22,7 @@ my $mech = FixMyStreet::TestMech->new; # create a test user and report $mech->delete_user('test@example.com'); -my $user = - FixMyStreet::App->model('DB::User') - ->find_or_create( { email => 'test@example.com', name => 'Test User' } ); -ok $user, "created test user"; - -my $user2 = - FixMyStreet::App->model('DB::User') - ->find_or_create( { email => 'test2@example.com', name => 'Other User' } ); -ok $user2, "created test user"; +my $user = $mech->create_user_ok('test@example.com', name => 'Test User'); my $dt = DateTime->new( year => 2011, @@ -33,7 +36,7 @@ my $dt = DateTime->new( my $report = FixMyStreet::App->model('DB::Problem')->find_or_create( { postcode => 'SW1A 1AA', - council => '2504', + bodies_str => '2504', areas => ',105255,11806,11828,2247,2504,', category => 'Other', title => 'Test 2', @@ -56,37 +59,38 @@ my $report = FixMyStreet::App->model('DB::Problem')->find_or_create( my $report_id = $report->id; ok $report, "created test report - $report_id"; -SKIP: { - skip( "Need 'fixmybarangay' in ALLOWED_COBRANDS config", 29 ) - unless FixMyStreet::Cobrand->exists('fixmybarangay'); +FixMyStreet::override_config { + ALLOWED_COBRANDS => [ 'tester' ], +}, sub { + my $body = $mech->create_body_ok(2504, 'Westminster City Council'); + for my $test ( { - desc => 'if not from council then no supporter button', - from_body => 0, + desc => 'if not from body then no supporter button', + from_body => undef, support_string => 'No supporters', }, { - desc => 'from council user can increment supported count', - from_body => 2504, + desc => 'from body user can increment supported count', + from_body => $body->id, support_string => 'No supporters', updated_support => '1 supporter' }, { desc => 'correct grammar for more than one supporter', - from_body => 2504, + from_body => $body->id, support_string => '1 supporter', updated_support => '2 supporters' }, ) { subtest $test->{desc} => sub { - ok $mech->host('fixmybarangay.com'), 'changed to fixmybarangay'; $mech->log_in_ok( $user->email ); $user->from_body( $test->{from_body} ); $user->update; - $report->discard_changes; - $report->council( $test->{report_council} ); - $report->update; + $report->update( { + bodies_str => $test->{report_council} + } ); $mech->get_ok("/report/$report_id"); $mech->content_contains( $test->{support_string} ); @@ -95,7 +99,7 @@ SKIP: { $mech->content_contains('Add support'); $mech->submit_form_ok( { form_number => 1 } ); - is $mech->uri, "http://fixmybarangay.com/report/$report_id", 'add support redirects to report page'; + is $mech->uri->path, "/report/$report_id", 'add support redirects to report page'; $mech->content_contains($test->{updated_support}); } else { @@ -104,29 +108,23 @@ SKIP: { }; } - subtest 'check non council user cannot increment support count' => sub { - ok $mech->host('fixmybarangay.com'), 'changed to fixmybarangay'; - $report->discard_changes; - $report->interest_count(1); - ok $report->update(), 'updated interest count'; - - $report->discard_changes; + subtest 'check non body user cannot increment support count' => sub { + ok $report->update({ interest_count => 1 }), 'updated interest count'; is $report->interest_count, 1, 'correct interest count'; $mech->get_ok("/report/$report_id"); $mech->content_contains( '1 supporter' ); + $mech->log_out_ok( $user->email ); $mech->post_ok("/report/support", { id => $report_id } ); - is $mech->uri, "http://fixmybarangay.com/report/$report_id", 'add support redirects to report page'; + is $mech->uri->path, "/report/$report_id", 'add support redirects to report page'; $mech->content_contains( '1 supporter' ); }; }; subtest 'check support details not shown if not enabled in cobrand' => sub { - ok $mech->host('fixmystreet.com'), 'changed to fixmystreet'; - $report->interest_count(1); ok $report->update, 'updated interest count'; @@ -134,10 +132,7 @@ subtest 'check support details not shown if not enabled in cobrand' => sub { $mech->content_lacks( '1 supporter' ); }; -$report->discard_changes; -$report->council( 2504 ); -$report->update; - -# tidy up -$mech->delete_user('test@example.com'); -done_testing(); +END { + $mech->delete_user('test@example.com'); + done_testing(); +} diff --git a/t/app/controller/report_new.t b/t/app/controller/report_new.t index 92943bde9..71090cd26 100644 --- a/t/app/controller/report_new.t +++ b/t/app/controller/report_new.t @@ -2,12 +2,16 @@ use strict; use utf8; # sign in error message has – in it use warnings; use Test::More; -use utf8; use FixMyStreet::TestMech; +use FixMyStreet::App; use Web::Scraper; use Path::Class; +# disable info logs for this test run +FixMyStreet::App->log->disable('info'); +END { FixMyStreet::App->log->enable('info'); } + my $mech = FixMyStreet::TestMech->new; $mech->get_ok('/report/new'); @@ -17,86 +21,146 @@ ok -e $sample_file, "sample file $sample_file exists"; subtest "test that bare requests to /report/new get redirected" => sub { $mech->get_ok('/report/new'); - is $mech->uri->path, '/around', "went to /around"; + is $mech->uri->path, '/', "went to /"; is_deeply { $mech->uri->query_form }, {}, "query empty"; - $mech->get_ok('/report/new?pc=SW1A%201AA'); + FixMyStreet::override_config { + MAPIT_URL => 'http://mapit.uk/', + }, sub { + $mech->get_ok('/report/new?pc=SW1A%201AA'); + }; is $mech->uri->path, '/around', "went to /around"; is_deeply { $mech->uri->query_form }, { pc => 'SW1A 1AA' }, "pc correctly transferred"; }; -my %contact_params = ( - confirmed => 1, - deleted => 0, - editor => 'Test', - whenedited => \'current_timestamp', - note => 'Created for test', -); +my %body_ids; +my @bodies; +for my $body ( + { area_id => 2651, name => 'City of Edinburgh Council' }, + { area_id => 2226, name => 'Gloucestershire County Council' }, + { area_id => 2326, name => 'Cheltenham Borough Council' }, + { area_id => 2504, name => 'Westminster City Council' }, + # The next three have fixed IDs because bits of the code rely on + # the body ID === MapIt area ID. + { area_id => 2482, name => 'Bromley Council', id => 2482 }, + { area_id => 2227, name => 'Hampshire County Council', id => 2227 }, + { area_id => 2333, name => 'Hart Council', id => 2333 }, +) { + my $body_obj = $mech->create_body_ok($body->{area_id}, $body->{name}, id => $body->{id}); + push @bodies, $body_obj; + $body_ids{$body->{area_id}} = $body_obj->id; +} + # Let's make some contacts to send things to! -FixMyStreet::App->model('DB::Contact')->search( { - email => { 'like', '%example.com' }, -} )->delete; -my $contact1 = FixMyStreet::App->model('DB::Contact')->find_or_create( { - %contact_params, - body_id => 2651, # Edinburgh +my $contact1 = $mech->create_contact_ok( + body_id => $body_ids{2651}, # Edinburgh category => 'Street lighting', email => 'highways@example.com', -} ); -my $contact2 = FixMyStreet::App->model('DB::Contact')->find_or_create( { - %contact_params, - body_id => 2226, # Gloucestershire +); +my $contact2 = $mech->create_contact_ok( + body_id => $body_ids{2226}, # Gloucestershire category => 'Potholes', email => 'potholes@example.com', -} ); -my $contact3 = FixMyStreet::App->model('DB::Contact')->find_or_create( { - %contact_params, - body_id => 2326, # Cheltenham +); +my $contact3 = $mech->create_contact_ok( + body_id => $body_ids{2326}, # Cheltenham category => 'Trees', email => 'trees@example.com', -} ); -my $contact4 = FixMyStreet::App->model('DB::Contact')->find_or_create( { - %contact_params, - body_id => 2482, # Bromley +); +my $contact4 = $mech->create_contact_ok( + body_id => $body_ids{2482}, # Bromley category => 'Trees', email => 'trees@example.com', -} ); -my $contact5 = FixMyStreet::App->model('DB::Contact')->find_or_create( { - %contact_params, - body_id => 2651, # Edinburgh +); +my $contact5 = $mech->create_contact_ok( + body_id => $body_ids{2651}, # Edinburgh category => 'Trees', email => 'trees@example.com', -} ); -my $contact6 = FixMyStreet::App->model('DB::Contact')->find_or_create( { - %contact_params, - body_id => 2434, # Lichfield +); +my $contact6 = $mech->create_contact_ok( + body_id => $body_ids{2333}, # Hart category => 'Trees', email => 'trees@example.com', -} ); -my $contact7 = FixMyStreet::App->model('DB::Contact')->find_or_create( { - %contact_params, - body_id => 2240, # Lichfield - category => 'Street lighting', +); +my $contact7 = $mech->create_contact_ok( + body_id => $body_ids{2227}, # Hampshire + category => 'Street lighting', email => 'highways@example.com', -} ); -ok $contact1, "created test contact 1"; -ok $contact2, "created test contact 2"; -ok $contact3, "created test contact 3"; -ok $contact4, "created test contact 4"; -ok $contact5, "created test contact 5"; -ok $contact6, "created test contact 6"; -ok $contact7, "created test contact 7"; +); +my $contact8 = $mech->create_contact_ok( + body_id => $body_ids{2504}, + category => 'Street lighting', + email => 'highways@example.com' +); # test that the various bit of form get filled in and errors correctly # generated. foreach my $test ( { msg => 'all fields empty', + pc => 'OX1 3DH', + fields => { + title => '', + detail => '', + photo1 => '', + photo2 => '', + photo3 => '', + name => '', + may_show_name => '1', + email => '', + phone => '', + password_sign_in => '', + password_register => '', + remember_me => undef, + }, + changes => {}, + errors => [ + 'Please enter a subject', + 'Please enter some details', + # No category error, as no categories for Oxon at all, so is skipped + 'Please enter your email', + 'Please enter your name', + ], + }, + { + msg => 'all fields empty, bad category', + pc => 'GL50 2PR', + fields => { + title => '', + detail => '', + photo1 => '', + photo2 => '', + photo3 => '', + name => '', + may_show_name => '1', + email => '', + phone => '', + category => 'Something bad', + password_sign_in => '', + password_register => '', + remember_me => undef, + }, + changes => { + category => '-- Pick a category --', + }, + errors => [ + 'Please enter a subject', + 'Please enter some details', + 'Please choose a category', + 'Please enter your email', + 'Please enter your name', + ], + }, + { + msg => 'all fields empty except category', pc => 'SW1A 1AA', fields => { title => '', detail => '', - photo => '', + photo1 => '', + photo2 => '', + photo3 => '', name => '', may_show_name => '1', email => '', @@ -120,7 +184,9 @@ foreach my $test ( fields => { title => '', detail => '', - photo => '', + photo1 => '', + photo2 => '', + photo3 => '', name => '', may_show_name => undef, email => '', @@ -144,7 +210,9 @@ foreach my $test ( fields => { title => '', detail => '', - photo => '', + photo1 => '', + photo2 => '', + photo3 => '', name => 'Bob Jones', may_show_name => undef, email => '', @@ -167,7 +235,9 @@ foreach my $test ( fields => { title => '', detail => '', - photo => '', + photo1 => '', + photo2 => '', + photo3 => '', name => 'Bob Jones', may_show_name => '1', email => '', @@ -190,7 +260,9 @@ foreach my $test ( fields => { title => "DOG SHIT\r\nON WALLS", detail => "on this portakabin -\r\n\r\nmore of a portaloo HEH!!", - photo => '', + photo1 => '', + photo2 => '', + photo3 => '', name => 'Bob Jones', may_show_name => '1', email => '', @@ -213,7 +285,9 @@ foreach my $test ( fields => { title => 'Test title', detail => 'Test detail', - photo => '', + photo1 => '', + photo2 => '', + photo3 => '', name => 'DUDE', may_show_name => '1', email => '', @@ -235,7 +309,9 @@ foreach my $test ( fields => { title => 'Test title', detail => 'Test detail', - photo => '', + photo1 => '', + photo2 => '', + photo3 => '', name => 'anonymous', may_show_name => '1', email => '', @@ -257,7 +333,9 @@ foreach my $test ( fields => { title => 'Test title', detail => 'Test detail', - photo => '', + photo1 => '', + photo2 => '', + photo3 => '', name => 'Joe Smith', may_show_name => '1', email => 'not an email', @@ -276,7 +354,9 @@ foreach my $test ( fields => { title => " Test title ", detail => " first line \n\n second\nline\n\n ", - photo => '', + photo1 => '', + photo2 => '', + photo3 => '', name => '', may_show_name => '1', email => '', @@ -301,7 +381,9 @@ foreach my $test ( fields => { title => '', detail => '', - photo => '', + photo1 => '', + photo2 => '', + photo3 => '', name => ' Bob Jones ', may_show_name => '1', email => ' BOB @ExAmplE.COM ', @@ -323,7 +405,9 @@ foreach my $test ( fields => { title => 'Title', detail => 'Detail', - photo => [ [ undef, 'bad.txt', Content => 'This is not a JPEG', Content_Type => 'text/plain' ], 1 ], + photo1 => [ [ undef, 'bad.txt', Content => 'This is not a JPEG', Content_Type => 'text/plain' ], 1 ], + photo2 => '', + photo3 => '', name => 'Bob Jones', may_show_name => '1', email => 'bob@example.com', @@ -334,9 +418,9 @@ foreach my $test ( remember_me => undef, }, changes => { - photo => '', + photo1 => '', }, - errors => [ "Please upload a JPEG image only" ], + errors => [ "Please upload an image only" ], }, { msg => 'bad photo upload gives error', @@ -344,7 +428,9 @@ foreach my $test ( fields => { title => 'Title', detail => 'Detail', - photo => [ [ undef, 'fake.jpeg', Content => 'This is not a JPEG', Content_Type => 'image/jpeg' ], 1 ], + photo1 => [ [ undef, 'fake.jpeg', Content => 'This is not a JPEG', Content_Type => 'image/jpeg' ], 1 ], + photo2 => '', + photo3 => '', name => 'Bob Jones', may_show_name => '1', email => 'bob@example.com', @@ -355,9 +441,9 @@ foreach my $test ( remember_me => undef, }, changes => { - photo => '', + photo1 => '', }, - errors => [ "That image doesn't appear to have uploaded correctly (Please upload a JPEG image only ), please try again." ], + errors => [ "That image doesn't appear to have uploaded correctly (Please upload an image only ), please try again." ], }, { msg => 'photo with octet-stream gets through okay', @@ -365,7 +451,9 @@ foreach my $test ( fields => { title => '', detail => 'Detail', - photo => [ [ $sample_file, undef, Content_Type => 'application/octet-stream' ], 1 ], + photo1 => [ [ $sample_file, undef, Content_Type => 'application/octet-stream' ], 1 ], + photo2 => '', + photo3 => '', name => 'Bob Jones', may_show_name => '1', email => 'bob@example.com', @@ -376,7 +464,7 @@ foreach my $test ( remember_me => undef, }, changes => { - photo => '', + photo1 => '', }, errors => [ "Please enter a subject" ], }, @@ -386,20 +474,25 @@ foreach my $test ( $mech->get_ok('/around'); # submit initial pc form - $mech->submit_form_ok( { with_fields => { pc => $test->{pc} } }, - "submit location" ); - is_deeply $mech->page_errors, [], "no errors for pc '$test->{pc}'"; - - # click through to the report page - $mech->follow_link_ok( { text_regex => qr/skip this step/i, }, - "follow 'skip this step' link" ); - - # submit the main form - $mech->submit_form_ok( { with_fields => $test->{fields} }, - "submit form" ); + FixMyStreet::override_config { + ALLOWED_COBRANDS => [ { fixmystreet => '.' } ], + MAPIT_URL => 'http://mapit.uk/', + }, sub { + $mech->submit_form_ok( { with_fields => { pc => $test->{pc} } }, + "submit location" ); + is_deeply $mech->page_errors, [], "no errors for pc '$test->{pc}'"; + + # click through to the report page + $mech->follow_link_ok( { text_regex => qr/skip this step/i, }, + "follow 'skip this step' link" ); + + # submit the main form + $mech->submit_form_ok( { with_fields => $test->{fields} }, + "submit form" ); + }; # check that we got the errors expected - is_deeply $mech->page_errors, $test->{errors}, "check errors"; + is_deeply [ sort @{$mech->page_errors} ], [ sort @{$test->{errors}} ], "check errors"; # check that fields have changed as expected my $new_values = { @@ -454,30 +547,35 @@ foreach my $test ( # submit initial pc form $mech->get_ok('/around'); - $mech->submit_form_ok( { with_fields => { pc => 'EH1 1BB', } }, - "submit location" ); + FixMyStreet::override_config { + ALLOWED_COBRANDS => [ { fixmystreet => '.' } ], + MAPIT_URL => 'http://mapit.uk/', + }, sub { + $mech->submit_form_ok( { with_fields => { pc => 'EH1 1BB', } }, + "submit location" ); - # click through to the report page - $mech->follow_link_ok( { text_regex => qr/skip this step/i, }, - "follow 'skip this step' link" ); + # click through to the report page + $mech->follow_link_ok( { text_regex => qr/skip this step/i, }, + "follow 'skip this step' link" ); - $mech->submit_form_ok( - { - button => 'submit_register', - with_fields => { - title => 'Test Report', - detail => 'Test report details.', - photo => '', - name => 'Joe Bloggs', - may_show_name => '1', - email => 'test-1@example.com', - phone => '07903 123 456', - category => 'Street lighting', - password_register => $test->{password} ? 'secret' : '', - } - }, - "submit good details" - ); + $mech->submit_form_ok( + { + button => 'submit_register', + with_fields => { + title => 'Test Report', + detail => 'Test report details.', + photo1 => '', + name => 'Joe Bloggs', + may_show_name => '1', + email => 'test-1@example.com', + phone => '07903 123 456', + category => 'Street lighting', + password_register => $test->{password} ? 'secret' : '', + } + }, + "submit good details" + ); + }; # check that we got the errors expected is_deeply $mech->page_errors, [], "check there were no errors"; @@ -503,15 +601,14 @@ foreach my $test ( is $mech->get( '/report/' . $report->id )->code, 404, "report not found"; # Check the report has been assigned appropriately - is $report->council, 2651; + is $report->bodies_str, $body_ids{2651}; # receive token my $email = $mech->get_email; ok $email, "got an email"; - like $email->body, qr/confirm the problem/i, "confirm the problem"; + like $mech->get_text_body_from_email($email), qr/confirm that you want to send your\s+report/i, "confirm the problem"; - my ($url) = $email->body =~ m{(http://\S+)}; - ok $url, "extracted confirm url '$url'"; + my $url = $mech->get_link_from_email($email); # confirm token $mech->get_ok($url); @@ -555,8 +652,7 @@ subtest "test password errors for a user who is signing in as they report" => su # check that the user does not exist my $test_email = 'test-2@example.com'; - my $user = FixMyStreet::App->model('DB::User')->find_or_create( { email => $test_email } ); - ok $user, "test user does exist"; + my $user = $mech->create_user_ok($test_email); # setup the user. ok $user->update( { @@ -567,27 +663,32 @@ subtest "test password errors for a user who is signing in as they report" => su # submit initial pc form $mech->get_ok('/around'); - $mech->submit_form_ok( { with_fields => { pc => 'EH1 1BB', } }, - "submit location" ); + FixMyStreet::override_config { + ALLOWED_COBRANDS => [ { fixmystreet => '.' } ], + MAPIT_URL => 'http://mapit.uk/', + }, sub { + $mech->submit_form_ok( { with_fields => { pc => 'EH1 1BB', } }, + "submit location" ); - # click through to the report page - $mech->follow_link_ok( { text_regex => qr/skip this step/i, }, - "follow 'skip this step' link" ); + # click through to the report page + $mech->follow_link_ok( { text_regex => qr/skip this step/i, }, + "follow 'skip this step' link" ); - $mech->submit_form_ok( - { - button => 'submit_sign_in', - with_fields => { - title => 'Test Report', - detail => 'Test report details.', - photo => '', - email => 'test-2@example.com', - password_sign_in => 'secret1', - category => 'Street lighting', - } - }, - "submit with wrong password" - ); + $mech->submit_form_ok( + { + button => 'submit_sign_in', + with_fields => { + title => 'Test Report', + detail => 'Test report details.', + photo1 => '', + email => 'test-2@example.com', + password_sign_in => 'secret1', + category => 'Street lighting', + } + }, + "submit with wrong password" + ); + }; # check that we got the errors expected is_deeply $mech->page_errors, [ @@ -597,13 +698,13 @@ subtest "test password errors for a user who is signing in as they report" => su subtest "test report creation for a user who is signing in as they report" => sub { $mech->log_out_ok; + $mech->cookie_jar({}); $mech->clear_emails_ok; # check that the user does not exist my $test_email = 'test-2@example.com'; - my $user = FixMyStreet::App->model('DB::User')->find_or_create( { email => $test_email } ); - ok $user, "test user does exist"; + my $user = $mech->create_user_ok($test_email); # setup the user. ok $user->update( { @@ -614,52 +715,54 @@ subtest "test report creation for a user who is signing in as they report" => su # submit initial pc form $mech->get_ok('/around'); - $mech->submit_form_ok( { with_fields => { pc => 'EH1 1BB', } }, - "submit location" ); + FixMyStreet::override_config { + ALLOWED_COBRANDS => [ { fixmystreet => '.' } ], + MAPIT_URL => 'http://mapit.uk/', + }, sub { + $mech->submit_form_ok( { with_fields => { pc => 'EH1 1BB', } }, + "submit location" ); - # click through to the report page - $mech->follow_link_ok( { text_regex => qr/skip this step/i, }, - "follow 'skip this step' link" ); + # click through to the report page + $mech->follow_link_ok( { text_regex => qr/skip this step/i, }, + "follow 'skip this step' link" ); - $mech->submit_form_ok( - { - button => 'submit_sign_in', - with_fields => { - title => 'Test Report', - detail => 'Test report details.', - photo => '', - email => 'test-2@example.com', - password_sign_in => 'secret2', - category => 'Street lighting', - } - }, - "submit good details" - ); + $mech->submit_form_ok( + { + button => 'submit_sign_in', + with_fields => { + title => 'Test Report', + detail => 'Test report details.', + photo1 => '', + email => 'test-2@example.com', + password_sign_in => 'secret2', + category => 'Street lighting', + } + }, + "submit good details" + ); - # check that we got the errors expected - is_deeply $mech->page_errors, [ - 'You have successfully signed in; please check and confirm your details are accurate:', - ], "check there were errors"; + # check that we got the message expected + $mech->content_contains( 'You have successfully signed in; please check and confirm your details are accurate:' ); - # Now submit with a name - $mech->submit_form_ok( - { - with_fields => { - name => 'Joe Bloggs', - } - }, - "submit good details" - ); + # Now submit with a name + $mech->submit_form_ok( + { + with_fields => { + name => 'Joe Bloggs', + } + }, + "submit good details" + ); + }; # find the report my $report = $user->problems->first; ok $report, "Found the report"; - # check that we got redirected to /report/ - is $mech->uri->path, "/report/" . $report->id, "redirected to report page"; + $mech->content_contains('Thank you for reporting this issue'); # Check the report has been assigned appropriately - is $report->council, 2651; + is $report->bodies_str, $body_ids{2651}; # check that no emails have been sent $mech->email_count_is(0); @@ -708,52 +811,58 @@ foreach my $test ( # submit initial pc form $mech->get_ok('/around'); - $mech->submit_form_ok( { with_fields => { pc => 'GL50 2PR', } }, - "submit location" ); - - # click through to the report page - $mech->follow_link_ok( { text_regex => qr/skip this step/i, }, - "follow 'skip this step' link" ); - - # check that the fields are correctly prefilled - is_deeply( - $mech->visible_form_values, - { - title => '', - detail => '', - may_show_name => '1', - name => 'Test User', - phone => '01234 567 890', - photo => '', - category => '-- Pick a category --', - }, - "user's details prefilled" - ); - - $mech->submit_form_ok( - { - with_fields => { - title => "Test Report at café", - detail => 'Test report details.', - photo => '', - name => 'Joe Bloggs', + FixMyStreet::override_config { + ALLOWED_COBRANDS => [ { fixmystreet => '.' } ], + MAPIT_URL => 'http://mapit.uk/', + }, sub { + $mech->submit_form_ok( { with_fields => { pc => 'GL50 2PR', } }, + "submit location" ); + + # click through to the report page + $mech->follow_link_ok( { text_regex => qr/skip this step/i, }, + "follow 'skip this step' link" ); + + # check that the fields are correctly prefilled + is_deeply( + $mech->visible_form_values, + { + title => '', + detail => '', may_show_name => '1', - phone => '07903 123 456', - category => $test->{category}, - } - }, - "submit good details" - ); + name => 'Test User', + phone => '01234 567 890', + photo1 => '', + photo2 => '', + photo3 => '', + category => '-- Pick a category --', + }, + "user's details prefilled" + ); + + $mech->submit_form_ok( + { + with_fields => { + title => "Test Report at café", + detail => 'Test report details.', + photo1 => '', + name => 'Joe Bloggs', + may_show_name => '1', + phone => '07903 123 456', + category => $test->{category}, + } + }, + "submit good details" + ); + }; # find the report my $report = $user->problems->first; ok $report, "Found the report"; # Check the report has been assigned appropriately - is $report->council, $test->{council}; + is $report->bodies_str, $body_ids{$test->{council}}; - # check that we got redirected to /report/ - is $mech->uri->path, "/report/" . $report->id, "redirected to report page"; + $mech->content_contains('Thank you for reporting this issue'); # check that no emails have been sent $mech->email_count_is(0); @@ -795,34 +904,38 @@ subtest "test report creation for a category that is non public" => sub { # check that the user does not exist my $test_email = 'test-2@example.com'; - my $user = FixMyStreet::App->model('DB::User')->find_or_create( { email => $test_email } ); - ok $user, "test user does exist"; + my $user = $mech->create_user_ok($test_email); $contact1->update( { non_public => 1 } ); # submit initial pc form $mech->get_ok('/around'); - $mech->submit_form_ok( { with_fields => { pc => 'EH1 1BB', } }, - "submit location" ); + FixMyStreet::override_config { + ALLOWED_COBRANDS => [ { fixmystreet => '.' } ], + MAPIT_URL => 'http://mapit.uk/', + }, sub { + $mech->submit_form_ok( { with_fields => { pc => 'EH1 1BB', } }, + "submit location" ); - # click through to the report page - $mech->follow_link_ok( { text_regex => qr/skip this step/i, }, - "follow 'skip this step' link" ); + # click through to the report page + $mech->follow_link_ok( { text_regex => qr/skip this step/i, }, + "follow 'skip this step' link" ); - $mech->submit_form_ok( - { - button => 'submit_register', - with_fields => { - title => 'Test Report', - detail => 'Test report details.', - photo => '', - email => 'test-2@example.com', - name => 'Joe Bloggs', - category => 'Street lighting', - } - }, - "submit good details" - ); + $mech->submit_form_ok( + { + button => 'submit_register', + with_fields => { + title => 'Test Report', + detail => 'Test report details.', + photo1 => '', + email => 'test-2@example.com', + name => 'Joe Bloggs', + category => 'Street lighting', + } + }, + "submit good details" + ); + }; # find the report my $report = $user->problems->first; @@ -833,10 +946,9 @@ subtest "test report creation for a category that is non public" => sub { my $email = $mech->get_email; ok $email, "got an email"; - like $email->body, qr/confirm the problem/i, "confirm the problem"; + like $mech->get_text_body_from_email($email), qr/confirm that you want to send your\s+report/i, "confirm the problem"; - my ($url) = $email->body =~ m{(http://\S+)}; - ok $url, "extracted confirm url '$url'"; + my $url = $mech->get_link_from_email($email); # confirm token $mech->get_ok($url); @@ -858,8 +970,24 @@ subtest "test report creation for a category that is non public" => sub { $contact2->category( "Pothol\xc3\xa9s" ); $contact2->update; -$mech->get_ok( '/report/new/ajax?latitude=' . $saved_lat . '&longitude=' . $saved_lon ); + +my $extra_details; +FixMyStreet::override_config { + ALLOWED_COBRANDS => [ { fixmystreet => '.' } ], + MAPIT_URL => 'http://mapit.uk/', +}, sub { + $extra_details = $mech->get_ok_json( '/report/new/ajax?latitude=' . $saved_lat . '&longitude=' . $saved_lon ); +}; $mech->content_contains( "Pothol\xc3\xa9s" ); +ok !$extra_details->{titles_list}, 'Non Bromley does not send back list of titles'; + +FixMyStreet::override_config { + ALLOWED_COBRANDS => [ { fixmystreet => '.' } ], + MAPIT_URL => 'http://mapit.uk/', +}, sub { + $extra_details = $mech->get_ok_json( '/report/new/ajax?latitude=51.4021&longitude=0.01578'); +}; +ok $extra_details->{titles_list}, 'Bromley sends back list of titles'; #### test uploading an image @@ -867,18 +995,22 @@ $mech->content_contains( "Pothol\xc3\xa9s" ); #### possibly manual testing # create report without using map -# create report by clicking on may with javascript off +# create report by clicking on map with javascript off # create report with images off subtest "check that a lat/lon off coast leads to /around" => sub { my $off_coast_latitude = 50.78301; my $off_coast_longitude = -0.646929; - $mech->get_ok( # - "/report/new" - . "?latitude=$off_coast_latitude" - . "&longitude=$off_coast_longitude" - ); + FixMyStreet::override_config { + MAPIT_URL => 'http://mapit.uk/', + }, sub { + $mech->get_ok( # + "/report/new" + . "?latitude=$off_coast_latitude" + . "&longitude=$off_coast_longitude" + ); + }; is $mech->uri->path, '/around', "redirected to '/around'"; @@ -892,15 +1024,15 @@ subtest "check that a lat/lon off coast leads to /around" => sub { for my $test ( { desc => 'user title not set if not bromley problem', - host => 'http://www.fixmystreet.com', - postcode => 'EH99 1SP', + host => 'www.fixmystreet.com', + postcode => 'EH1 1BB', fms_extra_title => '', - extra => undef, + extra => [], user_title => undef, }, { desc => 'title shown for bromley problem on main site', - host => 'http://www.fixmystreet.com', + host => 'www.fixmystreet.com', postcode => 'BR1 3UH', fms_extra_title => 'MR', extra => [ @@ -915,7 +1047,7 @@ for my $test ( { desc => 'title, first and last name shown for bromley problem on cobrand', - host => 'http://bromley.fixmystreet.com', + host => 'bromley.fixmystreet.com', postcode => 'BR1 3UH', first_name => 'Test', last_name => 'User', @@ -942,9 +1074,10 @@ for my $test ( ) { subtest $test->{desc} => sub { - if ( $test->{host} =~ /bromley/ && !FixMyStreet::Cobrand->exists('bromley') ) { - plan skip_all => 'Skipping Bromley tests without Bromley cobrand'; - } + my $override = { + ALLOWED_COBRANDS => [ $test->{host} =~ /bromley/ ? 'bromley' : 'fixmystreet' ], + MAPIT_URL => 'http://mapit.uk/', + }; $mech->host( $test->{host} ); @@ -952,12 +1085,14 @@ for my $test ( $mech->clear_emails_ok; $mech->get_ok('/'); - $mech->submit_form_ok( { with_fields => { pc => $test->{postcode}, } }, - "submit location" ); - $mech->follow_link_ok( - { text_regex => qr/skip this step/i, }, - "follow 'skip this step' link" - ); + FixMyStreet::override_config $override, sub { + $mech->submit_form_ok( { with_fields => { pc => $test->{postcode}, } }, + "submit location" ); + $mech->follow_link_ok( + { text_regex => qr/skip this step/i, }, + "follow 'skip this step' link" + ); + }; my $fields = $mech->visible_form_values('mapSkippedForm'); if ( $test->{fms_extra_title} ) { @@ -980,7 +1115,7 @@ for my $test ( my $submission_fields = { title => "Test Report", detail => 'Test report details.', - photo => '', + photo1 => '', email => 'firstlast@example.com', may_show_name => '1', phone => '07903 123 456', @@ -999,15 +1134,16 @@ for my $test ( $submission_fields->{name} = 'Test User'; } - $mech->submit_form_ok( { with_fields => $submission_fields }, - "submit good details" ); + FixMyStreet::override_config $override, sub { + $mech->submit_form_ok( { with_fields => $submission_fields }, + "submit good details" ); + }; my $email = $mech->get_email; ok $email, "got an email"; - like $email->body, qr/confirm the problem/i, "confirm the problem"; + like $mech->get_text_body_from_email($email), qr/confirm that you want to send your\s+report/i, "confirm the problem"; - my ($url) = $email->body =~ m{(https?://\S+)}; - ok $url, "extracted confirm url '$url'"; + my $url = $mech->get_link_from_email($email); # confirm token in order to update the user details $mech->get_ok($url); @@ -1018,7 +1154,7 @@ for my $test ( my $report = $user->problems->first; ok $report, "Found the report"; - my $extras = $report->extra; + my $extras = $report->get_extra_fields; is $user->title, $test->{'user_title'}, 'user title correct'; is_deeply $extras, $test->{extra}, 'extra contains correct values'; @@ -1030,7 +1166,7 @@ for my $test ( subtest 'user title not reset if no user title in submission' => sub { $mech->log_out_ok; - $mech->host( 'http://fixmystreet.com' ); + $mech->host( 'www.fixmystreet.com' ); my $user = $mech->log_in_ok( 'userwithtitle@example.com' ); @@ -1047,7 +1183,7 @@ subtest 'user title not reset if no user title in submission' => sub { my $submission_fields = { title => "Test Report", detail => 'Test report details.', - photo => '', + photo1 => '', name => 'Has Title', may_show_name => '1', phone => '07903 123 456', @@ -1055,18 +1191,23 @@ subtest 'user title not reset if no user title in submission' => sub { }; $mech->get_ok('/'); - $mech->submit_form_ok( { with_fields => { pc => 'EH99 1SP', } }, - "submit location" ); - $mech->follow_link_ok( - { text_regex => qr/skip this step/i, }, - "follow 'skip this step' link" - ); - - my $fields = $mech->visible_form_values('mapSkippedForm'); - ok !exists( $fields->{fms_extra_title} ), 'user title field not displayed'; + FixMyStreet::override_config { + ALLOWED_COBRANDS => [ { fixmystreet => '.' } ], + MAPIT_URL => 'http://mapit.uk/', + }, sub { + $mech->submit_form_ok( { with_fields => { pc => 'EH1 1BB', } }, + "submit location" ); + $mech->follow_link_ok( + { text_regex => qr/skip this step/i, }, + "follow 'skip this step' link" + ); + + my $fields = $mech->visible_form_values('mapSkippedForm'); + ok !exists( $fields->{fms_extra_title} ), 'user title field not displayed'; - $mech->submit_form_ok( { with_fields => $submission_fields }, - "submit good details" ); + $mech->submit_form_ok( { with_fields => $submission_fields }, + "submit good details" ); + }; $user->discard_changes; my $report = $user->problems->first; @@ -1075,79 +1216,427 @@ subtest 'user title not reset if no user title in submission' => sub { is $user->title, 'MR', 'User title unchanged'; }; -SKIP: { - skip( "Need 'lichfielddc' in ALLOWED_COBRANDS config", 100 ) - unless FixMyStreet::Cobrand->exists('lichfielddc'); +subtest "test Hart" => sub { + for my $test ( + { + desc => 'confirm link for cobrand council in two tier cobrand links to cobrand site', + category => 'Trees', + council => 2333, + national => 0, + button => 'submit_register', + }, + { + desc => 'confirm link for non cobrand council in two tier cobrand links to national site', + category => 'Street Lighting', + council => 2227, + national => 1, + button => 'submit_register', + }, + { + desc => 'confirmation page for cobrand council in two tier cobrand links to cobrand site', + category => 'Trees', + council => 2333, + national => 0, + confirm => 1, + }, + { + desc => 'confirmation page for non cobrand council in two tier cobrand links to national site', + category => 'Street Lighting', + council => 2227, + national => 1, + confirm => 1, + }, + ) { + subtest $test->{ desc } => sub { + my $test_email = 'test-22@example.com'; + $mech->host( 'hart.fixmystreet.com' ); + $mech->clear_emails_ok; + $mech->log_out_ok; + + my $user = $mech->log_in_ok($test_email) if $test->{confirm}; + + FixMyStreet::override_config { + ALLOWED_COBRANDS => [ 'hart', 'fixmystreet' ], + BASE_URL => 'http://www.fixmystreet.com', + MAPIT_URL => 'http://mapit.uk/', + }, sub { + $mech->get_ok('/around'); + $mech->content_contains( "Hart Council" ); + $mech->submit_form_ok( { with_fields => { pc => 'GU51 4AE' } }, "submit location" ); + $mech->follow_link_ok( { text_regex => qr/skip this step/i, }, "follow 'skip this step' link" ); + my %optional_fields = $test->{confirm} ? () : + ( email => $test_email, phone => '07903 123 456' ); + + # we do this as otherwise test::www::mechanize::catalyst + # goes to the value set in ->host above irregardless and + # that is a 404. It works but it is not pleasant. + $mech->clear_host if $test->{confirm} && $test->{national}; + $mech->submit_form_ok( + { + button => $test->{button}, + with_fields => { + title => 'Test Report', + detail => 'Test report details.', + photo1 => '', + name => 'Joe Bloggs', + may_show_name => '1', + category => $test->{category}, + %optional_fields + } + }, + "submit good details" + ); + }; + is_deeply $mech->page_errors, [], "check there were no errors"; + + # check that the user has been created/ not changed + $user = + FixMyStreet::App->model('DB::User')->find( { email => $test_email } ); + ok $user, "user found"; + + # find the report + my $report = $user->problems->first; + ok $report, "Found the report"; + + # Check the report has been assigned appropriately + is $report->bodies_str, $body_ids{$test->{council}}; + + if ( $test->{confirm} ) { + is $mech->uri->path, "/report/new"; + my $base = 'www.fixmystreet.com'; + $base = "hart.fixmystreet.com" unless $test->{national}; + $mech->content_contains("$base/report/" . $report->id, "links to correct site"); + } else { + # receive token + my $email = $mech->get_email; + ok $email, "got an email"; + my $body = $mech->get_text_body_from_email($email); + like $body, qr/to confirm that you want to send your/i, "confirm the problem"; + + # does it reference the fact that this report hasn't been sent to Hart? + if ( $test->{national} ) { + like $body, qr/Hart Council is not responsible for this type/i, "mentions report hasn't gone to Hart"; + } else { + unlike $body, qr/Hart Council is not responsible for this type/i, "doesn't mention report hasn't gone to Hart"; + } - my $test_email = 'test-22@example.com'; - $mech->host( 'http://lichfielddc.fixmystreet.com/' ); - $mech->clear_emails_ok; - $mech->log_out_ok; + my $url = $mech->get_link_from_email($email); + + # confirm token + FixMyStreet::override_config { + ALLOWED_COBRANDS => [ 'hart', 'fixmystreet' ], + BASE_URL => 'http://www.fixmystreet.com', + }, sub { + $mech->get_ok($url); + }; + + my $base = 'www.fixmystreet.com'; + $base = 'hart.fixmystreet.com' unless $test->{national}; + $mech->content_contains( $base . '/report/' . + $report->id, 'confirm page links to correct site' ); + + if ( $test->{national} ) { + # Shouldn't be found, as it was a county problem + FixMyStreet::override_config { + ALLOWED_COBRANDS => [ 'hart', 'fixmystreet' ], + }, sub { + is $mech->get( '/report/' . $report->id )->code, 404, "report not found"; + }; + + # But should be on the main site + $mech->host( 'www.fixmystreet.com' ); + } + FixMyStreet::override_config { + ALLOWED_COBRANDS => [ 'hart', 'fixmystreet' ], + }, sub { + $mech->get_ok( '/report/' . $report->id ); + }; + } - $mech->get_ok('/around'); - $mech->content_contains( "Lichfield District Council FixMyStreet" ); - $mech->submit_form_ok( { with_fields => { pc => 'WS13 7RD' } }, "submit location" ); - $mech->follow_link_ok( { text_regex => qr/skip this step/i, }, "follow 'skip this step' link" ); - $mech->submit_form_ok( - { - button => 'submit_register', - with_fields => { - title => 'Test Report', - detail => 'Test report details.', - photo => '', - name => 'Joe Bloggs', - may_show_name => '1', - email => $test_email, - phone => '07903 123 456', - category => 'Street lighting', + $report->discard_changes; + is $report->state, 'confirmed', "Report is now confirmed"; + + is $report->name, 'Joe Bloggs', 'name updated correctly'; + + $mech->delete_user($user); + }; + } +}; + +subtest "categories from deleted bodies shouldn't be visible for new reports" => sub { + FixMyStreet::override_config { + ALLOWED_COBRANDS => [ { fixmystreet => '.' } ], + MAPIT_URL => 'http://mapit.uk/', + }, sub { + $mech->get_ok('/report/new/ajax?latitude=51.896268&longitude=-2.093063'); # Cheltenham + ok $mech->content_contains( $contact3->category ); + + # Delete the body which the contact belongs to. + $contact3->body->update( { deleted => 1 } ); + + $mech->get_ok('/report/new/ajax?latitude=51.896268&longitude=-2.093063'); # Cheltenham + ok $mech->content_lacks( $contact3->category ); + + $contact3->body->update( { deleted => 0 } ); + }; +}; + +subtest "unresponsive body handling works" => sub { + FixMyStreet::override_config { + ALLOWED_COBRANDS => [ { fixmystreet => '.' } ], + MAPIT_URL => 'http://mapit.uk/', + }, sub { + # Test body-level send method + my $old_send = $contact1->body->send_method; + $contact1->body->update( { send_method => 'Refused' } ); + $mech->get_ok('/report/new/ajax?latitude=55.952055&longitude=-3.189579'); # Edinburgh + my $body_id = $contact1->body->id; + ok $mech->content_like( qr{Edinburgh.*accept reports.*/unresponsive\?body=$body_id} ); + + my $test_email = 'test-2@example.com'; + $mech->log_out_ok; + $mech->get_ok('/around'); + $mech->submit_form_ok( { with_fields => { pc => 'EH1 1BB', } }, "submit location" ); + $mech->follow_link_ok( { text_regex => qr/skip this step/i, }, "follow 'skip this step' link" ); + $mech->submit_form_ok( + { + with_fields => { + title => "Test Report at café", + detail => 'Test report details.', + photo1 => '', + name => 'Joe Bloggs', + email => $test_email, + may_show_name => '1', + phone => '07903 123 456', + category => 'Trees', + } + }, + "submit good details" + ); + + my $user = FixMyStreet::App->model('DB::User')->find( { email => $test_email } ); + ok $user, "test user does exist"; + + my $report = $user->problems->first; + ok $report, "Found the report"; + is $report->bodies_str, undef, "Report not going anywhere"; + + like $mech->get_text_body_from_email, qr/despite not being sent/i, "correct email sent"; + + $user->problems->delete; + $mech->clear_emails_ok; + + # Make sure the same behaviour occurs for reports from the mobile app + $mech->log_out_ok; + $mech->post_ok( '/report/new/mobile', { + title => "Test Report at café", + detail => 'Test report details.', + photo1 => '', + name => 'Joe Bloggs', + email => $test_email, + may_show_name => '1', + phone => '07903 123 456', + category => 'Trees', + service => 'iOS', + lat => 55.952055, + lon => -3.189579, + pc => '', + used_map => '1', + submit_register => '1', + password_register => '', + }); + my $res = $mech->response; + ok $res->header('Content-Type') =~ m{^application/json\b}, 'response should be json'; + + $user = FixMyStreet::App->model('DB::User')->find( { email => $test_email } ); + ok $user, "test user does exist"; + + $report = $user->problems->first; + ok $report, "Found the report"; + is $report->bodies_str, undef, "Report not going anywhere"; + + like $mech->get_text_body_from_email, qr/despite not being sent/i, "correct email sent"; + + $user->problems->delete; + $mech->clear_emails_ok; + + $contact1->body->update( { send_method => $old_send } ); + + # And test per-category refusing + my $old_email = $contact3->email; + $contact3->update( { email => 'REFUSED' } ); + $mech->get_ok('/report/new/category_extras?category=Trees&latitude=51.896268&longitude=-2.093063'); + ok $mech->content_like( qr/Cheltenham.*Trees.*unresponsive.*category=Trees/ ); + + $mech->get_ok('/around'); + $mech->submit_form_ok( { with_fields => { pc => 'GL50 2PR', } }, "submit location" ); + $mech->follow_link_ok( { text_regex => qr/skip this step/i, }, "follow 'skip this step' link" ); + $mech->submit_form_ok( + { + with_fields => { + title => "Test Report at café", + detail => 'Test report details.', + photo1 => '', + name => 'Joe Bloggs', + email => $test_email, + may_show_name => '1', + phone => '07903 123 456', + category => 'Trees', + } + }, + "submit good details" + ); + + $report = $user->problems->first; + ok $report, "Found the report"; + is $report->bodies_str, undef, "Report not going anywhere"; + + $contact3->update( { email => $old_email } ); + $mech->delete_user($user); + }; +}; + +subtest "unresponsive body page works" => sub { + FixMyStreet::override_config { + ALLOWED_COBRANDS => [ { fixmystreet => '.' } ], + }, sub { + my $old_send = $contact1->body->send_method; + my $body_id = $contact1->body->id; + my $url = "/unresponsive?body=$body_id"; + is $mech->get($url)->code, 404, "page not found"; + $contact1->body->update( { send_method => 'Refused' } ); + $mech->get_ok($url); + $mech->content_contains('Edinburgh'); + $contact1->body->update( { send_method => $old_send } ); + + my $old_email = $contact3->email; + $body_id = $contact3->body->id; + $url = "/unresponsive?body=$body_id;category=Trees"; + is $mech->get($url)->code, 404, "page not found"; + $contact3->update( { email => 'REFUSED' } ); + $mech->get_ok($url); + $mech->content_contains('Cheltenham'); + $mech->content_contains('Trees'); + $contact3->update( { email => $old_email } ); + }; +}; + +subtest "extra google analytics code displayed on logged in problem creation" => sub { + FixMyStreet::override_config { + ALLOWED_COBRANDS => [ { fixmystreet => '.' } ], + BASE_URL => 'https://www.fixmystreet.com', + MAPIT_URL => 'http://mapit.uk/', + }, sub { + # check that the user does not exist + my $test_email = 'test-2@example.com'; + + $mech->clear_emails_ok; + my $user = $mech->log_in_ok($test_email); + + # setup the user. + ok $user->update( + { + name => 'Test User', + phone => '01234 567 890', } - }, - "submit good details" - ); - is_deeply $mech->page_errors, [], "check there were no errors"; + ), + "set users details"; - # check that the user has been created/ not changed - my $user = - FixMyStreet::App->model('DB::User')->find( { email => $test_email } ); - ok $user, "user found"; + # submit initial pc form + $mech->get_ok('/around'); + $mech->submit_form_ok( { with_fields => { pc => 'GL50 2PR', } }, + "submit location" ); - # find the report - my $report = $user->problems->first; - ok $report, "Found the report"; + # click through to the report page + $mech->follow_link_ok( { text_regex => qr/skip this step/i, }, + "follow 'skip this step' link" ); - # Check the report has been assigned appropriately - is $report->council, 2240; + $mech->submit_form_ok( + { + with_fields => { + title => "Test Report at café", + detail => 'Test report details.', + photo1 => '', + name => 'Joe Bloggs', + may_show_name => '1', + phone => '07903 123 456', + category => 'Trees', + } + }, + "submit good details" + ); - # receive token - my $email = $mech->get_email; - ok $email, "got an email"; - like $email->body, qr/confirm the problem/i, "confirm the problem"; + # find the report + my $report = $user->problems->first; + ok $report, "Found the report"; - my ($url) = $email->body =~ m{(http://\S+)}; - ok $url, "extracted confirm url '$url'"; + $mech->content_contains( "'id': 'report/" . $report->id . "'", 'extra google code present' ); - # confirm token - $mech->get_ok($url); - $report->discard_changes; - is $report->state, 'confirmed', "Report is now confirmed"; + # cleanup + $mech->delete_user($user); + }; +}; - # Shouldn't be found, as it was a county problem - is $mech->get( '/report/' . $report->id )->code, 404, "report not found"; +subtest "extra google analytics code displayed on email confirmation problem creation" => sub { + FixMyStreet::override_config { + ALLOWED_COBRANDS => [ { fixmystreet => '.' } ], + BASE_URL => 'https://www.fixmystreet.com', + MAPIT_URL => 'http://mapit.uk/', + }, sub { + $mech->log_out_ok; + $mech->clear_emails_ok; - # But should be on the main site - $mech->host( 'www.fixmystreet.com' ); - $mech->get_ok( '/report/' . $report->id ); - is $report->name, 'Joe Bloggs', 'name updated correctly'; + $mech->get_ok('/'); + $mech->submit_form_ok( { with_fields => { pc => 'GL50 2PR' } }, + "submit location" ); + $mech->follow_link_ok( + { text_regex => qr/skip this step/i, }, + "follow 'skip this step' link" + ); - $mech->delete_user($user); -} + my $fields = $mech->visible_form_values('mapSkippedForm'); + my $submission_fields = { + title => "Test Report", + detail => 'Test report details.', + photo1 => '', + email => 'firstlast@example.com', + name => 'Test User', + may_show_name => '1', + phone => '07903 123 456', + category => 'Trees', + password_register => '', + }; -$contact1->delete; -$contact2->delete; -$contact3->delete; -$contact4->delete; -$contact5->delete; -$contact6->delete; -$contact7->delete; + $mech->submit_form_ok( { with_fields => $submission_fields }, + "submit good details" ); + + my $email = $mech->get_email; + ok $email, "got an email"; + like $mech->get_text_body_from_email($email), qr/confirm that you want to/i, "confirm the problem"; + + my $url = $mech->get_link_from_email($email); + + # confirm token in order to update the user details + $mech->get_ok($url); + + # find the report + my $user = + FixMyStreet::App->model('DB::User') + ->find( { email => 'firstlast@example.com' } ); + + my $report = $user->problems->first; + ok $report, "Found the report"; + + $mech->content_contains( "'id': 'report/" . $report->id . "'", 'extra google code present' ); + + $user->problems->delete; + $user->alerts->delete; + $user->delete; + }; +}; done_testing(); + +END { + $mech->delete_body($_) foreach @bodies; +} diff --git a/t/app/controller/report_new_mobile.t b/t/app/controller/report_new_mobile.t new file mode 100644 index 000000000..3dfb99b2f --- /dev/null +++ b/t/app/controller/report_new_mobile.t @@ -0,0 +1,42 @@ +use Test::More; +use FixMyStreet::TestMech; +use LWP::Protocol::PSGI; +use t::Mock::MapItZurich; + +my $mech = FixMyStreet::TestMech->new; + +# disable info logs for this test run +FixMyStreet::App->log->disable('info'); +END { FixMyStreet::App->log->enable('info'); } + +LWP::Protocol::PSGI->register(t::Mock::MapItZurich->to_psgi_app, host => 'mapit.zurich'); + +subtest "Check signed up for alert when logged in" => sub { + FixMyStreet::override_config { + MAPIT_URL => 'http://mapit.zurich', + MAPIT_TYPES => [ 'O08' ], + }, sub { + $mech->log_in_ok('user@example.org'); + $mech->post_ok( '/report/new/mobile', { + service => 'iPhone', + title => 'Title', + detail => 'Problem detail', + lat => 47.381817, + lon => 8.529156, + email => 'user@example.org', + pc => '', + name => 'Name', + }); + my $res = $mech->response; + ok $res->header('Content-Type') =~ m{^application/json\b}, 'response should be json'; + + my $user = FixMyStreet::DB->resultset('User')->search({ email => 'user@example.org' })->first; + my $a = FixMyStreet::DB->resultset('Alert')->search({ user_id => $user->id })->first; + isnt $a, undef, 'User is signed up for alert'; + }; +}; + +END { + $mech->delete_user('user@example.org'); + done_testing(); +} diff --git a/t/app/controller/report_new_open311.t b/t/app/controller/report_new_open311.t index e9ada9f41..e3a464f88 100644 --- a/t/app/controller/report_new_open311.t +++ b/t/app/controller/report_new_open311.t @@ -3,44 +3,46 @@ use warnings; use Test::More; use FixMyStreet::TestMech; +use FixMyStreet::App; use Web::Scraper; +# disable info logs for this test run +FixMyStreet::App->log->disable('info'); +END { FixMyStreet::App->log->enable('info'); } + my $mech = FixMyStreet::TestMech->new; -FixMyStreet::App->model('DB::Body')->find_or_create( { - area_id => 2651, - endpoint => 'http://example.com/open311', - jurisdiction => 'mySociety', - api_key => 'apikey', -} ); - -my %contact_params = ( - confirmed => 1, - deleted => 0, - editor => 'Test', - whenedited => \'current_timestamp', - note => 'Created for test', -); +my $body = $mech->create_body_ok(2245, 'Wiltshire Council'); +$body->update({ + endpoint => 'http://example.com/open311', + jurisdiction => 'mySociety', + api_key => 'apikey', +}); + # Let's make some contacts to send things to! -my $contact1 = FixMyStreet::App->model('DB::Contact')->find_or_create( { - %contact_params, - body_id => 2651, # Edinburgh +my $contact1 = $mech->create_contact_ok( + body_id => $body->id, # Edinburgh category => 'Street lighting', email => '100', extra => [ { description => 'Lamppost number', code => 'number', required => 'True' }, { description => 'Lamppost type', code => 'type', required => 'False', values => { value => [ { name => ['Gas'], key => ['old'] }, { name => [ 'Yellow' ], key => [ 'modern' ] } ] } - } + } + ], +); +my $contact1b = $mech->create_contact_ok( + body_id => $body->id, # Edinburgh + category => 'Moon lighting', + email => '100b', + extra => [ { description => 'Moon type', code => 'type', required => 'False', values => + [ { name => 'Full', key => 'full' }, { name => 'New', key => 'new' } ] } ], -} ); -my $contact2 = FixMyStreet::App->model('DB::Contact')->find_or_create( { - %contact_params, - body_id => 2651, # Edinburgh +); +my $contact2 = $mech->create_contact_ok( + body_id => $body->id, # Edinburgh category => 'Graffiti Removal', email => '101', -} ); -ok $contact1, "created test contact 1"; -ok $contact2, "created test contact 2"; +); # test that the various bit of form get filled in and errors correctly # generated. @@ -51,7 +53,9 @@ foreach my $test ( fields => { title => '', detail => '', - photo => '', + photo1 => '', + photo2 => '', + photo3 => '', name => '', may_show_name => '1', email => '', @@ -66,9 +70,9 @@ foreach my $test ( type => 'old', }, errors => [ + 'This information is required', 'Please enter a subject', 'Please enter some details', - 'This information is required', 'Please enter your email', 'Please enter your name', ], @@ -111,17 +115,22 @@ foreach my $test ( $mech->get_ok('/around'); # submit initial pc form - $mech->submit_form_ok( { with_fields => { pc => $test->{pc} } }, - "submit location" ); - is_deeply $mech->page_errors, [], "no errors for pc '$test->{pc}'"; - - # click through to the report page - $mech->follow_link_ok( { text_regex => qr/skip this step/i, }, - "follow 'skip this step' link" ); - - # submit the main form - $mech->submit_form_ok( { with_fields => $test->{fields} }, - "submit form" ); + FixMyStreet::override_config { + ALLOWED_COBRANDS => [ { 'fixmystreet' => '.' } ], + MAPIT_URL => 'http://mapit.uk/', + }, sub { + $mech->submit_form_ok( { with_fields => { pc => $test->{pc} } }, + "submit location" ); + is_deeply $mech->page_errors, [], "no errors for pc '$test->{pc}'"; + + # click through to the report page + $mech->follow_link_ok( { text_regex => qr/skip this step/i, }, + "follow 'skip this step' link" ); + + # submit the main form + $mech->submit_form_ok( { with_fields => $test->{fields} }, + "submit form" ); + }; # check that we got the errors expected is_deeply $mech->page_errors, $test->{errors}, "check errors"; @@ -147,21 +156,27 @@ foreach my $test ( %{ $test->{fields} }, %{ $test->{submit_with} }, }; - $mech->submit_form_ok( { with_fields => $new_values } ); + FixMyStreet::override_config { + ALLOWED_COBRANDS => [ { 'fixmystreet' => '.' } ], + MAPIT_URL => 'http://mapit.uk/', + }, sub { + $mech->submit_form_ok( { with_fields => $new_values } ); + }; $user = FixMyStreet::App->model('DB::User')->find( { email => $test_email } ); ok $user, 'created user'; my $prob = $user->problems->first; ok $prob, 'problem created'; - is_deeply $prob->extra, $test->{extra}, 'extra open311 data added to problem'; + is_deeply $prob->get_extra_fields, $test->{extra}, 'extra open311 data added to problem'; $user->problems->delete; $user->delete; }; } -$contact1->delete; -$contact2->delete; - done_testing(); + +END { + $mech->delete_body($body); +} diff --git a/t/app/controller/report_updates.t b/t/app/controller/report_updates.t index 2c7294222..de153978b 100644 --- a/t/app/controller/report_updates.t +++ b/t/app/controller/report_updates.t @@ -13,15 +13,11 @@ my $mech = FixMyStreet::TestMech->new; $mech->delete_user('commenter@example.com'); $mech->delete_user('test@example.com'); -my $user = - FixMyStreet::App->model('DB::User') - ->find_or_create( { email => 'test@example.com', name => 'Test User' } ); -ok $user, "created test user"; +my $user = $mech->create_user_ok('test@example.com', name => 'Test User'); -my $user2 = - FixMyStreet::App->model('DB::User') - ->find_or_create( { email => 'commenter@example.com', name => 'Commenter' } ); -ok $user2, "created comment user"; +my $user2 = $mech->create_user_ok('commenter@example.com', name => 'Commenter'); + +my $body = $mech->create_body_ok(2504, 'Westminster City Council'); my $dt = DateTime->new( year => 2011, @@ -35,7 +31,7 @@ my $dt = DateTime->new( my $report = FixMyStreet::App->model('DB::Problem')->find_or_create( { postcode => 'SW1A 1AA', - council => '2504', + bodies_str => $body->id, areas => ',105255,11806,11828,2247,2504,', category => 'Other', title => 'Test 2', @@ -135,6 +131,19 @@ for my $test ( }; } +subtest "updates displayed on report with empty bodies_str" => sub { + my $old_bodies_str = $report->bodies_str; + $report->update({ bodies_str => undef }); + $comment->update({ problem_state => 'fixed' , mark_open => 'false', mark_fixed => 'false' }); + + $mech->get_ok("/report/$report_id"); + + my $meta = $mech->extract_update_metas; + is scalar @$meta, 1, 'update displayed'; + + $report->update({ bodies_str => $old_bodies_str }); +}; + subtest "unconfirmed updates not displayed" => sub { $comment->state( 'unconfirmed' ); $comment->update; @@ -197,7 +206,9 @@ for my $test ( rznvy => '', update => '', name => '', - photo => '', + photo1 => '', + photo2 => '', + photo3 => '', fixed => undef, add_alert => 1, may_show_name => undef, @@ -214,7 +225,9 @@ for my $test ( rznvy => 'test', update => '', name => '', - photo => '', + photo1 => '', + photo2 => '', + photo3 => '', fixed => undef, add_alert => 1, may_show_name => undef, @@ -231,7 +244,9 @@ for my $test ( rznvy => 'test @ example. com', update => '', name => '', - photo => '', + photo1 => '', + photo2 => '', + photo3 => '', fixed => undef, add_alert => 1, may_show_name => undef, @@ -250,7 +265,9 @@ for my $test ( rznvy => 'test@EXAMPLE.COM', update => '', name => '', - photo => '', + photo1 => '', + photo2 => '', + photo3 => '', fixed => undef, add_alert => 1, may_show_name => undef, @@ -290,7 +307,9 @@ for my $test ( rznvy => '', may_show_name => 1, add_alert => 1, - photo => '', + photo1 => '', + photo2 => '', + photo3 => '', update => '', fixed => undef, remember_me => undef, @@ -314,7 +333,9 @@ for my $test ( rznvy => '', may_show_name => 1, add_alert => 1, - photo => '', + photo1 => '', + photo2 => '', + photo3 => '', update => '', fixed => undef, remember_me => undef, @@ -351,13 +372,14 @@ for my $test ( 'submit update' ); - $mech->content_contains('Nearly Done! Now check your email'); + $mech->content_contains('Nearly done! Now check your email'); my $email = $mech->get_email; - ok $email, "got an email"; - like $email->body, qr/confirm the update you/i, "Correct email text"; + my $body = $mech->get_text_body_from_email($email); + like $body, qr/confirm your update on/i, "Correct email text"; - my ( $url, $url_token ) = $email->body =~ m{(http://\S+/C/)(\S+)}; + my $url = $mech->get_link_from_email($email); + my ($url_token) = $url =~ m{/C/(\S+)}; ok $url, "extracted confirm url '$url'"; my $token = FixMyStreet::App->model('DB::Token')->find( @@ -384,7 +406,7 @@ for my $test ( is $update->text, $details->{update}, 'update text'; is $add_alerts, $details->{add_alert} ? 1 : 0, 'do not sign up for alerts'; - $mech->get_ok( $url . $url_token ); + $mech->get_ok( $url ); $mech->content_contains("/report/$report_id#update_$update_id"); my $unreg_user = FixMyStreet::App->model( 'DB::User' )->find( { email => $details->{rznvy} } ); @@ -407,24 +429,103 @@ for my $test ( $report->state('confirmed'); $report->update; +for my $test ( + { + desc => 'overriding email confirmation allows report confirmation with no email sent', + initial_values => { + name => '', + rznvy => '', + may_show_name => 1, + add_alert => 1, + photo1 => '', + photo2 => '', + photo3 => '', + update => '', + fixed => undef, + remember_me => undef, + password_register => '', + password_sign_in => '', + }, + form_values => { + submit_update => 1, + rznvy => 'unregistered@example.com', + update => "update no email confirm", + add_alert => 1, + name => 'Unreg User', + may_show_name => undef, + }, + changes => { + update => "Update no email confirm", + }, + } +) { + subtest $test->{desc} => sub { + my $send_confirmation_mail_override = Sub::Override->new( + "FixMyStreet::Cobrand::Default::never_confirm_updates", + sub { return 1; } + ); + $mech->log_out_ok(); + $mech->clear_emails_ok(); + + $mech->get_ok("/report/$report_id"); + + my $values = $mech->visible_form_values('updateForm'); + + is_deeply $values, $test->{initial_values}, 'initial form values'; + + $mech->submit_form_ok( + { + with_fields => $test->{form_values} + }, + 'submit update' + ); + $mech->content_contains("/report/$report_id"); + $mech->get_ok("/report/$report_id"); + + $mech->content_contains('Test 2'); + $mech->content_contains('Update no email confirm'); + + my $email = $mech->email_count_is(0); + + my $update = + FixMyStreet::App->model('DB::Comment')->find( { problem_id => $report_id, text => 'Update no email confirm' } ); + my $update_id = $update->id; + + $mech->content_contains('name="update_' . $update_id . '"'); + + my $details = { + %{ $test->{form_values} }, + %{ $test->{changes} } + }; + + ok $update, 'found update in database'; + is $update->state, 'confirmed', 'update confirmed'; + is $update->user->email, $details->{rznvy}, 'update email'; + is $update->text, $details->{update}, 'update text'; + + my $unreg_user = FixMyStreet::App->model( 'DB::User' )->find( { email => $details->{rznvy} } ); + + ok $unreg_user, 'found user'; + + $mech->delete_user( $unreg_user ); + $send_confirmation_mail_override->restore(); + }; +} + subtest 'check non authority user cannot change set state' => sub { $mech->log_in_ok( $user->email ); - $user->from_body( 0 ); + $user->from_body( undef ); $user->update; $mech->get_ok("/report/$report_id"); - $mech->post_ok( "/report/update", { - submit_update => 1, - id => $report_id, - name => $user->name, - may_show_name => 1, - add_alert => undef, - photo => '', - update => 'this is a forbidden update', - state => 'fixed - council', + $mech->submit_form_ok( { + form_id => 'form_update_form', + fields => { + may_show_name => 1, + update => 'this is a forbidden update', + state => 'fixed - council', }, - 'submitted with state', - ); + }, 'submitted with state'); is $mech->uri->path, "/report/update", "at /report/update"; @@ -437,22 +538,18 @@ subtest 'check non authority user cannot change set state' => sub { for my $state ( qw/unconfirmed hidden partial/ ) { subtest "check that update cannot set state to $state" => sub { $mech->log_in_ok( $user->email ); - $user->from_body( 2504 ); + $user->from_body( $body->id ); $user->update; $mech->get_ok("/report/$report_id"); - $mech->post_ok( "/report/update", { - submit_update => 1, - id => $report_id, - name => $user->name, - may_show_name => 1, - add_alert => undef, - photo => '', - update => 'this is a forbidden update', - state => $state, + $mech->submit_form_ok( { + form_id => 'form_update_form', + fields => { + may_show_name => 1, + update => 'this is a forbidden update', + state => $state, }, - 'submitted with state', - ); + }, 'submitted with state'); is $mech->uri->path, "/report/update", "at /report/update"; @@ -469,96 +566,136 @@ for my $test ( fields => { name => $user->name, may_show_name => 1, - add_alert => undef, - photo => '', update => 'Set state to investigating', state => 'investigating', }, state => 'investigating', }, { - desc => 'from authority user marks report as planned', + desc => 'from authority user marks report as in progress', fields => { name => $user->name, may_show_name => 1, - add_alert => undef, - photo => '', - update => 'Set state to planned', - state => 'planned', + update => 'Set state to in progress', + state => 'in progress', + }, + state => 'in progress', + }, + { + desc => 'from authority user marks report as fixed', + fields => { + name => $user->name, + may_show_name => 1, + update => 'Set state to fixed', + state => 'fixed', }, - state => 'planned', + state => 'fixed - council', }, { - desc => 'from authority user marks report as in progress', + desc => 'from authority user marks report as action scheduled', fields => { name => $user->name, may_show_name => 1, - add_alert => undef, - photo => '', - update => 'Set state to in progress', - state => 'in progress', + update => 'Set state to action scheduled', + state => 'action scheduled', }, - state => 'in progress', + state => 'action scheduled', }, { - desc => 'from authority user marks report as closed', + desc => 'from authority user marks report as unable to fix', fields => { name => $user->name, may_show_name => 1, - add_alert => undef, - photo => '', - update => 'Set state to closed', - state => 'closed', + update => 'Set state to unable to fix', + state => 'no further action', }, - state => 'closed', + state => 'unable to fix', }, { - desc => 'from authority user marks report as fixed', + desc => 'from authority user marks report as internal referral', fields => { name => $user->name, may_show_name => 1, - add_alert => undef, - photo => '', - update => 'Set state to fixed', - state => 'fixed', + update => 'Set state to internal referral', + state => 'internal referral', }, - state => 'fixed - council', + state => 'internal referral', + meta => "an internal referral", }, { - desc => 'from authority user marks report as confirmed', + desc => 'from authority user marks report as not responsible', fields => { name => $user->name, may_show_name => 1, - add_alert => undef, - photo => '', - update => 'Set state to confirmed', - state => 'confirmed', + update => 'Set state to not responsible', + state => 'not responsible', + }, + state => 'not responsible', + meta => "not the council's responsibility" + }, + { + desc => 'from authority user marks report as duplicate', + fields => { + name => $user->name, + may_show_name => 1, + update => 'Set state to duplicate', + state => 'duplicate', + }, + state => 'duplicate', + meta => 'a duplicate report', + }, + { + desc => 'from authority user marks report as internal referral', + fields => { + name => $user->name, + may_show_name => 1, + update => 'Set state to internal referral', + state => 'internal referral', }, - state => 'confirmed', + state => 'internal referral', + meta => 'an internal referral', }, { desc => 'from authority user marks report sent to two councils as fixed', fields => { name => $user->name, may_show_name => 1, - add_alert => undef, - photo => '', update => 'Set state to fixed', state => 'fixed', }, state => 'fixed - council', - report_councils => '2504,2505', + report_bodies => $body->id . ',2505', + }, + { + desc => 'from authority user show username for users with correct permissions', + fields => { + name => $user->name, + may_show_name => 1, + update => 'Set state to fixed', + state => 'fixed', + }, + state => 'fixed - council', + report_bodies => $body->id . ',2505', + view_username => 1 }, ) { subtest $test->{desc} => sub { $report->comments->delete; - if ( $test->{ report_councils } ) { - $report->council( $test->{ report_councils } ); + if ( $test->{ report_bodies } ) { + $report->bodies_str( $test->{ report_bodies } ); $report->update; } $mech->log_in_ok( $user->email ); - $user->from_body( 2504 ); + + if ($test->{view_username}) { + ok $user->user_body_permissions->create({ + body => $body, + permission_type => 'view_body_contribute_details' + }), 'Give user view_body_contribute_details permissions'; + } + + $user->from_body( $body->id ); $user->update; $mech->get_ok("/report/$report_id"); @@ -569,6 +706,7 @@ for my $test ( }, 'submit update' ); + $mech->get_ok("/report/$report_id"); $report->discard_changes; my $update = $report->comments->first; @@ -577,14 +715,22 @@ for my $test ( is $update->problem_state, $test->{state}, 'problem state set'; my $update_meta = $mech->extract_update_metas; - # setting it to confirmed shouldn't say anything - if ( $test->{fields}->{state} ne 'confirmed' ) { - like $update_meta->[0], qr/marked as $test->{fields}->{state}$/, 'update meta includes state change'; + my $meta_state = $test->{meta} || $test->{fields}->{state}; + if ( $test->{reopened} ) { + like $update_meta->[0], qr/reopened$/, 'update meta says reopened'; + } elsif ( $test->{state} eq 'duplicate' ) { + like $update_meta->[0], qr/closed as $meta_state$/, 'update meta includes state change'; } else { - like $update_meta->[0], qr/reopened$/, 'update meta includes state change'; + like $update_meta->[0], qr/marked as $meta_state$/, 'update meta includes state change'; + } + + if ($test->{view_username}) { + like $update_meta->[0], qr{Westminster City Council \(Test User\)}, 'update meta includes council and user name'; + $user->user_body_permissions->delete_all; + } else { + like $update_meta->[0], qr{Westminster City Council}, 'update meta includes council name'; + $mech->content_contains( '<strong>Westminster City Council</strong>', 'council name in bold'); } - like $update_meta->[0], qr{Test User \(Westminster City Council\)}, 'update meta includes council name'; - $mech->content_contains( 'Test User (<strong>Westminster City Council</strong>)', 'council name in bold'); $report->discard_changes; is $report->state, $test->{state}, 'state set'; @@ -598,7 +744,8 @@ subtest 'check meta correct for comments marked confirmed but not marked open' = user => $user, problem_id => $report->id, text => 'update text', - confirmed => DateTime->now, + # Subtract a day to deal with any code/db timezone difference + confirmed => DateTime->now( time_zone => 'local' ) - DateTime::Duration->new( days => 1 ), problem_state => 'confirmed', anonymous => 0, mark_open => 0, @@ -609,7 +756,7 @@ subtest 'check meta correct for comments marked confirmed but not marked open' = $mech->get_ok( "/report/" . $report->id ); my $update_meta = $mech->extract_update_metas; - like $update_meta->[0], qr/reopened$/, + unlike $update_meta->[0], qr/reopened$/, 'update meta does not say reopened'; $comment->update( { mark_open => 1, problem_state => undef } ); @@ -627,13 +774,170 @@ subtest 'check meta correct for comments marked confirmed but not marked open' = unlike $update_meta->[0], qr/marked as open$/, 'update meta does not says marked as open'; unlike $update_meta->[0], qr/reopened$/, 'update meta does not say reopened'; - }; +}; + +subtest "check first comment with no status change has no status in meta" => sub { + $mech->log_in_ok( $user->email ); + $user->from_body( undef ); + $user->update; + + my $comment = $report->comments->first; + $comment->update( { mark_fixed => 0, problem_state => 'confirmed' } ); + + $mech->get_ok("/report/$report_id"); + + my $update_meta = $mech->extract_update_metas; + unlike $update_meta->[0], qr/marked as|reopened/, 'update meta does not include state change'; +}; + +subtest "check comment with no status change has not status in meta" => sub { + $mech->log_in_ok( $user->email ); + $user->from_body( undef ); + $user->update; + + my $comment = $report->comments->first; + $comment->update( { mark_fixed => 1, problem_state => 'fixed - council' } ); + + $mech->get_ok("/report/$report_id"); + + $mech->submit_form_ok( + { + with_fields => { + name => $user->name, + may_show_name => 1, + add_alert => undef, + photo1 => '', + photo2 => '', + photo3 => '', + update => 'Comment that does not change state', + }, + }, + 'submit update' + ); + $mech->get_ok("/report/$report_id"); + + $report->discard_changes; + my @updates = $report->comments->all; + is scalar @updates, 2, 'correct number of updates'; + + my $update = pop @updates; + + is $report->state, 'fixed - council', 'correct report state'; + is $update->problem_state, 'fixed - council', 'correct update state'; + my $update_meta = $mech->extract_update_metas; + unlike $update_meta->[1], qr/marked as/, 'update meta does not include state change'; + + $user->from_body( $body->id ); + $user->update; + + $mech->get_ok("/report/$report_id"); + + $mech->submit_form_ok( + { + with_fields => { + name => $user->name, + may_show_name => 1, + add_alert => undef, + photo1 => '', + photo2 => '', + photo3 => '', + update => 'Comment that sets state to investigating', + state => 'investigating', + }, + }, + 'submit update' + ); + $mech->get_ok("/report/$report_id"); + + $report->discard_changes; + @updates = $report->comments->search(undef, { order_by => 'created' })->all;; + + is scalar @updates, 3, 'correct number of updates'; + + $update = pop @updates; + + is $report->state, 'investigating', 'correct report state'; + is $update->problem_state, 'investigating', 'correct update state'; + $update_meta = $mech->extract_update_metas; + like $update_meta->[0], qr/marked as fixed/, 'first update meta says fixed'; + unlike $update_meta->[1], qr/marked as/, 'second update meta does not include state change'; + like $update_meta->[2], qr/marked as investigating/, 'third update meta says investigating'; + + my $dt = DateTime->now( time_zone => "local" )->add( seconds => 1 ); + $comment = FixMyStreet::App->model('DB::Comment')->find_or_create( + { + problem_id => $report_id, + user_id => $user->id, + name => 'Other User', + mark_fixed => 'false', + text => 'This is some update text', + state => 'confirmed', + confirmed => $dt->ymd . ' ' . $dt->hms, + anonymous => 'f', + } + ); + + $mech->get_ok("/report/$report_id"); + + $report->discard_changes; + @updates = $report->comments->search(undef, { order_by => 'created' })->all;; + is scalar @updates, 4, 'correct number of updates'; + + $update = pop @updates; + + is $report->state, 'investigating', 'correct report state'; + is $update->problem_state, undef, 'no update state'; + $update_meta = $mech->extract_update_metas; + like $update_meta->[0], qr/marked as fixed/, 'first update meta says fixed'; + unlike $update_meta->[1], qr/marked as/, 'second update meta does not include state change'; + like $update_meta->[2], qr/marked as investigating/, 'third update meta says investigating'; + unlike $update_meta->[3], qr/marked as/, 'fourth update meta has no state change'; +}; + +subtest 'check meta correct for second comment marking as reopened' => sub { + $report->comments->delete; + my $comment = FixMyStreet::App->model('DB::Comment')->create( + { + user => $user, + problem_id => $report->id, + text => 'update text', + confirmed => DateTime->now( time_zone => 'local'), + problem_state => 'fixed - user', + anonymous => 0, + mark_open => 0, + mark_fixed => 1, + state => 'confirmed', + } + ); + + $mech->get_ok( "/report/" . $report->id ); + my $update_meta = $mech->extract_update_metas; + like $update_meta->[0], qr/fixed$/, 'update meta says fixed'; -$user->from_body(0); + $comment = FixMyStreet::App->model('DB::Comment')->create( + { + user => $user, + problem_id => $report->id, + text => 'update text', + confirmed => DateTime->now( time_zone => 'local' ) + DateTime::Duration->new( minutes => 1 ), + problem_state => 'confirmed', + anonymous => 0, + mark_open => 0, + mark_fixed => 0, + state => 'confirmed', + } + ); + + $mech->get_ok( "/report/" . $report->id ); + $update_meta = $mech->extract_update_metas; + like $update_meta->[1], qr/reopened$/, 'update meta says reopened'; +}; + +$user->from_body(undef); $user->update; $report->state('confirmed'); -$report->council('2504'); +$report->bodies_str($body->id); $report->update; for my $test ( @@ -660,9 +964,7 @@ for my $test ( add_alert => undef, password_sign_in => 'secret2', }, - field_errors => [ - 'You have successfully signed in; please check and confirm your details are accurate:', - ], + message => 'You have successfully signed in; please check and confirm your details are accurate:', } ) { subtest $test->{desc} => sub { @@ -683,7 +985,10 @@ for my $test ( 'submit update' ); - is_deeply $mech->page_errors, $test->{field_errors}, 'check there were errors'; + $mech->content_contains($test->{message}) if $test->{message}; + + is_deeply $mech->page_errors, $test->{field_errors}, 'check there were errors' + if $test->{field_errors}; SKIP: { skip( "Incorrect password", 5 ) unless $test->{form_values}{password_sign_in} eq $pw; @@ -698,7 +1003,7 @@ for my $test ( "submit good details" ); - is $mech->uri->path, "/report/" . $report_id, "redirected to report page"; + $mech->content_contains('Thank you for updating this issue'); $mech->email_count_is(0); my $update = $report->comments->first; @@ -729,7 +1034,7 @@ subtest 'submit an update for a registered user, creating update by email' => su }, }, 'submit update' ); - $mech->content_contains('Nearly Done! Now check your email'); + $mech->content_contains('Nearly done! Now check your email'); # No change to user yet. $user->discard_changes; @@ -737,10 +1042,11 @@ subtest 'submit an update for a registered user, creating update by email' => su is $user->name, 'Mr Reg', 'name unchanged'; my $email = $mech->get_email; - ok $email, "got an email"; - like $email->body, qr/confirm the update you/i, "Correct email text"; + my $body = $mech->get_text_body_from_email($email); + like $body, qr/confirm your update on/i, "Correct email text"; - my ( $url, $url_token ) = $email->body =~ m{(http://\S+/C/)(\S+)}; + my $url = $mech->get_link_from_email($email); + my ($url_token) = $url =~ m{/C/(\S+)}; ok $url, "extracted confirm url '$url'"; my $token = FixMyStreet::App->model('DB::Token')->find( { @@ -758,7 +1064,7 @@ subtest 'submit an update for a registered user, creating update by email' => su is $update->user->email, 'registered@example.com', 'update email'; is $update->text, 'Update from a user', 'update text'; - $mech->get_ok( $url . $url_token ); + $mech->get_ok( $url ); $mech->content_contains("/report/$report_id#update_$update_id"); # User should have new name and password @@ -771,6 +1077,9 @@ subtest 'submit an update for a registered user, creating update by email' => su $mech->delete_user( $user ); }; +my $sample_file = file(__FILE__)->parent->file("sample.jpg")->stringify; +ok -e $sample_file, "sample file $sample_file exists"; + for my $test ( { desc => 'submit update for registered user', @@ -778,7 +1087,9 @@ for my $test ( name => 'Test User', may_show_name => 1, add_alert => 1, - photo => '', + photo1 => '', + photo2 => '', + photo3 => '', update => '', fixed => undef, }, @@ -788,6 +1099,7 @@ for my $test ( update => 'update from a registered user', add_alert => undef, fixed => undef, + photo1 => [ [ $sample_file, undef, Content_Type => 'image/jpeg' ], 1 ], }, changed => { update => 'Update from a registered user' @@ -803,7 +1115,9 @@ for my $test ( name => 'Test User', may_show_name => 1, add_alert => 1, - photo => '', + photo1 => '', + photo2 => '', + photo3 => '', update => '', fixed => undef, }, @@ -829,7 +1143,9 @@ for my $test ( name => 'Test User', may_show_name => 1, add_alert => 1, - photo => '', + photo1 => '', + photo2 => '', + photo3 => '', update => '', fixed => undef, }, @@ -854,7 +1170,9 @@ for my $test ( name => 'Commenter', may_show_name => 1, add_alert => 1, - photo => '', + photo1 => '', + photo2 => '', + photo3 => '', update => '', fixed => undef, }, @@ -879,7 +1197,9 @@ for my $test ( name => 'Commenter', may_show_name => 1, add_alert => 1, - photo => '', + photo1 => '', + photo2 => '', + photo3 => '', update => '', }, email => 'commenter@example.com', @@ -928,7 +1248,14 @@ for my $test ( 'submit update' ); - is $mech->uri->path, "/report/" . $report_id, "redirected to report page"; + $mech->content_contains('Thank you for updating this issue'); + $mech->content_contains("/report/" . $report_id); + $mech->get_ok("/report/" . $report_id); + + my $update = $report->comments->first; + ok $update, 'found update'; + + $mech->content_contains("/photo/c/" . $update->id . ".0.jpeg") if $test->{fields}->{photo1}; if ( !defined( $test->{endstate_banner} ) ) { is $mech->extract_problem_banner->{text}, undef, 'endstate banner'; @@ -943,8 +1270,6 @@ for my $test ( %{ $test->{changed} }, }; - my $update = $report->comments->first; - ok $update, 'found update'; is $update->text, $results->{update}, 'update text'; is $update->user->email, $test->{email}, 'update user'; is $update->state, 'confirmed', 'update confirmed'; @@ -965,7 +1290,9 @@ foreach my $test ( name => 'Test User', may_show_name => 1, add_alert => 1, - photo => '', + photo1 => '', + photo2 => '', + photo3 => '', update => '', fixed => undef, }, @@ -982,7 +1309,6 @@ foreach my $test ( alert => 1, # we signed up for alerts before, do not unsign us anonymous => 0, answered => 0, - path => '/report/update', content => "Thanks, glad to hear it's been fixed! Could we just ask if you have ever reported a problem to a council before?", }, @@ -992,7 +1318,9 @@ foreach my $test ( name => 'Test User', may_show_name => 1, add_alert => 1, - photo => '', + photo1 => '', + photo2 => '', + photo3 => '', update => '', fixed => undef, }, @@ -1009,7 +1337,6 @@ foreach my $test ( alert => 1, # we signed up for alerts before, do not unsign us anonymous => 0, answered => 0, - path => '/report/update', content => "Thanks, glad to hear it's been fixed! Could we just ask if you have ever reported a problem to a council before?", }, @@ -1020,7 +1347,9 @@ foreach my $test ( name => 'Test User', may_show_name => 1, add_alert => 1, - photo => '', + photo1 => '', + photo2 => '', + photo3 => '', update => '', fixed => undef, }, @@ -1037,7 +1366,6 @@ foreach my $test ( alert => 1, # we signed up for alerts before, do not unsign us anonymous => 0, answered => 1, - path => '/report/' . $report->id, content => $report->title, }, ) @@ -1062,7 +1390,7 @@ foreach my $test ( { problem_id => $report_id, ever_reported => 'y', - whensent => \'ms_current_timestamp()', + whensent => \'current_timestamp', } ); @@ -1090,7 +1418,7 @@ foreach my $test ( $mech->submit_form_ok( { with_fields => $test->{fields}, }, 'submit update' ); - is $mech->uri->path, $test->{path}, "page after submission"; + is $mech->uri->path, '/report/update', "page after submission"; $mech->content_contains( $test->{content} ); @@ -1120,7 +1448,8 @@ foreach my $test ( $mech->submit_form_ok( { with_fields => { reported => 'Yes' } } ); - $mech->content_contains( 'Thank you — you can' ); + $mech->content_contains( $report->title ); + $mech->content_contains( 'Thank you for updating this issue' ); $questionnaire = FixMyStreet::App->model( 'DB::Questionnaire' )->find( { problem_id => $report_id } @@ -1179,7 +1508,7 @@ for my $test ( anonymous => 0, answered => 1, path => '/report/update', - content => "You have successfully confirmed your update", + content => "Thank you for updating this issue", }, ) { @@ -1203,7 +1532,7 @@ for my $test ( { problem_id => $report_id, ever_reported => 'y', - whensent => \'ms_current_timestamp()', + whensent => \'current_timestamp', } ); @@ -1228,8 +1557,6 @@ for my $test ( $mech->content_contains( 'Now check your email' ); - $mech->email_count_is(1); - my $results = { %{ $test->{fields} }, %{ $test->{changed} }, }; my $update = $report->comments->first; @@ -1240,10 +1567,11 @@ for my $test ( is $update->anonymous, $test->{anonymous}, 'user anonymous'; my $email = $mech->get_email; - ok $email, "got an email"; - like $email->body, qr/confirm the update you/i, "Correct email text"; + my $body = $mech->get_text_body_from_email($email); + like $body, qr/confirm your update on/i, "Correct email text"; - my ( $url, $url_token ) = $email->body =~ m{(http://\S+/C/)(\S+)}; + my $url = $mech->get_link_from_email($email); + my ($url_token) = $url =~ m{/C/(\S+)}; ok $url, "extracted confirm url '$url'"; my $token = FixMyStreet::App->model('DB::Token')->find( @@ -1269,7 +1597,8 @@ for my $test ( $mech->submit_form_ok( { with_fields => { reported => 'Yes' } } ); - $mech->content_contains( 'Thank you — you can' ); + $mech->content_contains( $report->title ); + $mech->content_contains( 'Thank you for updating this issue' ); $questionnaire = FixMyStreet::App->model( 'DB::Questionnaire' )->find( { problem_id => $report_id } @@ -1286,10 +1615,261 @@ for my $test ( }; } +for my $test ( + { + desc => 'update confirmed without marking as fixed leaves state unchanged', + initial_state => 'confirmed', + expected_form_fields => { + fixed => undef, + }, + submitted_form_fields => { + fixed => 0, + }, + end_state => 'confirmed', + }, + { + desc => 'update investigating without marking as fixed leaves state unchanged', + initial_state => 'investigating', + expected_form_fields => { + fixed => undef, + }, + submitted_form_fields => { + fixed => 0, + }, + end_state => 'investigating', + }, + { + desc => 'update in progress without marking as fixed leaves state unchanged', + initial_state => 'in progress', + expected_form_fields => { + fixed => undef, + }, + submitted_form_fields => { + fixed => 0, + }, + end_state => 'in progress', + }, + { + desc => 'update action scheduled without marking as fixed leaves state unchanged', + initial_state => 'action scheduled', + expected_form_fields => { + fixed => undef, + }, + submitted_form_fields => { + fixed => 0, + }, + end_state => 'action scheduled', + }, + { + desc => 'update fixed without marking as open leaves state unchanged', + initial_state => 'fixed', + expected_form_fields => { + reopen => undef, + }, + submitted_form_fields => { + reopen => 0, + }, + end_state => 'fixed', + }, + { + desc => 'update unable to fix without marking as fixed leaves state unchanged', + initial_state => 'unable to fix', + expected_form_fields => { + fixed => undef, + }, + submitted_form_fields => { + fixed => 0, + }, + end_state => 'unable to fix', + }, + { + desc => 'update internal referral without marking as fixed leaves state unchanged', + initial_state => 'internal referral', + expected_form_fields => { + fixed => undef, + }, + submitted_form_fields => { + fixed => 0, + }, + end_state => 'internal referral', + }, + { + desc => 'update not responsible without marking as fixed leaves state unchanged', + initial_state => 'not responsible', + expected_form_fields => { + fixed => undef, + }, + submitted_form_fields => { + fixed => 0, + }, + end_state => 'not responsible', + }, + { + desc => 'update duplicate without marking as fixed leaves state unchanged', + initial_state => 'duplicate', + expected_form_fields => { + fixed => undef, + }, + submitted_form_fields => { + fixed => 0, + }, + end_state => 'duplicate', + }, + { + desc => 'can mark confirmed as fixed', + initial_state => 'confirmed', + expected_form_fields => { + fixed => undef, + }, + submitted_form_fields => { + fixed => 1, + }, + end_state => 'fixed - user', + }, + { + desc => 'can mark investigating as fixed', + initial_state => 'investigating', + expected_form_fields => { + fixed => undef, + }, + submitted_form_fields => { + fixed => 1, + }, + end_state => 'fixed - user', + }, + { + desc => 'can mark in progress as fixed', + initial_state => 'in progress', + expected_form_fields => { + fixed => undef, + }, + submitted_form_fields => { + fixed => 1, + }, + end_state => 'fixed - user', + }, + { + desc => 'can mark action scheduled as fixed', + initial_state => 'action scheduled', + expected_form_fields => { + fixed => undef, + }, + submitted_form_fields => { + fixed => 1, + }, + end_state => 'fixed - user', + }, + { + desc => 'cannot mark fixed as fixed, can mark as not fixed', + initial_state => 'fixed', + expected_form_fields => { + reopen => undef, + }, + submitted_form_fields => { + reopen => 1, + }, + end_state => 'confirmed', + }, + { + desc => 'can mark unable to fix as fixed, cannot mark not closed', + initial_state => 'unable to fix', + expected_form_fields => { + fixed => undef, + }, + submitted_form_fields => { + fixed => 1, + }, + end_state => 'fixed - user', + }, + { + desc => 'can mark internal referral as fixed, cannot mark not closed', + initial_state => 'internal referral', + expected_form_fields => { + fixed => undef, + }, + submitted_form_fields => { + fixed => 1, + }, + end_state => 'fixed - user', + }, + { + desc => 'can mark not responsible as fixed, cannot mark not closed', + initial_state => 'not responsible', + expected_form_fields => { + fixed => undef, + }, + submitted_form_fields => { + fixed => 1, + }, + end_state => 'fixed - user', + }, + { + desc => 'can mark duplicate as fixed, cannot mark not closed', + initial_state => 'duplicate', + expected_form_fields => { + fixed => undef, + }, + submitted_form_fields => { + fixed => 1, + }, + end_state => 'fixed - user', + }, +) { + subtest $test->{desc} => sub { + $mech->log_in_ok( $report->user->email ); + + my %standard_fields = ( + name => $report->user->name, + update => 'update text', + photo1 => '', + photo2 => '', + photo3 => '', + may_show_name => 1, + add_alert => 1, + ); + + my %expected_fields = ( + %standard_fields, + %{ $test->{expected_form_fields} }, + update => '', + ); + + my %submitted_fields = ( + %standard_fields, + %{ $test->{submitted_form_fields} }, + ); + + # clear out comments for this problem to make + # checking details easier later + ok( $_->delete, 'deleted comment ' . $_->id ) for $report->comments; + + $report->discard_changes; + $report->state($test->{initial_state}); + $report->update; + + $mech->get_ok("/report/$report_id"); + + my $values = $mech->visible_form_values('updateForm'); + is_deeply $values, \%expected_fields, 'correct form fields present'; + + if ( $test->{submitted_form_fields} ) { + $mech->submit_form_ok( { + with_fields => \%submitted_fields + }, + 'submit update' + ); + + $report->discard_changes; + is $report->state, $test->{end_state}, 'update sets correct report state'; + } + }; +} + subtest 'check have to be logged in for creator fixed questionnaire' => sub { $mech->log_out_ok(); - $mech->get_ok( "/questionnaire/submit?problem=$report_id&reported=Yes" ); + $mech->get( "/questionnaire/submit?problem=$report_id&reported=Yes" ); + is $mech->res->code, 400, "got 400"; $mech->content_contains( "I'm afraid we couldn't locate your problem in the database." ) }; @@ -1298,7 +1878,8 @@ subtest 'check cannot answer other user\'s creator fixed questionnaire' => sub { $mech->log_out_ok(); $mech->log_in_ok( $user2->email ); - $mech->get_ok( "/questionnaire/submit?problem=$report_id&reported=Yes" ); + $mech->get( "/questionnaire/submit?problem=$report_id&reported=Yes" ); + is $mech->res->code, 400, "got 400"; $mech->content_contains( "I'm afraid we couldn't locate your problem in the database." ) }; diff --git a/t/app/controller/reports.t b/t/app/controller/reports.t index a4dab6597..a21d3ad65 100644 --- a/t/app/controller/reports.t +++ b/t/app/controller/reports.t @@ -8,17 +8,85 @@ use DateTime; ok( my $mech = FixMyStreet::TestMech->new, 'Created mech object' ); -$mech->delete_problems_for_council( 2504 ); -$mech->delete_problems_for_council( 2651 ); +$mech->create_body_ok(2514, 'Birmingham City Council'); +my $body_edin_id = $mech->create_body_ok(2651, 'City of Edinburgh Council')->id; +my $body_west_id = $mech->create_body_ok(2504, 'Westminster City Council')->id; +my $body_fife_id = $mech->create_body_ok(2649, 'Fife Council')->id; +my $body_slash_id = $mech->create_body_ok(10000, 'Electricity/Gas Council')->id; -my @edinburgh_problems = $mech->create_problems_for_council(3, 2651, 'All reports'); -my @westminster_problems = $mech->create_problems_for_council(5, 2504, 'All reports'); +$mech->delete_problems_for_body( $body_west_id ); +$mech->delete_problems_for_body( $body_edin_id ); +$mech->delete_problems_for_body( $body_fife_id ); + +my @edinburgh_problems = $mech->create_problems_for_body(3, $body_edin_id, 'All reports'); +my @westminster_problems = $mech->create_problems_for_body(5, $body_west_id, 'All reports'); +my @fife_problems = $mech->create_problems_for_body(15, $body_fife_id, 'All reports'); is scalar @westminster_problems, 5, 'correct number of westminster problems created'; is scalar @edinburgh_problems, 3, 'correct number of edinburgh problems created'; +is scalar @fife_problems, 15, 'correct number of fife problems created'; + +$edinburgh_problems[1]->update( { + state => 'in progress', + confirmed => DateTime->now()->subtract( weeks => 6 ), + lastupdate => DateTime->now()->subtract( weeks => 5 ), +} ); + +$fife_problems[1]->update( { + state => 'fixed - user', + confirmed => DateTime->now()->subtract( weeks => 6 ), + lastupdate => DateTime->now()->subtract( weeks => 5 ), +}); + +$fife_problems[2]->update( { + state => 'fixed - user', + confirmed => DateTime->now()->subtract( weeks => 2 ), + lastupdate => DateTime->now()->subtract( weeks => 1 ), +}); + +$fife_problems[3]->update( { + state => 'fixed - user', + confirmed => DateTime->now()->subtract( weeks => 10 ), + lastupdate => DateTime->now()->subtract( weeks => 9 ), +}); + +$fife_problems[4]->update( { + confirmed => DateTime->now()->subtract( weeks => 10 ), + lastupdate => DateTime->now()->subtract( weeks => 9 ), +}); + +$fife_problems[5]->update( { + confirmed => DateTime->now()->subtract( weeks => 7 ), + lastupdate => DateTime->now()->subtract( weeks => 5 ), +}); + +$fife_problems[6]->update( { + confirmed => DateTime->now()->subtract( weeks => 7 ), + lastupdate => DateTime->now()->subtract( weeks => 2 ), +}); + +$fife_problems[7]->update( { + confirmed => DateTime->now()->subtract( weeks => 10 ), + lastupdate => DateTime->now()->subtract( weeks => 6 ), +}); + +$fife_problems[8]->update( { + confirmed => DateTime->now()->subtract( weeks => 10 ), + lastupdate => DateTime->now()->subtract( weeks => 2 ), +}); + +$fife_problems[9]->update( { + state => 'fixed - user', + confirmed => DateTime->now()->subtract( weeks => 10 ), + lastupdate => DateTime->now()->subtract( weeks => 7 ), +}); + +$fife_problems[10]->update( { + state => 'hidden', +}); # Run the cron script that makes the data for /reports so we don't get an error. -system( "bin/cron-wrapper update-all-reports" ); +system( "bin/update-all-reports" ); # check that we can get the page $mech->get_ok('/reports'); @@ -27,49 +95,192 @@ $mech->content_contains('Birmingham'); my $stats = $mech->extract_report_stats; -is $stats->{'City of Edinburgh Council'}->[1], 3, 'correct number of reports for Edinburgh'; +is $stats->{'City of Edinburgh Council'}->[1], 2, 'correct number of new reports for Edinburgh'; +is $stats->{'City of Edinburgh Council'}->[2], 1, 'correct number of older reports for Edinburgh'; + is $stats->{'Westminster City Council'}->[1], 5, 'correct number of reports for Westminster'; -$mech->follow_link_ok( { text_regex => qr/Birmingham/ } ); +is $stats->{'Fife Council'}->[1], 5, 'correct number of new reports for Fife'; +is $stats->{'Fife Council'}->[2], 4, 'correct number of old reports for Fife'; +is $stats->{'Fife Council'}->[3], 1, 'correct number of unknown reports for Fife'; +is $stats->{'Fife Council'}->[4], 3, 'correct number of fixed reports for Fife'; +is $stats->{'Fife Council'}->[5], 1, 'correct number of older fixed reports for Fife'; + +FixMyStreet::override_config { + MAPIT_URL => 'http://mapit.uk/', +}, sub { + $mech->follow_link_ok( { text_regex => qr/Birmingham/ } ); + $mech->get_ok('/reports/Westminster'); +}; -$mech->get_ok('/reports/Westminster'); $mech->title_like(qr/Westminster City Council/); $mech->content_contains('Westminster City Council'); -$mech->content_contains('All reports Test 3 for 2504', 'problem to be marked non public visible'); +$mech->content_contains('All reports Test 3 for ' . $body_west_id, 'problem to be marked non public visible'); my $problems = $mech->extract_problem_list; is scalar @$problems, 5, 'correct number of problems displayed'; +FixMyStreet::override_config { + MAPIT_URL => 'http://mapit.uk/', +}, sub { + $mech->get_ok('/reports'); + $mech->follow_link_ok({ url_regex => qr{/reports/Electricity_Gas\+Council} }); + is $mech->uri->path, '/reports/Electricity_Gas+Council', 'Path is correct'; + + $mech->get_ok('/reports/City+of+Edinburgh?t=new'); +}; +$problems = $mech->extract_problem_list; +is scalar @$problems, 2, 'correct number of new problems displayed'; + +FixMyStreet::override_config { + MAPIT_URL => 'http://mapit.uk/', +}, sub { + $mech->get_ok('/reports/City+of+Edinburgh?t=older'); +}; +$problems = $mech->extract_problem_list; +is scalar @$problems, 1, 'correct number of older problems displayed'; + +for my $test ( + { + desc => 'new fife problems on report page', + type => 'new', + expected => 5 + }, + { + desc => 'older fife problems on report page', + type => 'older', + expected => 4 + }, + { + desc => 'unknown fife problems on report page', + type => 'unknown', + expected => 1 + }, + { + desc => 'fixed fife problems on report page', + type => 'fixed', + expected => 3 + }, + { + desc => 'older_fixed fife problems on report page', + type => 'older_fixed', + expected => 1 + }, +) { + subtest $test->{desc} => sub { + FixMyStreet::override_config { + MAPIT_URL => 'http://mapit.uk/', + }, sub { + $mech->get_ok('/reports/Fife+Council?t=' . $test->{type}); + }; + + $problems = $mech->extract_problem_list; + is scalar @$problems, $test->{expected}, 'correct number of ' . $test->{type} . ' problems displayed'; + }; +} + my $private = $westminster_problems[2]; ok $private->update( { non_public => 1 } ), 'problem marked non public'; -$mech->get_ok('/reports/Westminster'); +FixMyStreet::override_config { + MAPIT_URL => 'http://mapit.uk/', +}, sub { + $mech->get_ok('/reports/Westminster'); +}; $problems = $mech->extract_problem_list; is scalar @$problems, 4, 'only public problems are displayed'; -$mech->content_lacks('All reports Test 3 for 2504', 'non public problem is not visible'); +$mech->content_lacks('All reports Test 3 for ' . $body_west_id, 'non public problem is not visible'); $mech->get_ok('/reports'); $stats = $mech->extract_report_stats; is $stats->{'Westminster City Council'}->[1], 5, 'non public reports included in stats'; -SKIP: { - skip( "Need 'emptyhomes' in ALLOWED_COBRANDS config", 8 ) - unless FixMyStreet::Cobrand->exists('emptyhomes'); - ok $mech->host("reportemptyhomes.com"), 'change host to reportemptyhomes'; - $mech->get_ok('/reports'); - # EHA lacks one column the others have - $mech->content_lacks('state unknown'); +subtest "test fiksgatami all reports page" => sub { + FixMyStreet::override_config { + ALLOWED_COBRANDS => [ 'fiksgatami' ], + MAPIT_URL => 'http://mapit.nuug.no/', + }, sub { + $mech->create_body_ok(3, 'Oslo'); + ok $mech->host("fiksgatami.no"), 'change host to fiksgatami'; + $mech->get_ok('/reports'); + # There should only be one Oslo + $mech->content_contains('Oslo'); + $mech->content_unlike(qr{Oslo">Oslo.*Oslo}s); + } +}; - skip( "Need 'fiksgatami' in ALLOWED_COBRANDS config", 8 ) - unless FixMyStreet::Cobrand->exists('fiksgatami'); - mySociety::MaPit::configure('http://mapit.nuug.no/'); - ok $mech->host("fiksgatami.no"), 'change host to fiksgatami'; - $mech->get_ok('/reports'); - # There should only be one Oslo - $mech->content_contains('Oslo'); - $mech->content_unlike(qr{Oslo">Oslo.*Oslo}s); -} +subtest "test greenwich all reports page" => sub { + FixMyStreet::override_config { + ALLOWED_COBRANDS => [ 'greenwich' ], + MAPIT_URL => 'http://mapit.uk/' + }, sub { + my $body = $mech->create_body_ok(2493, 'Royal Borough of Greenwich'); + my $deleted_contact = $mech->create_contact_ok( + body_id => $body->id, + category => 'Deleted', + email => 'deleted@example.com', + deleted => 1 + ); + ok $mech->host("greenwich.fixmystreet.com"), 'change host to greenwich'; + $mech->get_ok('/reports/Royal+Borough+of+Greenwich'); + # There should not be deleted categories in the list + my $category_select = $mech->forms()->[0]->find_input('filter_category'); + is $category_select, undef, 'deleted categories are not shown'; -done_testing(); + # Clean up after the test + $deleted_contact->delete; + } +}; + +subtest "it lists shortlisted reports" => sub { + FixMyStreet::override_config { + MAPIT_URL => 'http://mapit.uk/' + }, sub { + my $body = FixMyStreet::App->model('DB::Body')->find( $body_edin_id ); + my $user = $mech->log_in_ok( 'test@example.com' ); + $user->update({ from_body => $body }); + $user->user_body_permissions->find_or_create({ + body => $body, + permission_type => 'planned_reports', + }); + + my ($shortlisted_problem) = $mech->create_problems_for_body(1, $body_edin_id, 'Shortlisted report'); + my ($unshortlisted_problem) = $mech->create_problems_for_body(1, $body_edin_id, 'Unshortlisted report'); + my ($removed_from_shortlist_problem) = $mech->create_problems_for_body(1, $body_edin_id, 'Removed from shortlist report'); + + $user->add_to_planned_reports($shortlisted_problem); + $user->add_to_planned_reports($removed_from_shortlist_problem); + $user->remove_from_planned_reports($removed_from_shortlist_problem); + $mech->get_ok('/reports/City+of+Edinburgh+Council'); + $mech->content_contains('<option value="shortlisted">Shortlisted</option>'); + $mech->content_contains('<option value="unshortlisted">Unshortlisted</option>'); + + $mech->get_ok('/reports/City+of+Edinburgh+Council?status=shortlisted'); + + $mech->content_contains('Shortlisted report'); + $mech->content_lacks('Unshortlisted report'); + $mech->content_lacks('Removed from shortlist report'); + + $mech->get_ok('/reports/City+of+Edinburgh+Council?status=shortlisted,open'); + + $mech->content_contains('Shortlisted report'); + $mech->content_lacks('Unshortlisted report'); + $mech->content_lacks('Removed from shortlist report'); + + $mech->get_ok('/reports/City+of+Edinburgh+Council?status=unshortlisted,open'); + + $mech->content_contains('Unshortlisted report'); + $mech->content_contains('Removed from shortlist report'); + $mech->content_lacks('Shortlisted report'); + + $user->admin_user_body_permissions->delete; + + $mech->get_ok('/reports/City+of+Edinburgh+Council'); + $mech->content_lacks('<option value="shortlisted">Shortlisted</option>'); + $mech->content_lacks('<option value="unshortlisted">Unshortlisted</option>'); + }; +}; + +done_testing(); diff --git a/t/app/controller/rss.t b/t/app/controller/rss.t index 77e2c7ee1..bec504760 100644 --- a/t/app/controller/rss.t +++ b/t/app/controller/rss.t @@ -3,6 +3,7 @@ use warnings; use Test::More; use FixMyStreet::TestMech; +use FixMyStreet::App; my $mech = FixMyStreet::TestMech->new; @@ -12,12 +13,13 @@ my $dt = DateTime->new( day => 10 ); -my $user1 = FixMyStreet::App->model('DB::User') - ->find_or_create( { email => 'reporter@example.com', name => 'Reporter User' } ); +my $user1 = $mech->create_user_ok('reporter-rss@example.com', name => 'Reporter User'); + +my $dt_parser = FixMyStreet::App->model('DB')->schema->storage->datetime_parser; my $report = FixMyStreet::App->model('DB::Problem')->find_or_create( { postcode => 'eh1 1BB', - council => '2651', + bodies_str => '2651', areas => ',11808,135007,14419,134935,2651,20728,', category => 'Street lighting', title => 'Testing', @@ -26,9 +28,9 @@ my $report = FixMyStreet::App->model('DB::Problem')->find_or_create( { name => $user1->name, anonymous => 0, state => 'confirmed', - confirmed => $dt, - lastupdate => $dt, - whensent => $dt->clone->add( minutes => 5 ), + confirmed => $dt_parser->format_datetime($dt), + lastupdate => $dt_parser->format_datetime($dt), + whensent => $dt_parser->format_datetime($dt->clone->add( minutes => 5 )), lang => 'en-gb', service => '', cobrand => 'default', @@ -39,10 +41,16 @@ my $report = FixMyStreet::App->model('DB::Problem')->find_or_create( { user_id => $user1->id, } ); - -$mech->get_ok("/rss/pc/EH11BB/2"); +$mech->host('www.fixmystreet.com'); +FixMyStreet::override_config { + ALLOWED_COBRANDS => [ 'fixmystreet' ], + MAPIT_URL => 'http://mapit.uk/', +}, sub { + $mech->get_ok("/rss/pc/EH11BB/2"); +}; $mech->content_contains( "Testing, 10th October" ); $mech->content_lacks( 'Nearest road to the pin' ); +is $mech->response->header('Access-Control-Allow-Origin'), '*'; $report->geocode( { @@ -108,11 +116,87 @@ $report->geocode( ); $report->update(); -$mech->get_ok("/rss/pc/EH11BB/2"); +FixMyStreet::override_config { + ALLOWED_COBRANDS => [ 'fixmystreet' ], + MAPIT_URL => 'http://mapit.uk/', +}, sub { + $mech->get_ok("/rss/pc/EH11BB/2"); +}; $mech->content_contains( "Testing, 10th October" ); $mech->content_contains( '18 North Bridge, Edinburgh' ); $report->delete(); -$user1->delete(); + +my $council = $mech->create_body_ok(2333, 'Hart Council'); +my $county = $mech->create_body_ok(2227, 'Hampshire Council'); + +my $now = DateTime->now(); +my $report_to_council = FixMyStreet::App->model('DB::Problem')->find_or_create( + { + postcode => 'GU51 4AE', + bodies_str => $council->id, + areas => ',2333,2227,', + category => 'Other', + title => 'council report', + detail => 'Test 2 Detail', + used_map => 't', + name => 'Test User', + anonymous => 'f', + state => 'closed', + confirmed => $now->ymd . ' ' . $now->hms, + lang => 'en-gb', + service => '', + cobrand => 'default', + cobrand_data => '', + send_questionnaire => 't', + latitude => '51.279616', + longitude => '-0.846040', + user_id => $user1->id, + } +); + +my $report_to_county_council = FixMyStreet::App->model('DB::Problem')->find_or_create( + { + postcode => 'GU51 4AE', + bodies_str => $county->id, + areas => ',2333,2227,', + category => 'Other', + title => 'county report', + detail => 'Test 2 Detail', + used_map => 't', + name => 'Test User', + anonymous => 'f', + state => 'closed', + confirmed => $now->ymd . ' ' . $now->hms, + lang => 'en-gb', + service => '', + cobrand => 'default', + cobrand_data => '', + send_questionnaire => 't', + latitude => '51.279616', + longitude => '-0.846040', + user_id => $user1->id, + } +); + +subtest "check RSS feeds on cobrand have correct URLs for non-cobrand reports" => sub { + $mech->host('hart.fixmystreet.com'); + my $expected1 = FixMyStreet->config('BASE_URL') . '/report/' . $report_to_county_council->id; + my $expected2; + + FixMyStreet::override_config { + ALLOWED_COBRANDS => [ 'hart' ], + MAPIT_URL => 'http://mapit.uk/', + }, sub { + $mech->get_ok("/rss/area/Hart"); + my $cobrand = FixMyStreet::Cobrand->get_class_for_moniker('hart')->new(); + $expected2 = $cobrand->base_url . '/report/' . $report_to_council->id; + }; + + $mech->content_contains($expected1, 'non cobrand area report point to fixmystreet.com'); + $mech->content_contains($expected2, 'cobrand area report point to cobrand url'); +}; + +$mech->delete_user( $user1 ); done_testing(); diff --git a/t/app/controller/sample.jpg b/t/app/controller/sample.jpg Binary files differindex 23198cb83..b84c0c94e 100644 --- a/t/app/controller/sample.jpg +++ b/t/app/controller/sample.jpg diff --git a/t/app/controller/token.t b/t/app/controller/token.t new file mode 100644 index 000000000..ac88f4f7a --- /dev/null +++ b/t/app/controller/token.t @@ -0,0 +1,35 @@ +use strict; +use warnings; +use Test::More; +use utf8; + +use FixMyStreet::TestMech; +use FixMyStreet::App; + +my $mech = FixMyStreet::TestMech->new; +my $user = $mech->create_user_ok('bob@example.com', name => 'Bob'); + +subtest 'Zurich special case for C::Tokens->problem_confirm' => sub { + FixMyStreet::override_config { + ALLOWED_COBRANDS => ['zurich'], + }, sub { + my $c = FixMyStreet::App->new; + my $zurich = $mech->create_body_ok( 1, 'Zurich' ); + my ($report) = $mech->create_problems_for_body( + 1, $zurich->id, + { + state => 'unconfirmed', + confirmed => undef, + cobrand => 'zurich', + }); + + is $report->get_extra_metadata('email_confirmed'), undef, 'email_confirmed not yet set (sanity)'; + my $token = $c->model('DB::Token')->create({ scope => 'problem', data => $report->id }); + + $mech->get_ok('/P/' . $token->token); + $report->discard_changes; + is $report->get_extra_metadata('email_confirmed'), 1, 'email_confirmed set by Zurich special case'; + }; +}; + +done_testing; diff --git a/t/app/helpers/emails/html_test.html b/t/app/helpers/emails/html_test.html new file mode 100644 index 000000000..49f7b38d3 --- /dev/null +++ b/t/app/helpers/emails/html_test.html @@ -0,0 +1,15 @@ +Subject: test email ☺ + +Hello, + +This is a test email where foo: [% foo %]. + +utf8: 我们应该能够无缝处理UTF8编码 + + indented_text + +It additionally has an inline image! +<img src="[% inline_image('t/app/helpers/grey.gif') %]"> + +Yours, +FixMyStreet. diff --git a/t/app/helpers/emails/html_test.txt b/t/app/helpers/emails/html_test.txt new file mode 100644 index 000000000..692d25ccf --- /dev/null +++ b/t/app/helpers/emails/html_test.txt @@ -0,0 +1,14 @@ +Subject: test email ☺ + +Hello, + +This is a test email where foo: [% foo %]. + +utf8: 我们应该能够无缝处理UTF8编码 + + indented_text + +It additionally has an inline image! + +Yours, +FixMyStreet. diff --git a/t/app/helpers/emails/test.txt b/t/app/helpers/emails/test.txt new file mode 100644 index 000000000..1acd4b6ca --- /dev/null +++ b/t/app/helpers/emails/test.txt @@ -0,0 +1,14 @@ +Subject: test email ☺ + +Hello, + +This is a test email where foo: [% foo %]. + +utf8: 我们应该能够无缝处理UTF8编码 + + indented_text + +long line: Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. + +Yours, +FixMyStreet. diff --git a/t/app/helpers/grey.gif b/t/app/helpers/grey.gif Binary files differnew file mode 100644 index 000000000..98eee7d12 --- /dev/null +++ b/t/app/helpers/grey.gif diff --git a/t/app/helpers/send_email.t b/t/app/helpers/send_email.t index 8c043f701..66b771292 100644 --- a/t/app/helpers/send_email.t +++ b/t/app/helpers/send_email.t @@ -1,57 +1,134 @@ -#!/usr/bin/perl - use strict; use warnings; use utf8; +package FixMyStreet::Cobrand::Tester; +use parent 'FixMyStreet::Cobrand::Default'; +sub path_to_email_templates { [ FixMyStreet->path_to( 't', 'app', 'helpers', 'emails') ] } + +package main; + BEGIN { use FixMyStreet; FixMyStreet->test_mode(1); } -use Test::More tests => 6; +use Email::MIME; +use Test::More; +use Test::LongString; + +use Catalyst::Test 'FixMyStreet::App'; -use Email::Send::Test; -use Path::Class; +use Path::Tiny; -use_ok 'FixMyStreet::App'; -my $c = FixMyStreet::App->new( - { - request => Catalyst::Request->new( - { - base => URI->new('http://fixmystreet.com/'), - uri => URI->new('http://fixmystreet.com/') - } - ), - } -); -$c->setup_request(); +use FixMyStreet::TestMech; +my $mech = FixMyStreet::TestMech->new; + +my $c = ctx_request("/"); # set some values in the stash $c->stash->{foo} = 'bar'; # clear the email queue -Email::Send::Test->clear; +$mech->clear_emails_ok; # send the test email -ok $c->send_email( 'test.txt', { to => 'test@recipient.com' } ), - "sent an email"; +FixMyStreet::override_config { + ALLOWED_COBRANDS => [ 'tester' ], +}, sub { + ok $c->send_email( 'test.txt', { to => 'test@recipient.com' } ), + "sent an email"; +}; # check it got templated and sent correctly -my @emails = Email::Send::Test->emails; +my @emails = $mech->get_email; is scalar(@emails), 1, "caught one email"; # Get the email, check it has a date and then strip it out -my $email_as_string = $emails[0]->as_string; -ok $email_as_string =~ s{\s+Date:\s+\S.*?$}{}xms, "Found and stripped out date"; -ok $email_as_string =~ s{\s+Message-ID:\s+\S.*?$}{}xms, "Found and stripped out message ID (contains epoch)"; +my $email_as_string = $mech->get_first_email(@emails); +my $email = Email::MIME->new($email_as_string); -my $expected_email_content = file(__FILE__)->dir->file('send_email_sample.txt')->slurp; +my $expected_email_content = path(__FILE__)->parent->child('send_email_sample.txt')->slurp; my $name = FixMyStreet->config('CONTACT_NAME'); -$name = "\"$name\"" if $name =~ / /; -my $sender = $name . ' <' . FixMyStreet->config('CONTACT_EMAIL') . '>'; +my $sender = '"' . $name . '" <' . FixMyStreet->config('DO_NOT_REPLY_EMAIL') . '>'; $expected_email_content =~ s{CONTACT_EMAIL}{$sender}; +my $expected_email = Email::MIME->new($expected_email_content); + +is_deeply { $email->header_pairs }, { $expected_email->header_pairs }, 'MIME email headers ok'; +is_string $email->body, $expected_email->body, 'email is as expected'; + +subtest 'MIME attachments' => sub { + my $data = path(__FILE__)->parent->child('grey.gif')->slurp_raw; + + $mech->clear_emails_ok; + + ok $c->send_email( 'test.txt', + { to => 'test@recipient.com', + attachments => [ + { + body => $data, + attributes => { + filename => 'foo.gif', + content_type => 'image/gif', + encoding => 'quoted-printable', + name => 'foo.gif', + }, + }, + { + body => $data, + attributes => { + filename => 'bar.gif', + content_type => 'image/gif', + encoding => 'quoted-printable', + name => 'bar.gif', + }, + }, + ] + } ), "sent an email with MIME attachments"; + + @emails = $mech->get_email; + is scalar(@emails), 1, "caught one 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('send_email_sample_mime.txt')->slurp; + $expected_email_content =~ s{CONTACT_EMAIL}{$sender}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"; + }; + $mech->clear_emails_ok; +}; + +subtest 'Inline emails!' => sub { + ok $c->send_email( 'html_test.txt', { to => 'test@recipient.com' } ), "sent an email with email attachments"; + + my $email = $mech->get_email; + like $email->debug_structure, qr[ + \+\ multipart/related.*\n + \ {5}\+\ multipart/alternative.*\n + \ {10}\+\ text/plain.*\n + \ {10}\+\ text/html.*\n + \ {5}\+\ image/gif]x; + $mech->clear_emails_ok; +}; -is $email_as_string, -$expected_email_content, - "email is as expected"; +done_testing; diff --git a/t/app/helpers/send_email_sample.txt b/t/app/helpers/send_email_sample.txt index 2fe5272cb..68fe61f0e 100644 --- a/t/app/helpers/send_email_sample.txt +++ b/t/app/helpers/send_email_sample.txt @@ -1,29 +1,26 @@ MIME-Version: 1.0 -Subject: test email =?utf-8?Q?=E2=98=BA?= +Subject: =?UTF-8?B?dGVzdCBlbWFpbCDimLo=?= Content-Type: text/plain; charset="utf-8" To: test@recipient.com Content-Transfer-Encoding: quoted-printable From: CONTACT_EMAIL - Hello, +Hello, - This is a test email where foo: bar. +This is a test email where foo: bar. - utf8: =E6=88=91=E4=BB=AC=E5=BA=94=E8=AF=A5=E8=83=BD=E5=A4=9F=E6=97=A0= -=E7=BC=9D=E5=A4=84=E7=90=86UTF8=E7=BC=96=E7=A0=81 +utf8: =E6=88=91=E4=BB=AC=E5=BA=94=E8=AF=A5=E8=83=BD=E5=A4=9F=E6=97=A0=E7=BC= +=9D=E5=A4=84=E7=90=86UTF8=E7=BC=96=E7=A0=81 - indented_text - - long line: Lorem ipsum dolor sit amet, consectetur adipisicing - elit, sed do eiusmod tempor incididunt ut labore et dolore - magna aliqua. Ut enim ad minim veniam, quis nostrud - exercitation ullamco laboris nisi ut aliquip ex ea commodo - consequat. Duis aute irure dolor in reprehenderit in voluptate - velit esse cillum dolore eu fugiat nulla pariatur. Excepteur - sint occaecat cupidatat non proident, sunt in culpa qui officia - deserunt mollit anim id est laborum. - - Yours,=20=20 - FixMyStreet.=20= + indented_text +long line: Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do= + eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad min= +im veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea = +commodo consequat. Duis aute irure dolor in reprehenderit in voluptate veli= +t esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupi= +datat non proident, sunt in culpa qui officia deserunt mollit anim id est l= +aborum. +Yours, +FixMyStreet.= diff --git a/t/app/helpers/send_email_sample_mime.txt b/t/app/helpers/send_email_sample_mime.txt new file mode 100644 index 000000000..7b4ce91f6 --- /dev/null +++ b/t/app/helpers/send_email_sample_mime.txt @@ -0,0 +1,49 @@ +MIME-Version: 1.0
+Subject: =?UTF-8?B?dGVzdCBlbWFpbCDimLo=?=
+Content-Type: multipart/mixed; boundary="BOUNDARY"
+To: test@recipient.com
+Content-Transfer-Encoding: 7bit
+From: CONTACT_EMAIL
+
+
+--BOUNDARY
+Content-Type: text/plain; charset="utf-8"
+Content-Transfer-Encoding: quoted-printable
+
+Hello,
+
+This is a test email where foo: bar.
+
+utf8: =E6=88=91=E4=BB=AC=E5=BA=94=E8=AF=A5=E8=83=BD=E5=A4=9F=E6=97=A0=E7=BC=
+=9D=E5=A4=84=E7=90=86UTF8=E7=BC=96=E7=A0=81
+
+ indented_text
+
+long line: Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do=
+ eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad min=
+im veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea =
+commodo consequat. Duis aute irure dolor in reprehenderit in voluptate veli=
+t esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupi=
+datat non proident, sunt in culpa qui officia deserunt mollit anim id est l=
+aborum.
+
+Yours,
+FixMyStreet.=
+
+--BOUNDARY
+Content-Type: image/gif; name="foo.gif"
+Content-Disposition: inline; filename="foo.gif"
+Content-Transfer-Encoding: quoted-printable
+
+GIF89a=01=00=01=00=80=00=00=00=00=00=CC=CC=CC,=00=00=00=00=01=00=01=00=00=
+=02=01L=00;=
+
+--BOUNDARY
+Content-Type: image/gif; name="bar.gif"
+Content-Disposition: inline; filename="bar.gif"
+Content-Transfer-Encoding: quoted-printable
+
+GIF89a=01=00=01=00=80=00=00=00=00=00=CC=CC=CC,=00=00=00=00=01=00=01=00=00=
+=02=01L=00;=
+
+--BOUNDARY--
diff --git a/t/app/load_general_config.t b/t/app/load_general_config.t index 3855c2565..16ca2fc54 100644 --- a/t/app/load_general_config.t +++ b/t/app/load_general_config.t @@ -1,5 +1,3 @@ -#!/usr/bin/perl -w - use strict; use warnings; @@ -7,7 +5,6 @@ use Test::More tests => 2; use_ok 'FixMyStreet::App'; -# GAZE_URL chosen as it is unlikely to change -is FixMyStreet::App->config->{GAZE_URL}, # - 'http://gaze.mysociety.org/gaze', # +is FixMyStreet::App->config->{GAZE_URL}, + 'https://gaze.mysociety.org/gaze', "check that known config param is loaded"; diff --git a/t/app/model/alert_type.t b/t/app/model/alert_type.t index c592e9d3f..5e4fcec0a 100644 --- a/t/app/model/alert_type.t +++ b/t/app/model/alert_type.t @@ -9,21 +9,21 @@ my $mech = FixMyStreet::TestMech->new(); # this is the easiest way to make sure we're not going # to get any emails sent by data kicking about in the database -FixMyStreet::App->model('DB::AlertType')->email_alerts(); +FixMyStreet::DB->resultset('AlertType')->email_alerts(); $mech->clear_emails_ok; my $user = - FixMyStreet::App->model('DB::User') + FixMyStreet::DB->resultset('User') ->find_or_create( { email => 'test@example.com', name => 'Test User' } ); ok $user, "created test user"; my $user2 = - FixMyStreet::App->model('DB::User') + FixMyStreet::DB->resultset('User') ->find_or_create( { email => 'commenter@example.com', name => 'Commenter' } ); ok $user2, "created comment user"; my $user3 = - FixMyStreet::App->model('DB::User') + FixMyStreet::DB->resultset('User') ->find_or_create( { email => 'bystander@example.com', name => 'Bystander' } ); ok $user3, "created bystander"; @@ -36,10 +36,10 @@ my $dt = DateTime->new( second => 23 ); -my $report = FixMyStreet::App->model('DB::Problem')->find_or_create( +my $report = FixMyStreet::DB->resultset('Problem')->find_or_create( { postcode => 'SW1A 1AA', - council => '2504', + bodies_str => '2504', areas => ',105255,11806,11828,2247,2504,', category => 'Other', title => 'Test 2', @@ -62,7 +62,7 @@ my $report = FixMyStreet::App->model('DB::Problem')->find_or_create( my $report_id = $report->id; ok $report, "created test report - $report_id"; -my $comment = FixMyStreet::App->model('DB::Comment')->find_or_create( +my $comment = FixMyStreet::DB->resultset('Comment')->find_or_create( { problem_id => $report_id, user_id => $user2->id, @@ -74,7 +74,7 @@ my $comment = FixMyStreet::App->model('DB::Comment')->find_or_create( anonymous => 'f', } ); -my $comment2 = FixMyStreet::App->model('DB::Comment')->find_or_create( +my $comment2 = FixMyStreet::DB->resultset('Comment')->find_or_create( { problem_id => $report_id, user_id => $user2->id, @@ -87,26 +87,28 @@ my $comment2 = FixMyStreet::App->model('DB::Comment')->find_or_create( } ); -$comment->confirmed( \"ms_current_timestamp() - '3 days'::interval" ); +$comment->confirmed( \"current_timestamp - '3 days'::interval" ); $comment->update; -my $alert = FixMyStreet::App->model('DB::Alert')->find_or_create( +my $alert = FixMyStreet::DB->resultset('Alert')->find_or_create( { user => $user, parameter => $report_id, alert_type => 'new_updates', whensubscribed => $dt->ymd . ' ' . $dt->hms, confirmed => 1, + cobrand => 'default', } ); -my $alert3 = FixMyStreet::App->model('DB::Alert')->find_or_create( +my $alert3 = FixMyStreet::DB->resultset('Alert')->find_or_create( { user => $user3, parameter => $report_id, alert_type => 'new_updates', whensubscribed => $dt->ymd . ' ' . $dt->hms, confirmed => 1, + cobrand => 'default', } ); @@ -127,7 +129,7 @@ for my $test ( subtest "correct summary for state of $test->{state}" => sub { $mech->clear_emails_ok; - my $sent = FixMyStreet::App->model('DB::AlertSent')->search( + my $sent = FixMyStreet::DB->resultset('AlertSent')->search( { alert_id => [ $alert->id, $alert3->id ], parameter => $comment->id, @@ -137,18 +139,18 @@ for my $test ( $report->state( $test->{state} ); $report->update; - FixMyStreet::App->model('DB::AlertType')->email_alerts(); + FixMyStreet::DB->resultset('AlertType')->email_alerts(); $mech->email_count_is( 2 ); my @emails = $mech->get_email; my $msg = $test->{msg}; for my $email (@emails) { - my $body = $email->body; + my $body = $mech->get_text_body_from_email($email); my $to = $email->header('To'); like $body, qr/$msg/, 'email says problem is ' . $test->{state}; if ($to eq $user->email) { - like $body, qr{/M/}, 'contains problem login url'; + like $body, qr{/R/}, 'contains problem login url'; } elsif ($to eq $user3->email) { like $body, qr{/report/$report_id}, 'contains problem url'; } @@ -165,7 +167,7 @@ my $now = DateTime->now(); $report->confirmed( $now->ymd . ' ' . $now->hms ); $report->update(); -my $council_alert = FixMyStreet::App->model('DB::Alert')->find_or_create( +my $council_alert = FixMyStreet::DB->resultset('Alert')->find_or_create( { user => $user2, parameter => 2504, @@ -179,17 +181,20 @@ my $council_alert = FixMyStreet::App->model('DB::Alert')->find_or_create( subtest "correct text for title after URL" => sub { $mech->clear_emails_ok; - my $sent = FixMyStreet::App->model('DB::AlertSent')->search( + my $sent = FixMyStreet::DB->resultset('AlertSent')->search( { alert_id => $council_alert->id, parameter => $report->id, } )->delete; - FixMyStreet::App->model('DB::AlertType')->email_alerts(); + FixMyStreet::override_config { + MAPIT_URL => 'http://mapit.uk/', + }, sub { + FixMyStreet::DB->resultset('AlertType')->email_alerts(); + }; - my $email = $mech->get_email; (my $title = $report->title) =~ s/ /\\s+/; - my $body = $email->body; + my $body = $mech->get_text_body_from_email; like $body, qr#report/$report_id\s+-\s+$title#, 'email contains expected title'; }; @@ -281,7 +286,7 @@ foreach my $test ( desc => 'address only', addressLine => '18 North Bridge', locality => undef, - nearest => qr/: 18 North Bridge\n/, + nearest => qr/: 18 North Bridge\r?\n/, }, { desc => 'no fields', @@ -298,7 +303,7 @@ foreach my $test ( subtest "correct Nearest Road text with $test->{desc}" => sub { $mech->clear_emails_ok; - my $sent = FixMyStreet::App->model('DB::AlertSent')->search( + my $sent = FixMyStreet::DB->resultset('AlertSent')->search( { alert_id => $council_alert->id, parameter => $report->id, @@ -318,10 +323,13 @@ foreach my $test ( $report->geocode( $g ); $report->update(); - FixMyStreet::App->model('DB::AlertType')->email_alerts(); + FixMyStreet::override_config { + MAPIT_URL => 'http://mapit.uk/', + }, sub { + FixMyStreet::DB->resultset('AlertType')->email_alerts(); + }; - my $email = $mech->get_email; - my $body = $email->body; + my $body = $mech->get_text_body_from_email; if ( $test->{nearest} ) { like $body, $test->{nearest}, 'correct nearest line'; @@ -331,21 +339,21 @@ foreach my $test ( }; } -my $ward_alert = FixMyStreet::App->model('DB::Alert')->find_or_create( +my $ward_alert = FixMyStreet::DB->resultset('Alert')->find_or_create( { user => $user, parameter => 7117, alert_type => 'area_problems', whensubscribed => $dt->ymd . ' ' . $dt->hms, confirmed => 1, - cobrand => 'lichfielddc', + cobrand => 'hart', } ); -my $report_to_council = FixMyStreet::App->model('DB::Problem')->find_or_create( +my $report_to_council = FixMyStreet::DB->resultset('Problem')->find_or_create( { postcode => 'WS13 6YY', - council => '2434', + bodies_str => '2434', areas => ',105255,11806,11828,2247,2504,7117,', category => 'Other', title => 'council report', @@ -366,10 +374,10 @@ my $report_to_council = FixMyStreet::App->model('DB::Problem')->find_or_create( } ); -my $report_to_county_council = FixMyStreet::App->model('DB::Problem')->find_or_create( +my $report_to_county_council = FixMyStreet::DB->resultset('Problem')->find_or_create( { postcode => 'WS13 6YY', - council => '2240', + bodies_str => '2240', areas => ',105255,11806,11828,2247,2504,7117,', category => 'Other', title => 'county report', @@ -390,10 +398,10 @@ my $report_to_county_council = FixMyStreet::App->model('DB::Problem')->find_or_c } ); -my $report_outside_district = FixMyStreet::App->model('DB::Problem')->find_or_create( +my $report_outside_district = FixMyStreet::DB->resultset('Problem')->find_or_create( { postcode => 'WS13 6YY', - council => '2221', + bodies_str => '2221', areas => ',105255,11806,11828,2247,2504,7117,', category => 'Other', title => 'outside district report', @@ -417,20 +425,23 @@ my $report_outside_district = FixMyStreet::App->model('DB::Problem')->find_or_cr subtest "check alerts from cobrand send main site url for alerts for different council" => sub { $mech->clear_emails_ok; - my $sent = FixMyStreet::App->model('DB::AlertSent')->search( + my $sent = FixMyStreet::DB->resultset('AlertSent')->search( { alert_id => $ward_alert->id, } )->delete; - FixMyStreet::App->model('DB::AlertType')->email_alerts(); + FixMyStreet::override_config { + MAPIT_URL => 'http://mapit.uk/', + }, sub { + FixMyStreet::DB->resultset('AlertType')->email_alerts(); + }; - my $email = $mech->get_email; - my $body = $email->body; + my $body = $mech->get_text_body_from_email; - my $expected1 = mySociety::Config::get('BASE_URL') . '/report/' . $report_to_county_council->id; - my $expected3 = mySociety::Config::get('BASE_URL') . '/report/' . $report_outside_district->id; - my $cobrand = FixMyStreet::Cobrand->get_class_for_moniker('lichfielddc')->new(); + my $expected1 = FixMyStreet->config('BASE_URL') . '/report/' . $report_to_county_council->id; + my $expected3 = FixMyStreet->config('BASE_URL') . '/report/' . $report_outside_district->id; + my $cobrand = FixMyStreet::Cobrand->get_class_for_moniker('hart')->new(); my $expected2 = $cobrand->base_url . '/report/' . $report_to_council->id; like $body, qr#$expected1#, 'non cobrand area report point to fixmystreet.com'; @@ -439,14 +450,14 @@ subtest "check alerts from cobrand send main site url for alerts for different c }; -my $local_alert = FixMyStreet::App->model('DB::Alert')->find_or_create( +my $local_alert = FixMyStreet::DB->resultset('Alert')->find_or_create( { user => $user, parameter => -1.731322, parameter2 => 52.727588, alert_type => 'local_problems', whensubscribed => $dt->ymd . ' ' . $dt->hms, - cobrand => 'lichfielddc', + cobrand => 'hart', confirmed => 1, } ); @@ -454,26 +465,50 @@ my $local_alert = FixMyStreet::App->model('DB::Alert')->find_or_create( subtest "check local alerts from cobrand send main site url for alerts for different council" => sub { $mech->clear_emails_ok; - my $sent = FixMyStreet::App->model('DB::AlertSent')->search( + my $sent = FixMyStreet::DB->resultset('AlertSent')->search( { alert_id => $local_alert->id, } )->delete; - FixMyStreet::App->model('DB::AlertType')->email_alerts(); + FixMyStreet::DB->resultset('AlertType')->email_alerts(); - my $email = $mech->get_email; - my $body = $email->body; + my $body = $mech->get_text_body_from_email; - my $expected1 = mySociety::Config::get('BASE_URL') . '/report/' . $report_to_county_council->id; - my $cobrand = FixMyStreet::Cobrand->get_class_for_moniker('lichfielddc')->new(); + my $expected1 = FixMyStreet->config('BASE_URL') . '/report/' . $report_to_county_council->id; + my $cobrand = FixMyStreet::Cobrand->get_class_for_moniker('hart')->new(); my $expected2 = $cobrand->base_url . '/report/' . $report_to_council->id; like $body, qr#$expected1#, 'non cobrand area report point to fixmystreet.com'; like $body, qr#$expected2#, 'cobrand area report point to cobrand url'; }; -$report->comments->delete(); -$report->delete(); -done_testing(); +# Test that email alerts are sent in the right language. +subtest "correct i18n-ed summary for state of closed" => sub { + $mech->clear_emails_ok; + + $report->update( { state => 'closed' } ); + $alert->update( { lang => 'nb', cobrand => 'fiksgatami' } ); + + FixMyStreet::DB->resultset('AlertSent')->search( { + alert_id => $alert->id, + parameter => $comment->id, + } )->delete; + FixMyStreet::override_config { + ALLOWED_COBRANDS => [ 'fiksgatami' ], + }, sub { + FixMyStreet::DB->resultset('AlertType')->email_alerts(); + }; + + my $body = $mech->get_text_body_from_email; + my $msg = 'Denne rapporten er for tiden markert som lukket'; + like $body, qr/$msg/, 'email says problem is closed, in Norwegian'; +}; + +END { + $mech->delete_user($user) if $user; + $mech->delete_user($user2) if $user2; + $mech->delete_user($user3) if $user3; + done_testing(); +} diff --git a/t/app/model/comment.t b/t/app/model/comment.t index 93104c2e5..e83d795fc 100644 --- a/t/app/model/comment.t +++ b/t/app/model/comment.t @@ -1,14 +1,12 @@ -#!/usr/bin/perl - use strict; use warnings; use Test::More tests => 2; use FixMyStreet; -use FixMyStreet::App; +use FixMyStreet::DB; -my $comment_rs = FixMyStreet::App->model('DB::Comment'); +my $comment_rs = FixMyStreet::DB->resultset('Comment'); my $comment = $comment_rs->new( { @@ -23,5 +21,5 @@ my $comment = $comment_rs->new( } ); -is $comment->confirmed_local, undef, 'inflating null confirmed ok'; -is $comment->created_local, undef, 'inflating null confirmed ok'; +is $comment->confirmed, undef, 'inflating null confirmed ok'; +is $comment->created, undef, 'inflating null confirmed ok'; diff --git a/t/app/model/defecttype.t b/t/app/model/defecttype.t new file mode 100644 index 000000000..0f66ac684 --- /dev/null +++ b/t/app/model/defecttype.t @@ -0,0 +1,67 @@ +use strict; +use warnings; +use Test::More; + +use FixMyStreet::App; +use FixMyStreet::TestMech; +my $mech = FixMyStreet::TestMech->new; + +my $oxfordshire = $mech->create_body_ok(2237, 'Oxfordshire County Council', id => 2237); +my $potholes_contact = $mech->create_contact_ok( body_id => $oxfordshire->id, category => 'Potholes', email => 'potholes@example.com' ); +my $traffic_lights_contact =$mech->create_contact_ok( body_id => $oxfordshire->id, category => 'Traffic lights', email => 'lights@example.com' ); + +my $potholes_defect_type = FixMyStreet::App->model('DB::DefectType')->find_or_create( + { + body_id => 2237, + name => 'Potholes', + description => 'This defect type is to do with potholes' + } +); +$potholes_defect_type->contact_defect_types->find_or_create({ + contact_id => $potholes_contact->id, +}); + +my $general_defect_type = FixMyStreet::App->model('DB::DefectType')->find_or_create( + { + body_id => 2237, + name => 'All categories', + description => 'This defect type is for all categories' + } +); + + +subtest 'for_bodies returns correct results' => sub { + my $defect_types = FixMyStreet::App->model('DB::DefectType')->for_bodies( + [ $oxfordshire->id ], + 'Potholes' + ); + + is $defect_types->count, 2, 'Both defect types are included for Potholes category'; + + $defect_types = FixMyStreet::App->model('DB::DefectType')->for_bodies( + [ $oxfordshire->id ], + 'Traffic lights' + ); + + is $defect_types->count, 1, 'Only 1 defect type is included for Traffic lights category'; + is $defect_types->first->name, $general_defect_type->name, 'Correct defect type is returned for Traffic lights category'; +}; + +subtest 'Problem->defect_types behaves correctly' => sub { + my ($problem) = $mech->create_problems_for_body(1, $oxfordshire->id, 'Test', { + category => 'Potholes', + }); + + is $problem->defect_types->count, 2, 'Both defect types are available for the problem'; + + $problem->update({ category => 'Traffic lights' }); + is $problem->defect_types->count, 1, 'Only 1 defect type is included for Traffic lights category'; + is $problem->defect_types->first->name, $general_defect_type->name, 'Correct defect type is returned for Traffic lights category'; +}; + + +END { + $mech->delete_body( $oxfordshire ); + + done_testing(); +} diff --git a/t/app/model/extra.t b/t/app/model/extra.t new file mode 100644 index 000000000..3b46ce128 --- /dev/null +++ b/t/app/model/extra.t @@ -0,0 +1,109 @@ +use strict; +use warnings; +use Test::More; +use utf8; + +use FixMyStreet::DB; +use Data::Dumper; +use DateTime; + +my $db = FixMyStreet::DB->connect; +$db->txn_begin; + +my $body = $db->resultset('Body')->create({ name => 'ExtraTestingBody' }); + +my $serial = 1; +sub get_test_contact { + my $extra = shift; + my $contact = $db->resultset('Contact')->create({ + category => "Testing ${serial}", + body => $body, + email => 'test@example.com', + confirmed => 1, + deleted => 0, + editor => 'test script', + note => 'test script', + whenedited => DateTime->now(), + $extra ? ( extra => $extra ) : (), + }); + $serial++; + return $contact; +} + +subtest 'Old list layout transparently upgraded' => sub { + + subtest 'layout' => sub { + my $contact = get_test_contact([]); + + is_deeply $contact->get_extra(), { _fields => [] }, 'transparently upgraded to a hash'; + }; + + subtest 'extra fields' => sub { + my $contact = get_test_contact([]); + + is_deeply $contact->get_extra_fields(), [], 'No extra fields'; + + my @fields = ( { a => 1 }, { b => 2 } ); + $contact->set_extra_fields(@fields); + is_deeply $contact->extra, { _fields => \@fields }, 'extra fields set...'; + $contact->update; + $contact->discard_changes; + is_deeply $contact->extra, { _fields => \@fields }, '...and retrieved'; + is_deeply $contact->get_extra_fields(), \@fields, 'extra fields returned'; + }; + + subtest 'metadata' => sub { + my $contact = get_test_contact([]); + is_deeply $contact->get_extra_metadata, {}, 'No extra metadata'; + + $contact->set_extra_metadata('foo' => 'bar'); + is $contact->get_extra_metadata('foo'), 'bar', 'extra metadata set...'; + $contact->update; + $contact->discard_changes; + is $contact->get_extra_metadata('foo'), 'bar', '... and retrieved'; + is_deeply $contact->get_extra_metadata, { foo => 'bar' }, 'No extra metadata'; + }; +}; + +subtest 'Default hash layout' => sub { + subtest 'layout' => sub { + my $contact = get_test_contact(); + + is_deeply $contact->get_extra(), {}, 'default layout is hash'; + }; + + subtest 'extra fields' => sub { + my $contact = get_test_contact(); + + is_deeply $contact->get_extra_fields(), [], 'No extra fields'; + + my @fields = ( { a => 1 }, { b => 2 } ); + $contact->set_extra_fields(@fields); + is_deeply $contact->get_extra_fields, \@fields, 'extra fields set...'; + $contact->update; + $contact->discard_changes; + is_deeply $contact->get_extra_fields(), \@fields, '... and returned'; + is_deeply $contact->extra, { _fields => \@fields }, '(sanity check layout)'; + }; + + subtest 'metadata' => sub { + my $contact = get_test_contact(); + is_deeply $contact->get_extra_metadata, {}, 'No extra metadata'; + + $contact->set_extra_metadata('foo' => 'bar'); + is $contact->get_extra_metadata('foo'), 'bar', 'extra metadata set...'; + $contact->update; + $contact->discard_changes; + is $contact->get_extra_metadata( 'foo'), 'bar', '... and retrieved'; + is_deeply $contact->get_extra_metadata, { foo => 'bar' }, 'No extra metadata'; + + $contact->unset_extra_metadata('foo'); + is $contact->get_extra_metadata('foo'), undef, 'extra metadata now unset'; + $contact->update; + $contact->discard_changes; + is $contact->get_extra_metadata('foo'), undef, '... after retrieval'; + }; +}; + +$db->txn_rollback; +done_testing(); diff --git a/t/app/model/moderation.t b/t/app/model/moderation.t new file mode 100644 index 000000000..8fa333db4 --- /dev/null +++ b/t/app/model/moderation.t @@ -0,0 +1,66 @@ +use strict; +use warnings; +use Test::More; +use Test::Exception; +use utf8; + +use FixMyStreet::DB; +use Data::Dumper; +use DateTime; + +my $dt = DateTime->now; +my $user = FixMyStreet::DB->resultset('User')->find_or_create({ + name => 'Bob', email => 'bob@example.com', +}); + +sub get_report_and_original_data { + my $report = FixMyStreet::DB->resultset('Problem')->create( + { + postcode => 'BR1 3SB', + bodies_str => '', + areas => ",,", + category => 'Other', + title => 'test', + detail => 'test', + used_map => 't', + name => 'Anon', + anonymous => 't', + state => 'confirmed', + confirmed => $dt->ymd . ' ' . $dt->hms, + lang => 'en-gb', + service => '', + cobrand => 'default', + cobrand_data => '', + send_questionnaire => 't', + latitude => '51.4129', + longitude => '0.007831', + user => $user, + }); + my $original = $report->create_related( moderation_original_data => { + anonymous => 't', + title => 'test', + detail => 'test', + photo => 'f', + } ); + + return ($report, $original); +} + +subtest 'Explicit Deletion (sanity test)' => sub { + my ($report, $orig) = get_report_and_original_data; + + lives_ok { + $orig->delete; + $report->delete; + }; +}; + +subtest 'Implicit Chained Deletion' => sub { + my ($report, $orig) = get_report_and_original_data; + + lives_ok { + $report->delete; + }; +}; + +done_testing(); diff --git a/t/app/model/photoset.t b/t/app/model/photoset.t new file mode 100644 index 000000000..54530adfb --- /dev/null +++ b/t/app/model/photoset.t @@ -0,0 +1,81 @@ +use strict; +use warnings; +use Test::More; +use Test::Exception; +use utf8; + +use FixMyStreet::DB; +use DateTime; +use Path::Tiny 'path'; +use File::Temp 'tempdir'; + +my $dt = DateTime->now; + +my $UPLOAD_DIR = tempdir( CLEANUP => 1 ); + +my $db = FixMyStreet::DB->storage->schema; + +my $user = $db->resultset('User')->find_or_create({ + name => 'Bob', email => 'bob@example.com', +}); + +FixMyStreet::override_config { + UPLOAD_DIR => $UPLOAD_DIR, +}, sub { + +$db->txn_begin; + +my $image_path = path('t/app/controller/sample.jpg'); + +sub make_report { + my $photo_data = shift; + return $db->resultset('Problem')->create({ + postcode => 'BR1 3SB', + bodies_str => '', + areas => ",,", + category => 'Other', + title => 'test', + detail => 'test', + used_map => 't', + name => 'Anon', + anonymous => 't', + state => 'confirmed', + confirmed => $dt, + lang => 'en-gb', + service => '', + cobrand => 'default', + cobrand_data => '', + send_questionnaire => 't', + latitude => '51.4129', + longitude => '0.007831', + user => $user, + photo => $photo_data, + }); +} + + +subtest 'Photoset with photo inline in DB' => sub { + my $report = make_report( $image_path->slurp ); + my $photoset = $report->get_photoset(); + is $photoset->num_images, 1, 'Found just 1 image'; + is $photoset->data, '74e3362283b6ef0c48686fb0e161da4043bbcc97.jpeg'; +}; + +$image_path->copy( path( $UPLOAD_DIR, '0123456789012345678901234567890123456789.jpeg' ) ); +subtest 'Photoset with 1 referenced photo' => sub { + my $report = make_report( '0123456789012345678901234567890123456789' ); + my $photoset = $report->get_photoset(); + is $photoset->num_images, 1, 'Found just 1 image'; +}; + +subtest 'Photoset with 3 referenced photo' => sub { + my $report = make_report( '0123456789012345678901234567890123456789,0123456789012345678901234567890123456789,0123456789012345678901234567890123456789' ); + my $photoset = $report->get_photoset(); + is $photoset->num_images, 3, 'Found 3 images'; +}; + +$db->txn_rollback; + +}; + +done_testing(); diff --git a/t/app/model/problem.t b/t/app/model/problem.t index baec6baf0..6b1be0a76 100644 --- a/t/app/model/problem.t +++ b/t/app/model/problem.t @@ -1,24 +1,24 @@ -#!/usr/bin/perl - use strict; use warnings; use Test::More; +use FixMyStreet::TestMech; use FixMyStreet; use FixMyStreet::App; -use FixMyStreet::TestMech; +use FixMyStreet::DB; use mySociety::Locale; +use Sub::Override; mySociety::Locale::gettext_domain('FixMyStreet'); -my $problem_rs = FixMyStreet::App->model('DB::Problem'); +my $problem_rs = FixMyStreet::DB->resultset('Problem'); my $problem = $problem_rs->new( { postcode => 'EH99 1SP', - latitude => '51.5016605453401', - longitude => '-0.142497580865087', + latitude => '54.5', + longitude => '-1.5', areas => 1, title => '', detail => '', @@ -32,47 +32,40 @@ my $problem = $problem_rs->new( } ); -is $problem->confirmed_local, undef, 'inflating null confirmed ok'; -is $problem->whensent_local, undef, 'inflating null confirmed ok'; -is $problem->lastupdate_local, undef, 'inflating null confirmed ok'; -is $problem->created_local, undef, 'inflating null confirmed ok'; +my $visible_states = $problem->visible_states; +is_deeply $visible_states, { + 'confirmed' => 1, + 'investigating' => 1, + 'in progress' => 1, + 'planned' => 1, + 'action scheduled' => 1, + 'fixed' => 1, + 'fixed - council' => 1, + 'fixed - user' => 1, + 'unable to fix' => 1, + 'not responsible' => 1, + 'duplicate' => 1, + 'closed' => 1, + 'internal referral' => 1, + }, 'visible_states is correct'; + +is $problem->confirmed, undef, 'inflating null confirmed ok'; +is $problem->whensent, undef, 'inflating null confirmed ok'; +is $problem->lastupdate, undef, 'inflating null confirmed ok'; +is $problem->created, undef, 'inflating null confirmed ok'; -for my $test ( +for my $test ( { desc => 'more or less empty problem', changed => {}, errors => { title => 'Please enter a subject', detail => 'Please enter some details', - council => 'No council selected', + bodies => 'No council selected', name => 'Please enter your name', } }, { - desc => 'name too short', - changed => { - name => 'xx', - }, - errors => { - title => 'Please enter a subject', - detail => 'Please enter some details', - council => 'No council selected', - name => 'Please enter your full name, councils need this information – if you do not wish your name to be shown on the site, untick the box below', - } - }, - { - desc => 'name is anonymous', - changed => { - name => 'anonymous', - }, - errors => { - title => 'Please enter a subject', - detail => 'Please enter some details', - council => 'No council selected', - name => 'Please enter your full name, councils need this information – if you do not wish your name to be shown on the site, untick the box below', - } - }, - { desc => 'correct name', changed => { name => 'A User', @@ -80,7 +73,7 @@ for my $test ( errors => { title => 'Please enter a subject', detail => 'Please enter some details', - council => 'No council selected', + bodies => 'No council selected', } }, { @@ -90,7 +83,7 @@ for my $test ( }, errors => { detail => 'Please enter some details', - council => 'No council selected', + bodies => 'No council selected', } }, { @@ -99,22 +92,22 @@ for my $test ( detail => 'Some information about the problem', }, errors => { - council => 'No council selected', + bodies => 'No council selected', } }, { - desc => 'incorrectly formatted council', + desc => 'incorrectly formatted body', changed => { - council => 'my council', + bodies_str => 'my body', }, errors => { - council => 'No council selected', + bodies => 'No council selected', } }, { - desc => 'correctly formatted council', + desc => 'correctly formatted body', changed => { - council => '1001', + bodies_str => '1001', }, errors => { } @@ -153,9 +146,9 @@ for my $test ( }; } -my $user = FixMyStreet::App->model('DB::User')->find_or_create( +my $user = FixMyStreet::DB->resultset('User')->find_or_create( { - email => 'system_user@example.com' + email => 'system_user@example.net' } ); @@ -167,6 +160,10 @@ $problem->insert; my $tz_local = DateTime::TimeZone->new( name => 'local' ); +my $body = FixMyStreet::DB->resultset('Body')->new({ + name => 'Edinburgh City Council' +}); + for my $test ( { desc => 'request older than problem ignored', @@ -174,9 +171,6 @@ for my $test ( request => { updated_datetime => DateTime::Format::W3CDTF->new()->format_datetime( DateTime->now()->set_time_zone( $tz_local )->subtract( days => 2 ) ), }, - council => { - name => 'Edinburgh City Council', - }, created => 0, }, { @@ -187,9 +181,6 @@ for my $test ( status => 'open', status_notes => 'this is an update from the council', }, - council => { - name => 'Edinburgh City Council', - }, created => 1, state => 'confirmed', mark_fixed => 0, @@ -203,9 +194,6 @@ for my $test ( status => 'closed', status_notes => 'the council have fixed this', }, - council => { - name => 'Edinburgh City Council', - }, created => 1, state => 'fixed', mark_fixed => 1, @@ -219,9 +207,6 @@ for my $test ( status => 'open', status_notes => 'the council do not think this is fixed', }, - council => { - name => 'Edinburgh City Council', - }, created => 1, start_state => 'fixed', state => 'fixed', @@ -238,7 +223,7 @@ for my $test ( $problem->update; my $w3c = DateTime::Format::W3CDTF->new(); - my $ret = $problem->update_from_open311_service_request( $test->{request}, $test->{council}, $user ); + my $ret = $problem->update_from_open311_service_request( $test->{request}, $body, $user ); is $ret, $test->{created}, 'return value'; return unless $test->{created}; @@ -258,12 +243,13 @@ for my $test ( }; } -for my $test ( +for my $test ( { state => 'partial', is_visible => 0, is_fixed => 0, is_open => 0, + is_in_progress => 0, is_closed => 0, }, { @@ -271,6 +257,7 @@ for my $test ( is_visible => 0, is_fixed => 0, is_open => 0, + is_in_progress => 0, is_closed => 0, }, { @@ -278,6 +265,7 @@ for my $test ( is_visible => 0, is_fixed => 0, is_open => 0, + is_in_progress => 0, is_closed => 0, }, { @@ -285,6 +273,7 @@ for my $test ( is_visible => 1, is_fixed => 0, is_open => 1, + is_in_progress => 0, is_closed => 0, }, { @@ -292,6 +281,7 @@ for my $test ( is_visible => 1, is_fixed => 0, is_open => 1, + is_in_progress => 1, is_closed => 0, }, { @@ -299,6 +289,15 @@ for my $test ( is_visible => 1, is_fixed => 0, is_open => 1, + is_in_progress => 1, + is_closed => 0, + }, + { + state => 'action scheduled', + is_visible => 1, + is_fixed => 0, + is_open => 1, + is_in_progress => 1, is_closed => 0, }, { @@ -306,13 +305,39 @@ for my $test ( is_visible => 1, is_fixed => 0, is_open => 1, + is_in_progress => 1, is_closed => 0, }, { + state => 'duplicate', + is_visible => 1, + is_fixed => 0, + is_open => 0, + is_in_progress => 0, + is_closed => 1, + }, + { + state => 'not responsible', + is_visible => 1, + is_fixed => 0, + is_open => 0, + is_in_progress => 0, + is_closed => 1, + }, + { + state => 'unable to fix', + is_visible => 1, + is_fixed => 0, + is_open => 0, + is_in_progress => 0, + is_closed => 1, + }, + { state => 'fixed', is_visible => 1, is_fixed => 1, is_open => 0, + is_in_progress => 0, is_closed => 0, }, { @@ -320,6 +345,7 @@ for my $test ( is_visible => 1, is_fixed => 1, is_open => 0, + is_in_progress => 0, is_closed => 0, }, { @@ -327,6 +353,7 @@ for my $test ( is_visible => 1, is_fixed => 1, is_open => 0, + is_in_progress => 0, is_closed => 0, }, { @@ -334,6 +361,7 @@ for my $test ( is_visible => 1, is_fixed => 0, is_open => 0, + is_in_progress => 0, is_closed => 1, }, ) { @@ -343,64 +371,75 @@ for my $test ( is $problem->is_fixed, $test->{is_fixed}, 'is_fixed'; is $problem->is_closed, $test->{is_closed}, 'is_closed'; is $problem->is_open, $test->{is_open}, 'is_open'; + is $problem->is_in_progress, $test->{is_in_progress}, 'is_in_progress'; }; } my $mech = FixMyStreet::TestMech->new(); -my %contact_params = ( - confirmed => 1, - deleted => 0, - editor => 'Test', - whenedited => \'ms_current_timestamp()', - note => 'Created for test', -); +my %body_ids; +my @bodies; +for my $body ( + { area_id => 2651, name => 'City of Edinburgh Council' }, + { area_id => 2226, name => 'Gloucestershire County Council' }, + { area_id => 2326, name => 'Cheltenham Borough Council' }, + { area_id => 2333, name => 'Hart Council' }, + { area_id => 2227, name => 'Hampshire County Council' }, + { area_id => 14279, name => 'Ballymoney Borough Council' }, + { area_id => 2636, name => 'Isle of Wight Council' }, + { area_id => 2649, name => 'Fife Council' }, + { area_id => 14279, name => 'TransportNI (Western)' }, +) { + my $aid = $body->{area_id}; + my $body = $mech->create_body_ok($aid, $body->{name}); + if ($body_ids{$aid}) { + $body_ids{$aid} = [ $body_ids{$aid}, $body->id ]; + } else { + $body_ids{$aid} = $body->id; + } + push @bodies, $body; +} + # Let's make some contacts to send things to! -FixMyStreet::App->model('DB::Contact')->search( { - email => { 'like', '%example.com' }, -} )->delete; -my @contacts; for my $contact ( { - body_id => 2651, # Edinburgh + body_id => $body_ids{2651}, # Edinburgh category => 'potholes', email => 'test@example.org', }, { - body_id => 2226, # Gloucestershire + body_id => $body_ids{2226}, # Gloucestershire category => 'potholes', email => '2226@example.org', }, { - body_id => 2326, # Cheltenham + body_id => $body_ids{2326}, # Cheltenham category => 'potholes', email => '2326@example.org', }, { - body_id => 2434, # Lichfield + body_id => $body_ids{2333}, # Hart category => 'potholes', email => 'trees@example.com', }, { - body_id => 2240, # Staffordshire + body_id => $body_ids{2227}, # Hampshire category => 'potholes', email => 'highways@example.com', }, { - body_id => 14279, # Ballymoney + body_id => $body_ids{14279}[1], # TransportNI category => 'Street lighting', email => 'roads.western@drdni.example.org', }, { - body_id => 14279, # Ballymoney + body_id => $body_ids{14279}[0], # Ballymoney category => 'Graffiti', - email => 'highways@example.com', + email => 'highways@example.net', }, { confirmed => 0, - body_id => 2636, # Isle of Wight + body_id => $body_ids{2636}, # Isle of Wight category => 'potholes', email => '2636@example.com', } ) { - my $new_contact = FixMyStreet::App->model('DB::Contact')->find_or_create( { %contact_params, %$contact } ); - ok $new_contact, "created test contact"; - push @contacts, $new_contact; + $mech->create_contact_ok( %$contact ); } my %common = ( - email => 'system_user@example.com', + email => 'system_user@example.net', name => 'Andrew Smith', ); foreach my $test ( { @@ -410,21 +449,21 @@ foreach my $test ( { email_count => 1, dear => qr'Dear City of Edinburgh Council', to => qr'City of Edinburgh Council', - council => 2651, + body => $body_ids{2651}, }, { %common, desc => 'no email sent if no unsent problems', unset_whendef => 0, email_count => 0, - council => 2651, + body => $body_ids{2651}, }, { %common, desc => 'email to two tier council', unset_whendef => 1, email_count => 1, - to => qr'Gloucestershire County Council.*Cheltenham Borough Council', - dear => qr'Dear Gloucestershire County Council and Cheltenham Borough', - council => '2226,2326', + to => qr'Cheltenham Borough Council.*Gloucestershire County Council', + dear => qr'Dear Cheltenham Borough Council and Gloucestershire County', + body => $body_ids{2226} . ',' . $body_ids{2326}, multiple => 1, }, { %common, @@ -433,28 +472,29 @@ foreach my $test ( { email_count => 1, to => qr'Gloucestershire County Council" <2226@example', dear => qr'Dear Gloucestershire County Council,', - council => '2226|2649', + body => $body_ids{2226}, + body_missing => $body_ids{2649}, missing => qr'problem might be the responsibility of Fife.*Council'ms, }, { %common, desc => 'email to two tier council that only shows district, district', unset_whendef => 1, email_count => 1, - to => qr'Lichfield District Council', - dear => qr'Dear Lichfield District Council,', - council => '2434', - cobrand => 'lichfielddc', - url => 'lichfielddc.', + to => qr'Hart Council', + dear => qr'Dear Hart Council,', + body => $body_ids{2333}, + cobrand => 'hart', + url => 'hart.', }, { %common, desc => 'email to two tier council that only shows district, county', unset_whendef => 1, email_count => 1, - to => qr'Staffordshire County Council" <highways@example', - dear => qr'Dear Staffordshire County Council,', - council => '2240', - cobrand => 'lichfielddc', - url => '', + to => qr'Hampshire County Council" <highways@example', + dear => qr'Dear Hampshire County Council,', + body => $body_ids{2227}, + cobrand => 'hart', + url => 'www.', }, { %common, desc => 'directs NI correctly, 1', @@ -462,72 +502,96 @@ foreach my $test ( { email_count => 1, dear => qr'Dear Ballymoney Borough Council', to => qr'Ballymoney Borough Council', - council => 14279, + body => $body_ids{14279}[0], category => 'Graffiti', + longitude => -6.5, + # As Ballmoney contact has same domain as reporter, the From line will + # become a unique reply address and Reply-To will become the reporter + reply_to => 1, }, { %common, desc => 'directs NI correctly, 2', unset_whendef => 1, email_count => 1, - dear => qr'Dear Roads Service \(Western\)', - to => qr'Roads Service \(Western\)" <roads', - council => 14279, + dear => qr'Dear TransportNI \(Western\)', + to => qr'TransportNI \(Western\)" <roads', + body => $body_ids{14279}[1], category => 'Street lighting', + longitude => -6.5, }, { %common, desc => 'does not send to unconfirmed contact', unset_whendef => 1, stays_unsent => 1, email_count => 0, - council => 2636, + body => $body_ids{2636}, }, ) { subtest $test->{ desc } => sub { - if ( $test->{cobrand} && $test->{cobrand} =~ /lichfielddc/ && !FixMyStreet::Cobrand->exists('lichfielddc') ) { - plan skip_all => 'Skipping Lichfield tests without Lichfield cobrand'; + my $override = { + ALLOWED_COBRANDS => [ 'fixmystreet' ], + BASE_URL => 'http://www.fixmystreet.com', + MAPIT_URL => 'http://mapit.mysociety.org/', + }; + if ( $test->{cobrand} && $test->{cobrand} =~ /hart/ ) { + $override->{ALLOWED_COBRANDS} = [ 'hart' ]; } $mech->clear_emails_ok; - FixMyStreet::App->model('DB::Problem')->search( + $problem_rs->search( { whensent => undef } - )->update( { whensent => \'ms_current_timestamp()' } ); + )->update( { whensent => \'current_timestamp' } ); $problem->discard_changes; $problem->update( { - council => $test->{ council }, + bodies_str => $test->{ body }, + bodies_missing => $test->{ body_missing }, state => 'confirmed', - confirmed => \'ms_current_timestamp()', - whensent => $test->{ unset_whendef } ? undef : \'ms_current_timestamp()', + confirmed => \'current_timestamp', + whensent => $test->{ unset_whendef } ? undef : \'current_timestamp', category => $test->{ category } || 'potholes', name => $test->{ name }, cobrand => $test->{ cobrand } || 'fixmystreet', + longitude => $test->{longitude} || '-1.5', } ); - FixMyStreet::App->model('DB::Problem')->send_reports(); + FixMyStreet::override_config $override, sub { + $problem_rs->send_reports(); + }; $mech->email_count_is( $test->{ email_count } ); if ( $test->{ email_count } ) { my $email = $mech->get_email; like $email->header('To'), $test->{ to }, 'to line looks correct'; - is $email->header('From'), sprintf('"%s" <%s>', $test->{ name }, $test->{ email } ), 'from line looks correct'; + if ($test->{reply_to}) { + is $email->header('Reply-To'), sprintf('"%s" <%s>', $test->{ name }, $test->{ email } ), 'Reply-To line looks correct'; + like $email->header('From'), qr/"$test->{name}" <fms-report-\d+-\w+\@example.org>/, 'from line looks correct'; + } else { + is $email->header('From'), sprintf('"%s" <%s>', $test->{ name }, $test->{ email } ), 'from line looks correct'; + } like $email->header('Subject'), qr/A Title/, 'subject line looks correct'; - like $email->body, qr/A user of FixMyStreet/, 'email body looks a bit like a report'; - like $email->body, qr/Subject: A Title/, 'more email body checking'; - like $email->body, $test->{ dear }, 'Salutation looks correct'; + my $body = $mech->get_text_body_from_email($email); + like $body, qr/A user of FixMyStreet/, 'email body looks a bit like a report'; + like $body, qr/Subject: A Title/, 'more email body checking'; + like $body, $test->{ dear }, 'Salutation looks correct'; + if ($test->{longitude}) { + like $body, qr{Easting/Northing \(IE\): 297279/362371}; + } else { + like $body, qr{Easting/Northing: }; + } if ( $test->{multiple} ) { - like $email->body, qr/This email has been sent to several councils /, 'multiple council text correct'; + like $body, qr/This email has been sent to several councils /, 'multiple body text correct'; } elsif ( $test->{ missing } ) { - like $email->body, $test->{ missing }, 'missing council information correct'; + like $body, $test->{ missing }, 'missing body information correct'; } if ( $test->{url} ) { - (my $base_url = FixMyStreet->config('BASE_URL')) =~ s{http://}{}; my $id = $problem->id; - like $email->body, qr[$test->{url}$base_url/report/$id], 'URL present is correct'; + like $body, qr[$test->{url}fixmystreet.com/report/$id], 'URL present is correct'; } $problem->discard_changes; @@ -540,12 +604,290 @@ foreach my $test ( { }; } -$problem->comments->delete; -$problem->delete; -$user->delete; +subtest 'check can set mutiple emails as a single contact' => sub { + my $override = { + ALLOWED_COBRANDS => [ 'fixmystreet' ], + BASE_URL => 'http://www.fixmystreet.com', + MAPIT_URL => 'http://mapit.mysociety.org/', + }; -foreach (@contacts) { - $_->delete; -} + my $contact = { + body_id => $body_ids{2651}, # Edinburgh + category => 'trees', + email => '2636@example.com,2636-2@example.com', + }; + $mech->create_contact_ok( %$contact ); -done_testing(); + $mech->clear_emails_ok; + + $problem_rs->search( + { + whensent => undef + } + )->update( { whensent => \'current_timestamp' } ); + + $problem->discard_changes; + $problem->update( { + bodies_str => $contact->{ body_id }, + state => 'confirmed', + confirmed => \'current_timestamp', + whensent => undef, + category => 'trees', + name => 'Test User', + cobrand => 'fixmystreet', + send_fail_count => 0, + } ); + + FixMyStreet::override_config $override, sub { + $problem_rs->send_reports(); + }; + + $mech->email_count_is(1); + my $email = $mech->get_email; + is $email->header('To'), '"City of Edinburgh Council" <2636@example.com>, "City of Edinburgh Council" <2636-2@example.com>', 'To contains two email addresses'; +}; + +subtest 'check can turn on report sent email alerts' => sub { + my $send_confirmation_mail_override = Sub::Override->new( + "FixMyStreet::Cobrand::Default::report_sent_confirmation_email", + sub { return 1; } + ); + $mech->clear_emails_ok; + + $problem_rs->search( + { + whensent => undef + } + )->update( { whensent => \'current_timestamp' } ); + + $problem->discard_changes; + $problem->update( { + bodies_str => $body_ids{2651}, + state => 'confirmed', + confirmed => \'current_timestamp', + whensent => undef, + category => 'potholes', + name => 'Test User', + cobrand => 'fixmystreet', + send_fail_count => 0, + } ); + + $problem_rs->send_reports(); + + $mech->email_count_is( 2 ); + my @emails = $mech->get_email; + my $email = $emails[0]; + + like $email->header('To'),qr/City of Edinburgh Council/, 'to line looks correct'; + is $email->header('From'), '"Test User" <system_user@example.net>', 'from line looks correct'; + like $email->header('Subject'), qr/A Title/, 'subject line looks correct'; + my $body = $mech->get_text_body_from_email($email); + like $body, qr/A user of FixMyStreet/, 'email body looks a bit like a report'; + like $body, qr/Subject: A Title/, 'more email body checking'; + like $body, qr/Dear City of Edinburgh Council/, 'Salutation looks correct'; + + $problem->discard_changes; + ok defined( $problem->whensent ), 'whensent set'; + + $email = $emails[1]; + like $email->header('Subject'), qr/FixMyStreet Report Sent/, 'report sent email title correct'; + $body = $mech->get_text_body_from_email($email); + like $body, qr/to submit your report/, 'report sent body correct'; + + $send_confirmation_mail_override->restore(); +}; + + +subtest 'check iOS app store test reports not sent' => sub { + $mech->clear_emails_ok; + + $problem_rs->search( + { + whensent => undef + } + )->update( { whensent => \'current_timestamp' } ); + + $problem->discard_changes; + $problem->update( { + bodies_str => $body_ids{2651}, + title => 'App store test', + state => 'confirmed', + confirmed => \'current_timestamp', + whensent => undef, + category => 'potholes', + send_fail_count => 0, + } ); + + $problem_rs->send_reports(); + + $mech->email_count_is( 0 ); + + $problem->discard_changes(); + is $problem->state, 'hidden', 'iOS test reports are hidden automatically'; + is $problem->whensent, undef, 'iOS test reports are not sent'; +}; + +subtest 'check reports from abuser not sent' => sub { + $mech->clear_emails_ok; + + $problem_rs->search( + { + whensent => undef + } + )->update( { whensent => \'current_timestamp' } ); + + $problem->discard_changes; + $problem->update( { + bodies_str => $body_ids{2651}, + title => 'Report', + state => 'confirmed', + confirmed => \'current_timestamp', + whensent => undef, + category => 'potholes', + send_fail_count => 0, + } ); + + $problem_rs->send_reports(); + + $mech->email_count_is( 1 ); + + $problem->discard_changes(); + ok $problem->whensent, 'Report has been sent'; + + $problem->update( { + state => 'confirmed', + confirmed => \'current_timestamp', + whensent => undef, + } ); + + my $abuse = FixMyStreet::DB->resultset('Abuse')->create( { email => $problem->user->email } ); + + $mech->clear_emails_ok; + $problem_rs->send_reports(); + + $mech->email_count_is( 0 ); + + $problem->discard_changes(); + is $problem->state, 'hidden', 'reports from abuse user are hidden automatically'; + is $problem->whensent, undef, 'reports from abuse user are not sent'; + + ok $abuse->delete(), 'user removed from abuse table'; +}; + +subtest 'check response templates' => sub { + my $c1 = $mech->create_contact_ok(category => 'Potholes', body_id => $body_ids{2651}, email => 'p'); + my $c2 = $mech->create_contact_ok(category => 'Graffiti', body_id => $body_ids{2651}, email => 'g'); + my $t1 = FixMyStreet::DB->resultset('ResponseTemplate')->create({ body_id => $body_ids{2651}, title => "Title 1", text => "Text 1" }); + my $t2 = FixMyStreet::DB->resultset('ResponseTemplate')->create({ body_id => $body_ids{2651}, title => "Title 2", text => "Text 2" }); + my $t3 = FixMyStreet::DB->resultset('ResponseTemplate')->create({ body_id => $body_ids{2651}, title => "Title 3", text => "Text 3" }); + $t1->add_to_contacts($c1); + $t2->add_to_contacts($c2); + my ($problem) = $mech->create_problems_for_body(1, $body_ids{2651}, 'TITLE'); + is $problem->response_templates, 1, 'Only the global template returned'; + ($problem) = $mech->create_problems_for_body(1, $body_ids{2651}, 'TITLE', { category => 'Potholes' }); + is $problem->response_templates, 2, 'Global and pothole templates returned'; +}; + +subtest 'check duplicate reports' => sub { + my ($problem1, $problem2) = $mech->create_problems_for_body(2, $body_ids{2651}, 'TITLE'); + $problem1->set_extra_metadata(duplicate_of => $problem2->id); + $problem1->state('duplicate'); + $problem1->update; + + is $problem1->duplicate_of->title, $problem2->title, 'problem1 returns correct problem from duplicate_of'; + is scalar @{ $problem2->duplicates }, 1, 'problem2 has correct number of duplicates'; + is $problem2->duplicates->[0]->title, $problem1->title, 'problem2 includes problem1 in duplicates'; +}; + +subtest 'generates a tokenised url for a user' => sub { + my ($problem) = $mech->create_problems_for_body(1, $body_ids{2651}, 'TITLE'); + my $url = $problem->tokenised_url($user); + (my $token = $url) =~ s/\/M\///g; + + like $url, qr/\/M\//, 'problem generates tokenised url'; + + my $token_obj = FixMyStreet::App->model('DB::Token')->find( { + scope => 'email_sign_in', token => $token + } ); + is $token, $token_obj->token, 'token is generated in database with correct scope'; + is $token_obj->data->{r}, $problem->url, 'token has correct redirect data'; +}; + +subtest 'stores params in a token' => sub { + my ($problem) = $mech->create_problems_for_body(1, $body_ids{2651}, 'TITLE'); + my $url = $problem->tokenised_url($user, { foo => 'bar', baz => 'boo'}); + (my $token = $url) =~ s/\/M\///g; + + my $token_obj = FixMyStreet::App->model('DB::Token')->find( { + scope => 'email_sign_in', token => $token + } ); + + is_deeply $token_obj->data->{p}, { foo => 'bar', baz => 'boo'}, 'token has correct params'; +}; + +subtest 'get report time ago in appropriate format' => sub { + my ($problem) = $mech->create_problems_for_body(1, $body_ids{2651}, 'TITLE'); + + $problem->update( { + confirmed => DateTime->now->subtract( minutes => 2) + } ); + is $problem->time_ago, '2 minutes', 'problem returns time ago in minutes'; + + $problem->update( { + confirmed => DateTime->now->subtract( hours => 18) + } ); + is $problem->time_ago, '18 hours', 'problem returns time ago in hours'; + + $problem->update( { + confirmed => DateTime->now->subtract( days => 4) + } ); + is $problem->time_ago, '4 days', 'problem returns time ago in days'; + + $problem->update( { + confirmed => DateTime->now->subtract( weeks => 3 ) + } ); + is $problem->time_ago, '3 weeks', 'problem returns time ago in weeks'; + + $problem->update( { + confirmed => DateTime->now->subtract( months => 4 ) + } ); + is $problem->time_ago, '4 months', 'problem returns time ago in months'; + + $problem->update( { + confirmed => DateTime->now->subtract( years => 2 ) + } ); + is $problem->time_ago, '2 years', 'problem returns time ago in years'; +}; + +subtest 'time ago works with other dates' => sub { + my ($problem) = $mech->create_problems_for_body(1, $body_ids{2651}, 'TITLE'); + + $problem->update( { + lastupdate => DateTime->now->subtract( days => 4) + } ); + is $problem->time_ago('lastupdate'), '4 days', 'problem returns last updated time ago in days'; +}; + +subtest 'return how many days ago a problem was reported' => sub { + my ($problem) = $mech->create_problems_for_body(1, $body_ids{2651}, 'TITLE'); + $problem->update( { + confirmed => DateTime->now->subtract( weeks => 2 ) + } ); + is $problem->days_ago, 14, 'days_ago returns the amount of days'; + + $problem->update( { + lastupdate => DateTime->now->subtract( days => 4) + } ); + + is $problem->days_ago('lastupdate'), 4, 'days_ago allows other dates to be specified'; +}; + +END { + $problem->comments->delete if $problem; + $problem->delete if $problem; + $mech->delete_user( $user ) if $user; + + $mech->delete_body($_) for @bodies; + + done_testing(); +} diff --git a/t/app/model/questionnaire.t b/t/app/model/questionnaire.t index 60b52043a..945a64633 100644 --- a/t/app/model/questionnaire.t +++ b/t/app/model/questionnaire.t @@ -1,5 +1,3 @@ -#!/usr/bin/perl - use strict; use warnings; @@ -8,9 +6,9 @@ use Test::More; use FixMyStreet; use FixMyStreet::TestMech; -my $user = FixMyStreet::App->model('DB::User')->find_or_create( { email => 'test@example.com' } ); +my $user = FixMyStreet::DB->resultset('User')->find_or_create( { email => 'test@example.com' } ); -my $problem = FixMyStreet::App->model('DB::Problem')->create( +my $problem = FixMyStreet::DB->resultset('Problem')->create( { postcode => 'EH99 1SP', latitude => 1, @@ -25,15 +23,13 @@ my $problem = FixMyStreet::App->model('DB::Problem')->create( service => '', cobrand => 'default', cobrand_data => '', - confirmed => \"ms_current_timestamp() - '5 weeks'::interval", - whensent => \"ms_current_timestamp() - '5 weeks'::interval", + confirmed => \"current_timestamp - '5 weeks'::interval", + whensent => \"current_timestamp - '5 weeks'::interval", user => $user, anonymous => 0, } ); -diag $problem->id; - my $mech = FixMyStreet::TestMech->new; for my $test ( @@ -62,6 +58,10 @@ for my $test ( send_email => 1, }, { + state => 'action scheduled', + send_email => 1, + }, + { state => 'in progress', send_email => 1, }, @@ -78,6 +78,18 @@ for my $test ( send_email => 1, }, { + state => 'duplicate', + send_email => 1, + }, + { + state => 'unable to fix', + send_email => 1, + }, + { + state => 'not responsible', + send_email => 1, + }, + { state => 'closed', send_email => 1, }, @@ -92,7 +104,7 @@ for my $test ( $mech->email_count_is(0); - FixMyStreet::App->model('DB::Questionnaire') + FixMyStreet::DB->resultset('Questionnaire') ->send_questionnaires( { site => 'fixmystreet' } ); $mech->email_count_is( $test->{send_email} ); diff --git a/t/app/model/rabx_column.t b/t/app/model/rabx_column.t new file mode 100644 index 000000000..607d578ce --- /dev/null +++ b/t/app/model/rabx_column.t @@ -0,0 +1,23 @@ +use strict; +use warnings; + +use Test::More; + +use_ok "FixMyStreet::DB::RABXColumn"; + +# Test that the class names are correctly normalised +my @tests = ( + ["FixMyStreet::DB::Result::Token", "Token"], + ["FixMyStreet::App::Model::DB::Token", "Token"], +); + +foreach my $test (@tests) { + my ($input, $expected) = @$test; + is( + FixMyStreet::DB::RABXColumn::_get_class_identifier($input), + $expected, + "$input -> $expected" + ); +} + +done_testing(); diff --git a/t/app/model/token.t b/t/app/model/token.t index 12945975e..e31901187 100644 --- a/t/app/model/token.t +++ b/t/app/model/token.t @@ -1,23 +1,10 @@ -#!/usr/bin/perl - use strict; use warnings; -use Test::More tests => 45; +use Test::More; use FixMyStreet; -use FixMyStreet::App; -use mySociety::AuthToken; -use mySociety::DBHandle 'dbh'; - -# set things up so that code using mySociety::DBHandle is happy -FixMyStreet->configure_mysociety_dbhandle(); - -# NOTE - remember that you need to explicitly dbh()->commit after making -# database changes with the mySociety::* modules. - -# create a token using DBIC and check we can read it using AuthToken, and vice -# versa +use FixMyStreet::DB; my %tests = ( nested_hash => { foo => 'bar', and => [ 'baz', 'bundy' ] }, @@ -25,72 +12,71 @@ my %tests = ( scalar => 123, ); -my $token_rs = FixMyStreet::App->model('DB::Token'); +my $token_rs = FixMyStreet::DB->resultset('Token'); -# create using DBIC foreach my $test_data_name ( sort keys %tests ) { my $test_data = $tests{$test_data_name}; - pass "--- testing DBIC create using '$test_data_name'"; + pass "--- testing token creation using '$test_data_name'"; my $dbic_token = $token_rs->create( { scope => 'testing', data => $test_data } ); my $token = $dbic_token->token; ok $token, "stored token '$token'"; - is_deeply $dbic_token->data, $test_data, "data stored correctly using DBIC"; + is_deeply $dbic_token->data, $test_data, "data stored correctly"; - # read back using DBIC + # read back from database is_deeply $token_rs->find( { token => $token, scope => 'testing' } )->data, $test_data, - "data read back correctly with DBIC"; - - # read back using mySociety::AuthToken - is_deeply mySociety::AuthToken::retrieve( 'testing', $token ), - $test_data, "data read back correctly with m::AT"; + "data read back correctly"; # delete token ok $dbic_token->delete, "delete token"; is $token_rs->find( { token => $token, scope => 'testing' } ), undef, - "token gone for DBIC"; - - # read back using mySociety::AuthToken - is mySociety::AuthToken::retrieve( 'testing', $token ), - undef, "token gone with m::AT"; - + "token gone"; } -# create using m::AT -foreach my $test_data_name ( sort keys %tests ) { - my $test_data = $tests{$test_data_name}; - - pass "--- testing m::AT create using '$test_data_name'"; - - my $token = mySociety::AuthToken::store( 'testing', $test_data ); - dbh->commit(); - ok $token, "stored token '$token'"; - - # read back using DBIC - is_deeply $token_rs->find( { token => $token, scope => 'testing' } )->data, - $test_data, - "data read back correctly with DBIC"; - - # read back using mySociety::AuthToken - is_deeply mySociety::AuthToken::retrieve( 'testing', $token ), - $test_data, "data read back correctly with m::AT"; - - # delete token - ok mySociety::AuthToken::destroy( 'testing', $token ), "destroy token"; - dbh->commit(); - - is $token_rs->find( { token => $token, scope => 'testing' } ), - undef, - "token gone for DBIC"; - - # read back using mySociety::AuthToken - is mySociety::AuthToken::retrieve( 'testing', $token ), - undef, "token gone with m::AT"; +# Test that the inflation and deflation works as expected +{ + my $token = + $token_rs->create( { scope => 'testing', data => {} } ); + END { $token->delete() }; + + # Add in temporary check to test that the data is updated as expected. + is_deeply($token->data, {}, "data is empty"); + + # store something in it + $token->update({ data => { foo => 'bar' } }); + $token->discard_changes(); + is_deeply($token->data, { foo => 'bar' }, "data has content"); + + # change the hash stored + $token->update({ data => { baz => 'bundy' } }); + $token->discard_changes(); + is_deeply($token->data, { baz => 'bundy' }, "data has new content"); + + # change the hashref in place + { + my $data = $token->data; + $data->{baz} = 'new'; + $token->data( $data ); + $token->update(); + $token->discard_changes(); + is_deeply($token->data, { baz => 'new' }, "data has been updated"); + } + + # change the hashref in place + { + my $data = $token->data; + $data->{baz} = 'new'; + $token->update({ data => $data }); + $token->discard_changes(); + is_deeply($token->data, { baz => 'new' }, "data has been updated"); + } } + +done_testing(); diff --git a/t/app/model/user.t b/t/app/model/user.t new file mode 100644 index 000000000..d4115d586 --- /dev/null +++ b/t/app/model/user.t @@ -0,0 +1,61 @@ +use strict; +use warnings; + +use Test::More; + +use FixMyStreet::TestMech; +use FixMyStreet::DB; + +my $mech = FixMyStreet::TestMech->new(); +$mech->log_in_ok('test@example.com'); + +my ($problem) = $mech->create_problems_for_body(1, '2504', 'Title', { anonymous => 'f' }); +is $problem->user->latest_anonymity, 0, "User's last report was not anonymous"; + +FixMyStreet::override_config { + ALLOWED_COBRANDS => [ { fixmystreet => '.' } ], + MAPIT_URL => 'http://mapit.uk/', +}, sub { + $mech->get_ok('/around?pc=sw1a1aa'); + $mech->follow_link_ok( { text_regex => qr/skip this step/i, }, "follow 'skip this step' link" ); + $mech->content_like(qr/may_show_name[^>]*checked/); +}; + +($problem) = $mech->create_problems_for_body(1, '2504', 'Title', { anonymous => 't' }); +is $problem->user->latest_anonymity, 1, "User's last report was anonymous"; + +create_update($problem, anonymous => 'f'); +is $problem->user->latest_anonymity, 0, "User's last update was not anonyous"; + +create_update($problem, anonymous => 't'); +is $problem->user->latest_anonymity, 1, "User's last update was anonymous"; + +FixMyStreet::override_config { + ALLOWED_COBRANDS => [ { fixmystreet => '.' } ], + MAPIT_URL => 'http://mapit.uk/', +}, sub { + $mech->get_ok('/around?pc=sw1a1aa'); + $mech->follow_link_ok( { text_regex => qr/skip this step/i, }, "follow 'skip this step' link" ); + $mech->content_like(qr/may_show_name[^>c]*>/); +}; + +END { + $mech->delete_user( $problem->user ) if $problem; + done_testing(); +} + +sub create_update { + my ($problem, %params) = @_; + my $dt = DateTime->now()->add(days => 1); + return FixMyStreet::App->model('DB::Comment')->find_or_create({ + problem_id => $problem->id, + user_id => $problem->user_id, + name => 'Other User', + mark_fixed => 'false', + text => 'This is some update text', + state => 'confirmed', + anonymous => 'f', + created => $dt->ymd . ' ' . $dt->hms, + %params, + }); +} diff --git a/t/app/model/user_planned_report.t b/t/app/model/user_planned_report.t new file mode 100644 index 000000000..95a76615e --- /dev/null +++ b/t/app/model/user_planned_report.t @@ -0,0 +1,52 @@ +use strict; +use warnings; + +use Test::More; + +use FixMyStreet::TestMech; +use FixMyStreet::DB; + +my $mech = FixMyStreet::TestMech->new(); + +my @problems = $mech->create_problems_for_body(1, 2237, 'Title'); +my $problem = $problems[0]; +my $user = $problem->user; +my $user2 = $mech->create_user_ok('other@example.net'); + +is $user->active_planned_reports, 0; +is $user->planned_reports, 0; + +$user->add_to_planned_reports($problem); +is $user->active_planned_reports, 1; +is $user->planned_reports, 1; + +$user->add_to_planned_reports($problem); +is $user->active_planned_reports, 1; +is $user->planned_reports, 1; + +$user->remove_from_planned_reports($problem); +is $user->active_planned_reports, 0; +is $user->planned_reports, 1; + +$user->add_to_planned_reports($problem); +is $user->active_planned_reports, 1; +is $user->planned_reports, 2; + +$user2->add_to_planned_reports($problem); +is $user->active_planned_reports, 0; +is $user->planned_reports, 2; +is $user2->active_planned_reports, 1; +is $user2->planned_reports, 1; + +$user->add_to_planned_reports($problem); +is $user->active_planned_reports, 1; +is $user->planned_reports, 3; +is $user2->active_planned_reports, 0; +is $user2->planned_reports, 1; + +done_testing(); + +END { + $mech->delete_user($user); + $mech->delete_user($user2); +} diff --git a/t/app/script/archive_old_enquiries.t b/t/app/script/archive_old_enquiries.t new file mode 100644 index 000000000..e87d6a0f8 --- /dev/null +++ b/t/app/script/archive_old_enquiries.t @@ -0,0 +1,163 @@ +use strict; +use warnings; +use Test::More; +use FixMyStreet::TestMech; +use FixMyStreet::Script::ArchiveOldEnquiries; + +mySociety::Locale::gettext_domain( 'FixMyStreet' ); + +my $mech = FixMyStreet::TestMech->new(); + +$mech->clear_emails_ok; + +my $opts = { + commit => 1, +}; + +my $user = $mech->create_user_ok('test@example.com', name => 'Test User'); +my $oxfordshire = $mech->create_body_ok(2237, 'Oxfordshire County Council', id => 2237); +my $west_oxon = $mech->create_body_ok(2420, 'West Oxfordshire District Council', id => 2420); + +subtest 'sets reports to the correct status' => sub { + FixMyStreet::override_config { + ALLOWED_COBRANDS => [ 'oxfordshire' ], + }, sub { + my ($report) = $mech->create_problems_for_body(1, $oxfordshire->id, 'Test', { + areas => ',2237,', + user_id => $user->id, + }); + + my ($report1) = $mech->create_problems_for_body(1, $oxfordshire->id . "," .$west_oxon->id, 'Test', { + areas => ',2237,', + lastupdate => '2015-12-01 07:00:00', + user => $user, + }); + + my ($report2) = $mech->create_problems_for_body(1, $oxfordshire->id, 'Test 2', { + areas => ',2237,', + lastupdate => '2015-12-01 08:00:00', + user => $user, + state => 'investigating', + }); + + my ($report3, $report4) = $mech->create_problems_for_body(2, $oxfordshire->id, 'Test', { + areas => ',2237,', + lastupdate => '2014-12-01 07:00:00', + user => $user, + }); + + my ($report5) = $mech->create_problems_for_body(1, $oxfordshire->id . "," .$west_oxon->id, 'Test', { + areas => ',2237,', + lastupdate => '2014-12-01 07:00:00', + user => $user, + state => 'in progress' + }); + + FixMyStreet::Script::ArchiveOldEnquiries::archive($opts); + + $report->discard_changes; + $report1->discard_changes; + $report2->discard_changes; + $report3->discard_changes; + $report4->discard_changes; + $report5->discard_changes; + + is $report1->state, 'closed', 'Report 1 has been set to closed'; + is $report2->state, 'closed', 'Report 2 has been set to closed'; + is $report3->state, 'closed', 'Report 3 has been set to closed'; + is $report4->state, 'closed', 'Report 4 has been set to closed'; + is $report5->state, 'closed', 'Report 5 has been set to closed'; + + is $report->state, 'confirmed', 'Recent report has been left alone'; + }; +}; + +subtest 'sends emails to a user' => sub { + FixMyStreet::override_config { + ALLOWED_COBRANDS => [ 'oxfordshire' ], + }, sub { + $mech->clear_emails_ok; + $mech->email_count_is(0); + + $mech->create_problems_for_body(1, $oxfordshire->id, 'Shiny new report', { + areas => ',2237,', + user => $user, + }); + + $mech->create_problems_for_body(1, $oxfordshire->id, 'Problem the first', { + areas => ',2237,', + lastupdate => '2015-12-01 07:00:00', + user => $user, + }); + + $mech->create_problems_for_body(1, $oxfordshire->id, 'Problem the second', { + areas => ',2237,', + lastupdate => '2015-12-01 07:00:00', + user => $user, + }); + + $mech->create_problems_for_body(1, $oxfordshire->id, 'Problem the third', { + areas => ',2237,', + lastupdate => '2015-12-01 07:00:00', + user => $user, + }); + + $mech->create_problems_for_body(1, $oxfordshire->id, 'Really old report', { + areas => ',2237,', + lastupdate => '2014-12-01 07:00:00', + user => $user, + }); + + FixMyStreet::Script::ArchiveOldEnquiries::archive($opts); + + my @emails = $mech->get_email; + $mech->email_count_is(1); + + my $email = $emails[0]; + my $body = $mech->get_text_body_from_email($email); + + like $body, qr/Problem the first/, 'Email body matches report name'; + like $body, qr/Problem the second/, 'Email body matches report name'; + like $body, qr/Problem the third/, 'Email body matches report name'; + + unlike $body, qr/Shiny new report/, 'Email body does not have new report'; + unlike $body, qr/Really old report/, 'Email body does not have old report'; + }; +}; + +subtest 'user with old reports does not get email' => sub { + $mech->clear_emails_ok; + $mech->email_count_is(0); + + $mech->create_problems_for_body(4, $oxfordshire->id, 'Really old report', { + areas => ',2237,', + lastupdate => '2014-12-01 07:00:00', + user => $user, + }); + + FixMyStreet::Script::ArchiveOldEnquiries::archive($opts); + + my @emails = $mech->get_email; + $mech->email_count_is(0); +}; + +subtest 'user with new reports does not get email' => sub { + $mech->clear_emails_ok; + $mech->email_count_is(0); + + $mech->create_problems_for_body(4, $oxfordshire->id, 'Shiny new report', { + areas => ',2237,', + user => $user, + }); + + FixMyStreet::Script::ArchiveOldEnquiries::archive($opts); + + $mech->email_count_is(0); +}; + +done_testing(); + +END { + $mech->delete_user($user); + $mech->delete_body($oxfordshire); +} diff --git a/t/app/sendreport/angus.t b/t/app/sendreport/angus.t new file mode 100644 index 000000000..a19ee483a --- /dev/null +++ b/t/app/sendreport/angus.t @@ -0,0 +1,61 @@ +use strict; +use warnings; + +use FixMyStreet::DB; + +use Test::More; + +use_ok("FixMyStreet::SendReport::Angus"); + +my $u = FixMyStreet::DB->resultset('User')->new( { email => 'test@example.org', name => 'A User' } ); + +my $p = FixMyStreet::DB->resultset('Problem')->new( { + latitude => 1, + longitude => 1, + title => 'title', + detail => 'detail', + user => $u, + id => 1, + name => 'A User', + cobrand => 'fixmystreet', +} ); + +my $angus = FixMyStreet::SendReport::Angus->new(); + +subtest 'parses authentication token correctly' => sub { + my $authxml = <<EOT; + <AuthenticateResponse> + + <AuthenticateResult> + TVRreUxqRTJPQzR5TlRVdU1qSjhNakF4Tmpvd01Ub3lNam94TlRvME16b3pPUT09VGhvdVNoYWx0Tm90UGFzcw== + </AuthenticateResult> + <success> + True + </success> + <message></message> + + </AuthenticateResponse> +EOT +; + is $angus->get_auth_token($authxml), 'TVRreUxqRTJPQzR5TlRVdU1qSjhNakF4Tmpvd01Ub3lNam94TlRvME16b3pPUT09VGhvdVNoYWx0Tm90UGFzcw==', 'token correct'; +}; + +subtest 'parses report external id correctly' => sub { + my $respxml = <<EOT; +<CreateRequestResponse> + + <CreateRequestResult> + <RequestId>7245</RequestId> + </CreateRequestResult> + + <success>True</success> + <message></message> + +</CreateRequestResponse> +EOT +; + is $angus->get_external_id($respxml), '7245', 'external id correct'; +}; + + +done_testing(); diff --git a/t/app/sendreport/email.t b/t/app/sendreport/email.t index 04b3854cc..471145dcb 100644 --- a/t/app/sendreport/email.t +++ b/t/app/sendreport/email.t @@ -1,33 +1,34 @@ -#!/usr/bin/perl - use strict; use warnings; use Test::More; use FixMyStreet; -use FixMyStreet::App; -use FixMyStreet::DB::Result::Contact; +use FixMyStreet::DB; use FixMyStreet::SendReport::Email; use FixMyStreet::TestMech; use mySociety::Locale; +ok( my $mech = FixMyStreet::TestMech->new, 'Created mech object' ); + my $e = FixMyStreet::SendReport::Email->new(); -my $contact = FixMyStreet::App->model('DB::Contact')->find_or_create( +# area id 1000 +my $params = { id => 1000, name => 'Council of the Thousand' }; +my $body = FixMyStreet::DB->resultset('Body')->find_or_create($params); +ok $body, "found/created body"; + +my $contact = $mech->create_contact_ok( email => 'council@example.com', body_id => 1000, category => 'category', - confirmed => 1, - deleted => 0, - editor => 'test suite', - whenedited => DateTime->now, note => '', ); -my $row = FixMyStreet::App->model('DB::Problem')->new( { - council => '1000', +my $row = FixMyStreet::DB->resultset('Problem')->new( { + bodies_str => '1000', category => 'category', + cobrand => '', } ); ok $e; @@ -44,14 +45,14 @@ foreach my $test ( { }, { desc => 'unconfirmed contact results in no receipients', - count => undef, + count => 0, add_council => 1, unconfirmed => 1, - expected_note => 'Council 1000 deleted', + expected_note => 'Body 1000 deleted', }, { desc => 'unconfirmed contact note uses note from contact table', - count => undef, + count => 0, add_council => 1, unconfirmed => 1, note => 'received bounced so unconfirmed', @@ -62,7 +63,7 @@ foreach my $test ( { my $e = FixMyStreet::SendReport::Email->new; $contact->update( { confirmed => 0 } ) if $test->{unconfirmed}; $contact->update( { note => $test->{note} } ) if $test->{note}; - $e->add_council( 1000, { name => 'test council' } ) if $test->{add_council}; + $e->add_body( $body ) if $test->{add_council}; is $e->build_recipient_list( $row, {} ), $test->{count}, 'correct recipient list count'; if ( $test->{unconfirmed} ) { @@ -72,6 +73,8 @@ foreach my $test ( { }; } -$contact->delete; - done_testing(); + +END { + $mech->delete_body($body); +} diff --git a/t/app/sendreport/inspection_required.t b/t/app/sendreport/inspection_required.t new file mode 100644 index 000000000..f9d40d39f --- /dev/null +++ b/t/app/sendreport/inspection_required.t @@ -0,0 +1,111 @@ +use strict; +use warnings; + +use Test::More; + +use FixMyStreet; +use FixMyStreet::DB; +use FixMyStreet::TestMech; +use FixMyStreet::SendReport::Email; +use mySociety::Locale; + +ok( my $mech = FixMyStreet::TestMech->new, 'Created mech object' ); + +my $user = $mech->create_user_ok( 'user@example.com' ); + +my $body = $mech->create_body_ok( 2237, 'Oxfordshire County Council', id => 2237 ); +# $body->update({ send_method => 'Email' }); + +my $contact = $mech->create_contact_ok( + body_id => $body->id, + category => 'Pothole', + email => 'test@example.org', +); +$contact->set_extra_metadata(inspection_required => 1); +$contact->update; + +my @reports = $mech->create_problems_for_body( 1, $body->id, 'Test', { + cobrand => 'oxfordshire', + category => $contact->category, + user => $user, +}); +my $report = $reports[0]; + +subtest 'Report isn’t sent if uninspected' => sub { + $mech->clear_emails_ok; + + FixMyStreet::DB->resultset('Problem')->send_reports(); + + $mech->email_count_is( 0 ); + is $report->whensent, undef, 'Report hasn’t been sent'; +}; + +subtest 'Report is sent when inspected' => sub { + $mech->clear_emails_ok; + $report->set_extra_metadata(inspected => 1); + $report->update; + + FixMyStreet::DB->resultset('Problem')->send_reports(); + + $report->discard_changes; + $mech->email_count_is( 1 ); + ok $report->whensent, 'Report marked as sent'; +}; + +subtest 'Uninspected report is sent when made by trusted user' => sub { + $mech->clear_emails_ok; + $report->unset_extra_metadata('inspected'); + $report->whensent( undef ); + $report->update; + + $user->user_body_permissions->find_or_create({ + body => $body, + permission_type => 'trusted', + }); + ok $user->has_permission_to('trusted', $report->bodies_str_ids), 'User can make trusted reports'; + + FixMyStreet::DB->resultset('Problem')->send_reports(); + + $report->discard_changes; + $mech->email_count_is( 1 ); + ok $report->whensent, 'Report marked as sent'; + is $report->get_extra_metadata('inspected'), undef, 'Report not marked as inspected'; +}; + +subtest 'Uninspected report isn’t sent when user rep is too low' => sub { + $mech->clear_emails_ok; + $report->whensent( undef ); + $report->update; + + $user->user_body_permissions->delete; + $user->set_extra_metadata(reputation => 15); + $user->update; + + $contact->set_extra_metadata(reputation_threshold => 20); + $contact->update; + + FixMyStreet::DB->resultset('Problem')->send_reports(); + + $report->discard_changes; + $mech->email_count_is( 0 ); + is $report->whensent, undef, 'Report hasn’t been sent'; +}; + +subtest 'Uninspected report is sent when user rep is high enough' => sub { + $user->set_extra_metadata(reputation => 21); + $user->update; + + FixMyStreet::DB->resultset('Problem')->send_reports(); + + $report->discard_changes; + $mech->email_count_is( 1 ); + ok $report->whensent, 'Report marked as sent'; + is $report->get_extra_metadata('inspected'), undef, 'Report not marked as inspected'; +}; + +done_testing(); + +END { + $mech->delete_user($user); + $mech->delete_body($body); +} diff --git a/t/app/sendreport/open311.t b/t/app/sendreport/open311.t new file mode 100644 index 000000000..c4c17577c --- /dev/null +++ b/t/app/sendreport/open311.t @@ -0,0 +1,267 @@ +use strict; +use warnings; + +use Test::More; +use Test::Deep; + +use Open311; +use FixMyStreet::SendReport::Open311; +use FixMyStreet::DB; + +use Data::Dumper; + +package main; +sub test_overrides; # defined below + +use constant TEST_USER_EMAIL => 'fred@example.com'; + +my %standard_open311_parameters = ( + 'use_extended_updates' => 0, + 'send_notpinpointed' => 0, + 'extended_description' => 1, + 'use_service_as_deviceid' => 0, + 'extended_statuses' => 0, + 'always_send_latlong' => 1, + 'debug' => 0, + 'error' => '', + 'endpoints' => { + 'requests' => 'requests.xml', + 'service_request_updates' => 'servicerequestupdates.xml', + 'services' => 'services.xml', + 'update' => 'servicerequestupdates.xml', + }, +); + +test_overrides oxfordshire => + { + body_name => 'Oxfordshire', + body_id => 2237, + row_data => { + postcode => 'OX1 1AA', + }, + extra => { + northing => 100, + easting => 100, + closest_address => '49 St Giles', + }, + }, + superhashof({ + handler => isa('FixMyStreet::Cobrand::Oxfordshire'), + discard_changes => 1, + 'open311' => noclass(superhashof({ + %standard_open311_parameters, + 'extended_description' => 'oxfordshire', + 'endpoints' => { + 'requests' => 'open311_service_request.cgi' + }, + })), + problem_extra => bag( + { name => 'northing', value => 100 }, + { name => 'easting', value => 100 }, + { name => 'closest_address' => value => '49 St Giles' }, + { name => 'external_id', value => re('[0-9]+') }, + ), + }); + +my $bromley_check = + superhashof({ + handler => isa('FixMyStreet::Cobrand::Bromley'), + discard_changes => 1, + 'open311' => noclass(superhashof({ + %standard_open311_parameters, + 'send_notpinpointed' => 1, + 'extended_description' => 0, + 'use_service_as_deviceid' => 0, + 'always_send_latlong' => 0, + })), + problem_extra => bag( + { name => 'report_url' => value => 'http://example.com/1234' }, + { name => 'report_title', value => 'Problem' }, + { name => 'public_anonymity_required', value => 'TRUE' }, + { name => 'email_alerts_requested', value => 'FALSE' }, + { name => 'requested_datetime', value => re(qr/^(\d{4})-(\d\d)-(\d\d)T(\d\d):(\d\d):(\d\d)/) }, + { name => 'email', value => TEST_USER_EMAIL }, + { name => 'last_name', value => 'Bloggs' }, + ), + }); + +test_overrides bromley => + { + body_name => 'Bromley', + body_id => 2482, + row_data => { + postcode => 'BR1 1AA', + extra => [ { name => 'last_name', value => 'Bloggs' } ], + }, + extra => { + northing => 100, + easting => 100, + url => 'http://example.com/1234', + }, + }, + $bromley_check; + +test_overrides fixmystreet => + { + body_name => 'Bromley', + body_id => 2482, + row_data => { + postcode => 'BR1 1AA', + # NB: we don't pass last_name here, as main cobrand doesn't know to do this! + }, + extra => { + northing => 100, + easting => 100, + url => 'http://example.com/1234', + }, + }, + $bromley_check; + +test_overrides greenwich => + { + body_name => 'Greenwich', + body_id => 2493, + }, + superhashof({ + handler => isa('FixMyStreet::Cobrand::Greenwich'), + 'open311' => noclass(superhashof({ + %standard_open311_parameters, + })), + problem_extra => bag( + { name => 'external_id', value => re('[0-9]+') }, + ), + }); + +test_overrides fixmystreet => + { + body_name => 'West Berkshire', + body_id => 2619, + row_data => { + postcode => 'RG1 1AA', + }, + }, + superhashof({ + handler => isa('FixMyStreet::Cobrand::WestBerkshire'), + 'open311' => noclass(superhashof({ + %standard_open311_parameters, + 'endpoints' => { + 'requests' => 'Requests', + 'services' => 'Services', + }, + })), + }); + +sub test_overrides { + # NB: Open311 and ::SendReport::Open311 are mocked below in BEGIN { ... } + my ($cobrand, $input, $expected_data) = @_; + subtest "$cobrand ($input->{body_name}) overrides" => sub { + + FixMyStreet::override_config { + ALLOWED_COBRANDS => ['fixmystreet', 'oxfordshire', 'bromley', 'westberkshire', 'greenwich'], + }, sub { + my $db = FixMyStreet::DB->storage->schema; + $db->txn_begin; + + my $params = { id => $input->{body_id}, name => $input->{body_name} }; + my $body = $db->resultset('Body')->find_or_create($params); + $body->body_areas->create({ area_id => $input->{body_id} }); + ok $body, "found/created body " . $input->{body_name}; + $body->update({ can_be_devolved => 1 }); + + my $contact = $body->contacts->find_or_create( + confirmed => 1, + email => 'ZZ', + category => 'ZZ', + deleted => 0, + editor => 'test suite', + note => '', + whenedited => DateTime->now, + jurisdiction => '1234', + api_key => 'SEEKRIT', + body_id => $input->{body_id}, + ); + $contact->update({ send_method => 'Open311', endpoint => 'http://example.com/open311' }); + + my $user = $db->resultset('User')->create( { + name => 'Fred Bloggs', + email => TEST_USER_EMAIL, + password => 'dummy', + }); + + my $row = $db->resultset('Problem')->create( { + title => 'Problem', + detail => 'A big problem', + used_map => 1, + name => 'Fred Bloggs', + anonymous => 1, + state => 'unconfirmed', + bodies_str => $input->{body_id}, + areas => (sprintf ',%d,', $input->{body_id}), + category => 'ZZ', + cobrand => $cobrand, + user => $user, + postcode => 'ZZ1 1AA', + latitude => 100, + longitude => 100, + confirmed => DateTime->now(), + %{ $input->{row_data} || {} }, + } ); + + my $sr = FixMyStreet::SendReport::Open311->new; + $sr->add_body($body, $contact); + $sr->send( $row, $input->{extra} || {} ); + + cmp_deeply (Open311->_get_test_data, $expected_data, 'Data as expected') + or diag Dumper( Open311->_get_test_data ); + + Open311->_reset_test_data(); + $db->txn_rollback; + }; + } +} + +BEGIN { + # Prepare the %data variable to write data from Open311 calls to... + my %data; + package Open311; + use Class::Method::Modifiers; + around new => sub { + my $orig = shift; + my ($class, %params) = @_; + my $self = $class->$orig(%params); + $data{open311} = $self; + $self; + }; + around send_service_request => sub { + my $orig = shift; + my ($self, $problem, $extra, $service_code) = @_; + $data{problem} = { $problem->get_columns }; + $data{extra} = $extra; + $data{problem_extra} = $problem->get_extra_fields; + $data{problem_user} = { $problem->user->get_columns }; + $data{service_code} = $service_code; + # don't actually send the service request! + }; + + sub _get_test_data { return +{ %data } } + sub _reset_test_data { %data = () } + + package FixMyStreet::DB::Result::Problem; + use Class::Method::Modifiers; # is marked as immutable by Moose + sub discard_changes { + $data{discard_changes}++; + # no need to actually discard, as we're in transaction anyway + }; + + package FixMyStreet::DB::Result::Body; + use Class::Method::Modifiers; # is marked as immutable by Moose + around get_cobrand_handler => sub { + my $orig = shift; + my ($self) = @_; + my $handler = $self->$orig(); + $data{handler} = $handler; + $handler; + }; +} + +done_testing(); diff --git a/t/app/uri_for.t b/t/app/uri_for.t index eecf30e32..9cbcd3767 100644 --- a/t/app/uri_for.t +++ b/t/app/uri_for.t @@ -4,7 +4,7 @@ use warnings; use Test::More; # FIXME Should this be here? A better way? uri_for varies by map. -use Test::WWW::Mechanize::Catalyst 'FixMyStreet::App'; +use Catalyst::Test 'FixMyStreet::App'; FixMyStreet::Map::set_map_class(); # structure of these tests borrowed from '/t/aggregate/unit_core_uri_for.t' @@ -15,43 +15,8 @@ use URI; use_ok('FixMyStreet::App'); -my $fms_c = FixMyStreet::App->new( - { - request => Catalyst::Request->new( - { - base => URI->new('http://www.fixmystreet.com/'), - uri => URI->new('http://www.fixmystreet.com/test_namespace') - } - ), - namespace => 'test_namespace', - } -); - -my $fgm_c = FixMyStreet::App->new( - { - request => Catalyst::Request->new( - { - base => URI->new('http://www.fiksgatami.no/'), - uri => URI->new('http://www.fiksgatami.no/test_namespace') - } - ), - namespace => 'test_namespace', - } -); - -my $reh_en_c = FixMyStreet::App->new( - { - request => Catalyst::Request->new( - { - base => URI->new('http://reportemptyhomes.com/'), - uri => URI->new('http://reportemptyhomes.com/test_namespace') - } - ), - namespace => 'test_namespace', - } -); -$reh_en_c->setup_request(); - +my $fms_c = ctx_request('http://www.fixmystreet.com/'); +my $fgm_c = ctx_request('http://www.fiksgatami.no/'); is( $fms_c->uri_for('/bar/baz') . "", @@ -61,7 +26,7 @@ is( is( $fms_c->uri_for('') . "", - 'http://www.fixmystreet.com/test_namespace', + 'http://www.fixmystreet.com/', 'URI for namespace' ); @@ -78,36 +43,4 @@ is( 'FiksGataMi url with lat not zoom' ); -SKIP: { - skip( "Need 'emptyhomes' in ALLOWED_COBRANDS config", 2 ) - unless FixMyStreet::Cobrand->exists('emptyhomes'); - - like( - $reh_en_c->uri_for_email( '/foo' ), - qr{^http://en.}, - 'adds en to retain language' - ); - - # instantiate this here otherwise sets locale to cy and breaks test - # above - my $reh_cy_c = FixMyStreet::App->new( - { - request => Catalyst::Request->new( - { - base => URI->new('http://cy.reportemptyhomes.com/'), - uri => URI->new('http://cy.reportemptyhomes.com/test_namespace') - } - ), - namespace => 'test_namespace', - } - ); - $reh_cy_c->setup_request(); - - like( - $reh_cy_c->uri_for_email( '/foo' ), - qr{^http://cy.}, - 'retains language' - ); -} - done_testing(); diff --git a/t/cobrand/bromley.t b/t/cobrand/bromley.t new file mode 100644 index 000000000..a7cc563dc --- /dev/null +++ b/t/cobrand/bromley.t @@ -0,0 +1,138 @@ +use strict; +use warnings; +use Test::More; + +use CGI::Simple; +use FixMyStreet::TestMech; +my $mech = FixMyStreet::TestMech->new; + +# Create test data +my $user = $mech->create_user_ok( 'bromley@example.com' ); +my $body = $mech->create_body_ok( 2482, 'Bromley Council', id => 2482 ); +my $contact = $mech->create_contact_ok( + body_id => $body->id, + category => 'Other', + email => 'LIGHT', +); +$contact->set_extra_metadata(id_field => 'service_request_id_ext'); +$contact->set_extra_fields( + { code => 'easting', datatype => 'number', }, + { code => 'northing', datatype => 'number', }, + { code => 'service_request_id_ext', datatype => 'number', }, +); +$contact->update; + +my @reports = $mech->create_problems_for_body( 1, $body->id, 'Test', { + cobrand => 'bromley', + user => $user, +}); +my $report = $reports[0]; + +for my $update ('in progress', 'unable to fix') { + FixMyStreet::DB->resultset('Comment')->find_or_create( { + problem_state => $update, + problem_id => $report->id, + user_id => $user->id, + name => 'User', + mark_fixed => 'f', + text => "This update marks it as $update", + state => 'confirmed', + confirmed => 'now()', + anonymous => 'f', + } ); +} + +# Test Bromley special casing of 'unable to fix' +$mech->get_ok( '/report/' . $report->id ); +$mech->content_contains( 'marks it as in progress' ); +$mech->content_contains( 'marked as in progress' ); +$mech->content_contains( 'marks it as unable to fix' ); +$mech->content_contains( 'marked as no further action' ); + +subtest 'testing special Open311 behaviour', sub { + $report->set_extra_fields(); + $report->update; + $body->update( { send_method => 'Open311', endpoint => 'http://bromley.endpoint.example.com', jurisdiction => 'FMS', api_key => 'test' } ); + my $test_data; + FixMyStreet::override_config { + STAGING_FLAGS => { send_reports => 1 }, + ALLOWED_COBRANDS => [ 'fixmystreet', 'bromley' ], + }, sub { + $test_data = FixMyStreet::DB->resultset('Problem')->send_reports(); + }; + $report->discard_changes; + ok $report->whensent, 'Report marked as sent'; + is $report->send_method_used, 'Open311', 'Report sent via Open311'; + is $report->external_id, 248, 'Report has right external ID'; + + my $req = $test_data->{test_req_used}; + my $c = CGI::Simple->new($req->content); + is $c->param('attribute[easting]'), 529025, 'Request had easting'; + is $c->param('attribute[northing]'), 179716, 'Request had northing'; + is $c->param('attribute[service_request_id_ext]'), $report->id, 'Request had correct ID'; + is $c->param('jurisdiction_id'), 'FMS', 'Request had correct jurisdiction'; +}; + +for my $test ( + { + cobrand => 'bromley', + fields => { + submit_update => 1, + rznvy => 'unregistered@example.com', + update => 'Update from an unregistered user', + add_alert => undef, + first_name => 'Unreg', + last_name => 'User', + fms_extra_title => 'DR', + may_show_name => undef, + } + }, + { + cobrand => 'fixmystreet', + fields => { + submit_update => 1, + rznvy => 'unregistered@example.com', + update => 'Update from an unregistered user', + add_alert => undef, + name => 'Unreg User', + fms_extra_title => 'DR', + may_show_name => undef, + } + }, +) +{ + subtest 'check Bromley update emails via ' . $test->{cobrand} . ' cobrand are correct' => sub { + $mech->log_out_ok(); + $mech->clear_emails_ok(); + + my $report_id = $report->id; + + FixMyStreet::override_config { + ALLOWED_COBRANDS => [ $test->{cobrand} ], + }, sub { + $mech->get_ok("/report/$report_id"); + $mech->submit_form_ok( + { + with_fields => $test->{fields} + }, + 'submit update' + ); + }; + $mech->content_contains('Nearly done! Now check your email'); + + my $body = $mech->get_text_body_from_email; + like $body, qr/This update will be sent to Bromley Council/i, "Email indicates problem will be sent to Bromley"; + unlike $body, qr/Note that we do not send updates to/i, "Email does not say updates aren't sent to Bromley"; + + my $unreg_user = FixMyStreet::App->model( 'DB::User' )->find( { email => 'unregistered@example.com' } ); + + ok $unreg_user, 'found user'; + + $mech->delete_user( $unreg_user ); + }; +} + +# Clean up +$mech->delete_user($user); +$mech->delete_body($body); +done_testing(); diff --git a/t/cobrand/closest.t b/t/cobrand/closest.t index 464c95e67..43b36f608 100644 --- a/t/cobrand/closest.t +++ b/t/cobrand/closest.t @@ -4,7 +4,10 @@ use warnings; use Test::More; use mySociety::Locale; -use FixMyStreet::App; +use FixMyStreet::DB; +use FixMyStreet::TestMech; + +my $mech = FixMyStreet::TestMech->new; use_ok 'FixMyStreet::Cobrand'; @@ -13,7 +16,7 @@ mySociety::Locale::gettext_domain( 'FixMyStreet' ); my $c = FixMyStreet::Cobrand::UK->new(); my $user = - FixMyStreet::App->model('DB::User') + FixMyStreet::DB->resultset('User') ->find_or_create( { email => 'test@example.com', name => 'Test User' } ); ok $user, "created test user"; @@ -26,10 +29,10 @@ my $dt = DateTime->new( second => 23 ); -my $report = FixMyStreet::App->model('DB::Problem')->find_or_create( +my $report = FixMyStreet::DB->resultset('Problem')->find_or_create( { postcode => 'SW1A 1AA', - council => '2504', + bodies_str => '2504', areas => ',105255,11806,11828,2247,2504,', category => 'Other', title => 'Test 2', @@ -81,4 +84,5 @@ SKIP: { } # all done +$mech->delete_user( $user ); done_testing(); diff --git a/t/cobrand/councils.t b/t/cobrand/councils.t index 8fb10cfbe..0e8b71f04 100644 --- a/t/cobrand/councils.t +++ b/t/cobrand/councils.t @@ -5,14 +5,14 @@ use Test::More; use FixMyStreet::TestMech; my $mech = FixMyStreet::TestMech->new; -foreach my $council (qw/southampton reading bromley/) { - SKIP: { - skip( "Need '$council' in ALLOWED_COBRANDS config", 3 ) - unless FixMyStreet::Cobrand->exists($council); +foreach my $council (qw/oxfordshire bromley/) { + FixMyStreet::override_config { + ALLOWED_COBRANDS => [ $council ], + }, sub { ok $mech->host("$council.fixmystreet.com"), "change host to $council"; $mech->get_ok('/'); - $mech->content_like( qr/$council/i ); - } + $mech->content_like( qr/\u$council/ ); + }; } done_testing(); diff --git a/t/cobrand/fixamingata.t b/t/cobrand/fixamingata.t new file mode 100644 index 000000000..d6a1c2b34 --- /dev/null +++ b/t/cobrand/fixamingata.t @@ -0,0 +1,121 @@ +use strict; +use warnings; +use Test::More; +use Test::MockModule; + +BEGIN { + use FixMyStreet; + FixMyStreet->test_mode(1); +} + +use mySociety::Locale; + +use FixMyStreet::TestMech; +my $mech = FixMyStreet::TestMech->new; + +# Closest road reverse geocode mock +my $resolver = Test::MockModule->new('LWP::Simple'); +$resolver->mock('get', sub($) { "<result></result>" }); + +# Front page test + +ok $mech->host("www.fixamingata.se"), "change host to FixaMinGata"; +FixMyStreet::override_config { + ALLOWED_COBRANDS => [ 'fixamingata' ], +}, sub { + $mech->get_ok('/'); +}; +$mech->content_like( qr/FixaMinGata/ ); + +my $body = $mech->create_body_ok( 1, 'Body' ); +$mech->create_contact_ok( + body => $body, + category => "Other", + email => "other\@example.org", +); + +my @reports = $mech->create_problems_for_body( 1, $body->id, 'Test', { + cobrand => 'fixamingata', + latitude => '55.605833', + longitude => '13.035833', +}); +my $report = $reports[0]; +$mech->get_ok( '/report/' . $report->id ); + +$mech->email_count_is(0); +FixMyStreet::override_config { + ALLOWED_COBRANDS => [ 'fixamingata' ], +}, sub { + FixMyStreet::DB->resultset('Problem')->send_reports(); +}; +my $email = $mech->get_email; +my $plain = $mech->get_text_body_from_email($email, 1); +like $plain->header('Content-Type'), qr/utf-8/, 'encoding looks okay'; +like $email->header('Subject'), qr/Ny rapport: Test Test/, 'subject looks okay'; +like $email->header('To'), qr/other\@example.org/, 'to line looks correct'; +like $plain->body_str, qr/V\xe4nligen,/, 'signature looks correct'; +$mech->clear_emails_ok; + +my $user = + FixMyStreet::DB->resultset('User') + ->find_or_create( { email => 'test@example.com', name => 'Test User' } ); +ok $user, "created test user"; + +my $user2 = + FixMyStreet::DB->resultset('User') + ->find_or_create( { email => 'commenter@example.com', name => 'Commenter' } ); +ok $user2, "created comment user"; + +my $comment = FixMyStreet::DB->resultset('Comment')->find_or_create({ + problem_id => $report->id, + user_id => $user2->id, + name => 'Other User', + mark_fixed => 'false', + text => 'This is some update text', + state => 'confirmed', + anonymous => 'f', +}); +$comment->confirmed( \"current_timestamp - '3 days'::interval" ); +$comment->update; + +my $alert = FixMyStreet::DB->resultset('Alert')->find_or_create({ + user => $user, + parameter => $report->id, + alert_type => 'new_updates', + whensubscribed => '2014-01-01 10:00:00', + confirmed => 1, + cobrand => 'fixamingata', +}); + +FixMyStreet::override_config { + ALLOWED_COBRANDS => [ 'fixamingata' ], +}, sub { + FixMyStreet::DB->resultset('AlertType')->email_alerts(); +}; + +$email = $mech->get_email; +$plain = $mech->get_text_body_from_email($email, 1); +like $plain->header('Content-Type'), qr/utf-8/, 'encoding looks okay'; +like $plain->body_str, qr/V\xe4nligen,/, 'signature looks correct'; +$mech->clear_emails_ok; + +subtest "Test ajax decimal points" => sub { + # The following line is so we are definitely not in Swedish before + # requesting the page, so that the code performs a full switch to Swedish + mySociety::Locale::push('en-gb'); + + FixMyStreet::override_config { + ALLOWED_COBRANDS => [ 'fixamingata' ], + MAPIT_URL => 'http://mapit.uk/' + }, sub { + $mech->get_ok('/ajax/lookup_location?term=12345'); + # We want an actual decimal point in a JSON response... + $mech->content_contains('51.5'); + }; +}; + +END { + $mech->delete_body($body); + ok $mech->host("www.fixmystreet.com"), "change host back"; + done_testing(); +} diff --git a/t/cobrand/form_extras.t b/t/cobrand/form_extras.t new file mode 100644 index 000000000..22a86ef21 --- /dev/null +++ b/t/cobrand/form_extras.t @@ -0,0 +1,72 @@ +use strict; +use warnings; + +package FixMyStreet::Cobrand::Tester; +use parent 'FixMyStreet::Cobrand::FixMyStreet'; + +sub report_form_extras { + ( { name => 'address', required => 1 }, { name => 'passport', required => 0 } ) +} + +# To allow a testing template override +sub path_to_web_templates { + my $self = shift; + return [ + FixMyStreet->path_to( 't/cobrand/form_extras/templates' )->stringify, + ]; +} + +package main; + +use Test::More; +use FixMyStreet::TestMech; + +# disable info logs for this test run +FixMyStreet::App->log->disable('info'); +END { FixMyStreet::App->log->enable('info'); } + +my $mech = FixMyStreet::TestMech->new; + +FixMyStreet::override_config { + ALLOWED_COBRANDS => [ { tester => '.' } ], + MAPIT_URL => 'http://mapit.uk/', +}, sub { + $mech->get_ok('/around'); + $mech->submit_form_ok( { with_fields => { pc => 'EH1 1BB', } }, "submit location" ); + $mech->follow_link_ok( { text_regex => qr/skip this step/i, }, "follow 'skip this step' link" ); + $mech->submit_form_ok( { + button => 'submit_register', + with_fields => { + title => 'Test Report', + detail => 'Test report details.', + name => 'Joe Bloggs', + may_show_name => '1', + email => 'test-1@example.com', + passport => '123456', + password_register => '', + } + }, + "submit details without address, with passport", + ); + $mech->content_like(qr{<label for="form_address">Address</label>\s*<p class='form-error'>This information is required</p>}, 'Address is required'); + $mech->content_contains('value="123456" name="passport"', 'Passport number reshown'); + + $mech->submit_form_ok( { + button => 'submit_register', + with_fields => { + address => 'My address', + } + }, + "submit details, now with address", + ); + $mech->content_contains('Now check your email'); + + my $problem = FixMyStreet::DB->resultset('Problem')->search({}, { order_by => '-id' })->first; + is $problem->get_extra_metadata('address'), 'My address', 'Address is stored'; + is $problem->get_extra_metadata('passport'), '123456', 'Passport number is stored'; +}; + +END { + $mech->delete_problems_for_body(undef); + done_testing(); +} diff --git a/t/cobrand/form_extras/templates/report/new/after_photo.html b/t/cobrand/form_extras/templates/report/new/after_photo.html new file mode 100644 index 000000000..89ab0b20c --- /dev/null +++ b/t/cobrand/form_extras/templates/report/new/after_photo.html @@ -0,0 +1,12 @@ +<label for="form_address">Address</label> +[% IF field_errors.address %] +<p class='form-error'>[% field_errors.address %]</p> +[% END %] +<input class="form-control" type="text" value="[% report.get_extra_metadata('address') | html %]" name="address" id="form_address" required> + +<label for="form_passport">Passport number (optional)</label> +[% IF field_errors.passport %] +<p class='form-error'>[% field_errors.passport %]</p> +[% END %] +<input class="form-control" type="text" value="[% report.get_extra_metadata('passport') | html %]" name="passport" id="form_passport"> + diff --git a/t/cobrand/get_body_sender.t b/t/cobrand/get_body_sender.t new file mode 100644 index 000000000..fbdfbffa7 --- /dev/null +++ b/t/cobrand/get_body_sender.t @@ -0,0 +1,42 @@ +use strict; +use warnings; + +use Test::More; + +use mySociety::Locale; +use FixMyStreet::DB; + +use_ok 'FixMyStreet::Cobrand'; + +mySociety::Locale::gettext_domain( 'FixMyStreet' ); + +my $c = FixMyStreet::Cobrand::FixMyStreet->new(); + +FixMyStreet::DB->resultset('BodyArea')->search( { body_id => 1000 } )->delete; +FixMyStreet::DB->resultset('Body')->search( { name => 'Body of a Thousand' } )->delete; + +my $body = FixMyStreet::DB->resultset('Body')->find_or_create({ + id => 1000, + name => 'Body of a Thousand', +}); +my $body_area = $body->body_areas->find_or_create({ area_id => 1000 }); + +FixMyStreet::override_config { + MAPIT_TYPES => [ 'LBO' ], + MAPIT_URL => 'http://mapit.mysociety.org/', +}, sub { + is_deeply $c->get_body_sender( $body ), { method => 'Email', contact => undef }, 'defaults to email'; + $body_area->update({ area_id => 2481 }); # Croydon LBO + is_deeply $c->get_body_sender( $body ), { method => 'Email', contact => undef }, 'still email if London borough'; +}; + +$body->send_method( 'TestMethod' ); +is $c->get_body_sender( $body )->{ method }, 'TestMethod', 'uses send_method in preference to London'; + +$body_area->update({ area_id => 1000 }); # Nothing +is $c->get_body_sender( $body )->{ method }, 'TestMethod', 'uses send_method in preference to Email'; + +$body_area->delete; +$body->delete; + +done_testing(); diff --git a/t/cobrand/get_council_sender.t b/t/cobrand/get_council_sender.t deleted file mode 100644 index c05864d8a..000000000 --- a/t/cobrand/get_council_sender.t +++ /dev/null @@ -1,30 +0,0 @@ -use strict; -use warnings; - -use Test::More; - -use mySociety::Locale; -use FixMyStreet::App; - -use_ok 'FixMyStreet::Cobrand'; - -mySociety::Locale::gettext_domain( 'FixMyStreet' ); - -my $c = FixMyStreet::Cobrand::FixMyStreet->new(); - - -is_deeply $c->get_council_sender( '1000', { type => 'DIS' } ), { method => 'Email' }, 'defaults to email'; -is_deeply $c->get_council_sender( '1000', { type => 'LBO' } ), { method=> 'London' }, 'returns london report it if London borough'; - -my $conf = FixMyStreet::App->model('DB::Body')->find_or_create( - area_id => 1000, - endpoint => '', - send_method => 'TestMethod' -); - -is $c->get_council_sender( '1000', { type => 'LBO' } )->{ method }, 'TestMethod', 'uses send_method in preference to London'; -is $c->get_council_sender( '1000', { type => 'DIS' } )->{ method }, 'TestMethod', 'uses send_method in preference to Email'; - -$conf->delete; - -done_testing(); diff --git a/t/cobrand/hart.t b/t/cobrand/hart.t new file mode 100644 index 000000000..f4a2473eb --- /dev/null +++ b/t/cobrand/hart.t @@ -0,0 +1,16 @@ +use strict; +use warnings; +use Test::More; + +use FixMyStreet::TestMech; +my $mech = FixMyStreet::TestMech->new; + +FixMyStreet::override_config { + ALLOWED_COBRANDS => [ 'hart' ], +}, sub { + ok $mech->host("hart.fixmystreet.com"), "change host to hart"; + $mech->get_ok('/'); + $mech->content_like( qr/Hart\b/ ); +}; + +done_testing(); diff --git a/t/cobrand/loading.t b/t/cobrand/loading.t index bd83da07f..b4738fb63 100644 --- a/t/cobrand/loading.t +++ b/t/cobrand/loading.t @@ -9,61 +9,102 @@ use FixMyStreet; use_ok 'FixMyStreet::Cobrand'; # check that the allowed cobrands is correctly loaded from config -{ +sub check_allowed_cobrands { + my $should = shift; + $should = [ map { { moniker => $_, host => $_ } } @$should ]; my $allowed = FixMyStreet::Cobrand->get_allowed_cobrands; - ok $allowed, "got the allowed_cobrands"; + ok $allowed, "got the allowed_cobrands"; isa_ok $allowed, "ARRAY"; - cmp_ok scalar @$allowed, '>', 1, "got more than one"; + is_deeply $allowed, $should, "allowed_cobrands matched"; } -# fake the allowed cobrands for testing -my $override = Sub::Override->new( # - 'FixMyStreet::Cobrand::_get_allowed_cobrands' => - sub { return ['emptyhomes'] } -); -is_deeply FixMyStreet::Cobrand->get_allowed_cobrands, [ { moniker => 'emptyhomes', host => 'emptyhomes' } ], - 'overidden get_allowed_cobrands'; +FixMyStreet::override_config { ALLOWED_COBRANDS => 'fixmyhouse' }, + sub { check_allowed_cobrands([ 'fixmyhouse' ]); }; +FixMyStreet::override_config { ALLOWED_COBRANDS => [ 'fixmyhouse' ] }, + sub { check_allowed_cobrands([ 'fixmyhouse' ]); }; +FixMyStreet::override_config { ALLOWED_COBRANDS => [ 'fixmyhouse', 'fixmyshed' ] }, + sub { check_allowed_cobrands([ 'fixmyhouse', 'fixmyshed' ]); }; sub run_host_tests { my %host_tests = @_; for my $host ( sort keys %host_tests ) { - is FixMyStreet::Cobrand->get_class_for_host($host), - "FixMyStreet::Cobrand::$host_tests{$host}", - "does $host -> F::C::$host_tests{$host}"; + # get the cobrand class by host + my $cobrand = FixMyStreet::Cobrand->get_class_for_host($host); + my $test_class = $host_tests{$host}; + my $test_moniker = lc $test_class; + is $cobrand, "FixMyStreet::Cobrand::$test_class", "does $host -> F::C::$test_class"; + my $c = $cobrand->new(); + is $c->moniker, $test_moniker; } } -# get the cobrand class by host -run_host_tests( - 'www.fixmystreet.com' => 'Default', - 'reportemptyhomes.com' => 'EmptyHomes', - 'barnet.fixmystreet.com' => 'Default', # not in the allowed_cobrands list - 'some.odd.site.com' => 'Default', -); +# Only one cobrand, always use it +FixMyStreet::override_config { + ALLOWED_COBRANDS => [ 'fixmystreet' ], +}, sub { + run_host_tests( + 'www.fixmystreet.com' => 'FixMyStreet', + 'fiksgatami.example.org' => 'FixMyStreet', + 'oxfordshire.fixmystreet.com' => 'FixMyStreet', + 'some.odd.site.com' => 'FixMyStreet', + ); +}; -# now enable barnet too and check that it works -$override->replace( # - 'FixMyStreet::Cobrand::_get_allowed_cobrands' => - sub { return [ 'emptyhomes', 'barnet' ] } -); +# Only one cobrand, no .pm file, should still work +FixMyStreet::override_config { + ALLOWED_COBRANDS => [ 'nopmfile' ], +}, sub { + run_host_tests( + 'www.fixmystreet.com' => 'nopmfile', + 'some.odd.site.com' => 'nopmfile', + ); +}; -# get the cobrand class by host -run_host_tests( - 'www.fixmystreet.com' => 'Default', - 'reportemptyhomes.com' => 'EmptyHomes', - 'barnet.fixmystreet.com' => 'Barnet', # found now it is in allowed_cobrands - 'some.odd.site.com' => 'Default', -); +# Couple of cobrands, hostname checking and default fallback +FixMyStreet::override_config { + ALLOWED_COBRANDS => [ 'fiksgatami', 'fixmystreet' ], +}, sub { + run_host_tests( + 'www.fixmystreet.com' => 'FixMyStreet', + 'fiksgatami.example.org' => 'FiksGataMi', + 'oxfordshire.fixmystreet.com' => 'FixMyStreet', # not in the allowed_cobrands list + 'some.odd.site.com' => 'Default', + ); +}; + +# now enable oxfordshire too and check that it works +FixMyStreet::override_config { + ALLOWED_COBRANDS => [ 'fiksgatami', 'oxfordshire', 'fixmystreet' ], +}, sub { + run_host_tests( + 'www.fixmystreet.com' => 'FixMyStreet', + 'fiksgatami.example.org' => 'FiksGataMi', + 'oxfordshire.fixmystreet.com' => 'Oxfordshire', # found now it is in allowed_cobrands + 'some.odd.site.com' => 'Default', + ); +}; + +# And a check with some regex matching +FixMyStreet::override_config { + ALLOWED_COBRANDS => [ { 'fixmystreet' => 'example' }, 'oxfordshire', { 'testing' => 'fixmystreet' } ], +}, sub { + run_host_tests( + 'www.fixmystreet.com' => 'testing', + 'fiksgatami.example.org' => 'FixMyStreet', + 'oxfordshire.fixmystreet.com' => 'Oxfordshire', + 'some.odd.site.com' => 'Default', + ); +}; # check that the moniker works as expected both on class and object. -is FixMyStreet::Cobrand::EmptyHomes->moniker, 'emptyhomes', +is FixMyStreet::Cobrand::FiksGataMi->moniker, 'fiksgatami', 'class->moniker works'; -is FixMyStreet::Cobrand::EmptyHomes->new->moniker, 'emptyhomes', +is FixMyStreet::Cobrand::FiksGataMi->new->moniker, 'fiksgatami', 'object->moniker works'; # check is_default works ok FixMyStreet::Cobrand::Default->is_default, '::Default is default'; -ok !FixMyStreet::Cobrand::EmptyHomes->is_default, '::Emptyhomes is not default'; +ok !FixMyStreet::Cobrand::FiksGataMi->is_default, '::FiksGataMi is not default'; # all done done_testing(); diff --git a/t/cobrand/oxfordshire.t b/t/cobrand/oxfordshire.t new file mode 100644 index 000000000..b0fad3b56 --- /dev/null +++ b/t/cobrand/oxfordshire.t @@ -0,0 +1,73 @@ +use strict; +use warnings; +use Test::More; + +use FixMyStreet::TestMech; +my $mech = FixMyStreet::TestMech->new; + +subtest 'check /ajax defaults to open reports only' => sub { + my $categories = [ 'Bridges', 'Fences', 'Manhole' ]; + my $params = { + postcode => 'OX28 4DS', + latitude => 51.7847208192, + longitude => -1.49445264029, + }; + my $bbox = ($params->{longitude} - 0.01) . ',' . ($params->{latitude} - 0.01) + . ',' . ($params->{longitude} + 0.01) . ',' . ($params->{latitude} + 0.01); + + # Create one open and one fixed report in each category + foreach my $category ( @$categories ) { + foreach my $state ( 'confirmed', 'fixed' ) { + my %report_params = ( + %$params, + category => $category, + state => $state, + ); + $mech->create_problems_for_body( 1, 2237, 'Around page', \%report_params ); + } + } + + FixMyStreet::override_config { + ALLOWED_COBRANDS => [ { 'oxfordshire' => '.' } ], + MAPIT_URL => 'http://mapit.mysociety.org/', + }, sub { + my $json = $mech->get_ok_json( '/ajax?status=all&bbox=' . $bbox ); + my $pins = $json->{pins}; + is scalar @$pins, 6, 'correct number of reports created'; + + $json = $mech->get_ok_json( '/ajax?bbox=' . $bbox ); + $pins = $json->{pins}; + is scalar @$pins, 3, 'correct number of reports returned with no filters'; + + $json = $mech->get_ok_json( '/ajax?filter_category=Fences&bbox=' . $bbox ); + $pins = $json->{pins}; + is scalar @$pins, 1, 'only one Fences report by default'; + } +}; + +my $superuser = $mech->create_user_ok('superuser@example.com', name => 'Super User', is_superuser => 1); + +subtest 'Exor RDI download appears on Oxfordshire cobrand admin' => sub { + FixMyStreet::override_config { + ALLOWED_COBRANDS => [ { 'oxfordshire' => '.' } ], + }, sub { + $mech->log_in_ok( $superuser->email ); + $mech->get_ok('/admin'); + $mech->content_contains("Download Exor RDI"); + } +}; + +subtest 'Exor RDI download doesn’t appear outside of Oxfordshire cobrand admin' => sub { + FixMyStreet::override_config { + ALLOWED_COBRANDS => [ { 'fixmystreet' => '.' } ], + }, sub { + $mech->log_in_ok( $superuser->email ); + $mech->get_ok('/admin'); + $mech->content_lacks("Download Exor RDI"); + } +}; + +# Clean up +$mech->delete_user( $superuser ); +$mech->delete_problems_for_body( 2237 ); +done_testing(); diff --git a/t/cobrand/restriction.t b/t/cobrand/restriction.t new file mode 100644 index 000000000..873a396b7 --- /dev/null +++ b/t/cobrand/restriction.t @@ -0,0 +1,55 @@ +use strict; +use warnings; + +package FixMyStreet::Cobrand::Tester; + +use parent 'FixMyStreet::Cobrand::Default'; + +sub problems_restriction { + my ($self, $rs) = @_; + return $rs->search({ cobrand => 'tester' }); +} + +sub updates_restriction { + my ($self, $rs) = @_; + return $rs->search({ 'problem.cobrand' => 'tester' }, { join => 'problem' }); +} + +package main; + +use Test::More; +use FixMyStreet::TestMech; + +my $c = FixMyStreet::App->new; +my $cobrand = FixMyStreet::Cobrand::Tester->new({c => $c}); +$c->stash->{cobrand} = $cobrand; + +my $mech = FixMyStreet::TestMech->new; + +my ($prob1) = $mech->create_problems_for_body(1, 1234, 'Title'); +my ($prob2) = $mech->create_problems_for_body(1, 1234, 'Title', { cobrand => 'tester' }); +$mech->create_problems_for_body(1, 1234, 'Title', { latitude => 0, longitude => 0 }); +$mech->create_problems_for_body(1, 1234, 'Title', { cobrand => 'tester', latitude => 0, longitude => 0 }); + +for (1..2) { + $c->model('DB::Comment')->create({ + problem_id => $_ == 1 ? $prob1->id : $prob2->id, + user_id => $prob2->user_id, + name => 'User', + mark_fixed => 'false', + text => 'This is some update text', + state => 'confirmed', + cobrand => 'tester', + anonymous => 'f', + }); +} + +is($c->model('DB::Problem')->count, 4, 'Four reports in database'); +is($cobrand->problems->count, 2, 'Two reports in the right cobrand'); +is($cobrand->updates->count, 1, 'One update in the right cobrand'); + +my $nearby = $c->model('DB::Nearby')->nearby($c, 5, [], 10, 0.003, 0.004); +is(@$nearby, 1, 'One report close to the origin point'); + +$mech->delete_problems_for_body(1234); +done_testing(); diff --git a/t/cobrand/two_tier.t b/t/cobrand/two_tier.t new file mode 100644 index 000000000..97bbccc01 --- /dev/null +++ b/t/cobrand/two_tier.t @@ -0,0 +1,27 @@ +use strict; +use warnings; +use Test::More; + +use FixMyStreet; +use FixMyStreet::Cobrand; + +my @cobrands = ( + [ hart => 2333 ], + [ oxfordshire => 2237 ], + [ stevenage => 2347 ], + [ warwickshire => 2243 ], +); + +FixMyStreet::override_config { + ALLOWED_COBRANDS => [ map $_->[0], @cobrands ], +}, sub { + + for my $c (@cobrands) { + my ($m, $id) = @$c; + my $cobrand = FixMyStreet::Cobrand->get_class_for_moniker($m); + my $council_id = $cobrand->council_id; + is $council_id, $id, "council_id for $m"; + } +}; + +done_testing; diff --git a/t/cobrand/zurich-logo_portal.x.jpg b/t/cobrand/zurich-logo_portal.x.jpg Binary files differnew file mode 100644 index 000000000..c0cfef240 --- /dev/null +++ b/t/cobrand/zurich-logo_portal.x.jpg 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; diff --git a/t/cobrand/zurich_attachments.txt b/t/cobrand/zurich_attachments.txt new file mode 100644 index 000000000..25a1bacf0 --- /dev/null +++ b/t/cobrand/zurich_attachments.txt @@ -0,0 +1,33 @@ +MIME-Version: 1.0
+Subject: =?iso-8859-1?Q?Z=FCri?= wie neu: Weitergeleitete Meldung #REPORT_ID
+Content-Type: multipart/mixed; boundary="BOUNDARY"
+To: "External Body" <external_body@example.net>
+Content-Transfer-Encoding: 7bit
+From: "FixMyStreet" <division@example.org>
+
+
+--BOUNDARY
+Content-Type: text/plain; charset="utf-8"
+Content-Transfer-Encoding: quoted-printable
+
+Gr=C3=BCezi External Body,
+
+=C3=96ffentliche URL: http://www.example.org/report/REPORT_ID
+
+Bei Fragen zu "Z=C3=BCri wie neu" wenden Sie sich bitte an =
+gis-zentrum@zuerich.ch.=
+
+--BOUNDARY
+Content-Type: image/jpeg; name="REPORT_ID.0.jpeg"
+Content-Disposition: inline; filename="REPORT_ID.0.jpeg"
+Content-Transfer-Encoding: base64
+
+/9j/4AAQSkZJRgABAQEASABIAAD/2wBDAAUDBAQEAwUEBAQFBQUGBwwIBwcHBw8LCwkMEQ8SEhEP
+ERETFhwXExQaFRERGCEYGh0dHx8fExciJCIeJBweHx7/2wBDAQUFBQcGBw4ICA4eFBEUHh4eHh4e
+Hh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh7/wAARCABTAAEDAREA
+AhEBAxEB/8QAFwAAAwEAAAAAAAAAAAAAAAAAAAIIB//EAB8QAQAABAcAAAAAAAAAAAAAAAADBAbT
+BxcYVVaUpf/EABcBAQEBAQAAAAAAAAAAAAAAAAAFBgT/xAAgEQEAAAQHAQAAAAAAAAAAAAAAAwQV
+UgECFlNhodGx/9oADAMBAAIRAxEAPwCywAIozyxS5R58tbbujSW33j6zFRj3fGbKbjAGAgAACs9N
+FCbtUfYg2mO1BM25e/V+lQeW3ISo/9k=
+
+--BOUNDARY--
diff --git a/t/email.t b/t/email.t new file mode 100644 index 000000000..40a650da5 --- /dev/null +++ b/t/email.t @@ -0,0 +1,20 @@ +use strict; +use warnings; + +use Test::More; +use FixMyStreet::Email; + +my $secret = FixMyStreet::DB->resultset('Secret')->update({ + secret => 'abcdef123456' }); + +my $hash = FixMyStreet::Email::hash_from_id("report", 123); +is $hash, '8fb274c6', 'Hash generation okay'; + +my $token = FixMyStreet::Email::generate_verp_token("report", 123); +is $token, "report-123-8fb274c6", 'Token generation okay'; + +my ($type, $id) = FixMyStreet::Email::check_verp_token($token); +is $type, "report", 'Correct type from token'; +is $id, 123, 'Correct ID from token'; + +done_testing(); diff --git a/t/fixmystreet.t b/t/fixmystreet.t index d7f00b047..a601c10d5 100644 --- a/t/fixmystreet.t +++ b/t/fixmystreet.t @@ -18,17 +18,17 @@ is "$path_to_path", $file_path, "got $file_path"; # check that the config gets loaded and is immutable my $config = FixMyStreet->config; isa_ok $config, 'HASH'; -is $config->{GAZE_URL}, 'http://gaze.mysociety.org/gaze', +is $config->{GAZE_URL}, 'https://gaze.mysociety.org/gaze', "got GAZE_URL correctly"; throws_ok( sub { $config->{GAZE_URL} = 'some other value'; }, qr/Modification of a read-only value attempted/, 'attempt to change config caught' ); -is $config->{GAZE_URL}, 'http://gaze.mysociety.org/gaze', "GAZE_URL unchanged"; +is $config->{GAZE_URL}, 'https://gaze.mysociety.org/gaze', "GAZE_URL unchanged"; # check that we can get the value by key as well -is FixMyStreet->config('GAZE_URL'), 'http://gaze.mysociety.org/gaze', +is FixMyStreet->config('GAZE_URL'), 'https://gaze.mysociety.org/gaze', "GAZE_URL correct when got by key"; is FixMyStreet->config('BAD_KEY_DOES_NOT_EXIST'), undef, "config miss is undef"; @@ -3,25 +3,22 @@ use warnings; use Test::More; -use FixMyStreet; -use mySociety::Locale; -use Encode; -use Data::Dumper; use HTTP::Headers; use Sort::Key qw(keysort); use POSIX 'strcoll'; -local $Data::Dumper::Sortkeys = 1; -use utf8; + +use FixMyStreet; +use mySociety::Locale; # check that the mo files have been generated die "You need to run 'commonlib/bin/gettext-makemo --quiet FixMyStreet' " . "to generate the *.mo files needed." unless -e FixMyStreet->path_to( - 'locale/cy_GB.UTF-8/LC_MESSAGES/FixMyStreet-EmptyHomes.mo'); + 'locale/nb_NO.UTF-8/LC_MESSAGES/FixMyStreet.mo'); # Test the language negotiation works my $lang = mySociety::Locale::negotiate_language( - 'en-gb,English,en_GB|cy,Cymraeg,cy_GB|es,Spanish,es_ES', + 'en-gb,English,en_GB|es,Spanish,es_ES', undef, HTTP::Headers->new( Accept_Language => 'es,en-gb;q=0.6,en;q=0.4' @@ -31,27 +28,26 @@ is $lang, 'es', 'Language negotiation works okay'; # Example strings my $english = "Please enter a valid email"; -my $welsh = "Cofnodwch gyfeiriad e-bost dilys"; +my $norwegian = "Legg til en gyldig e-post"; # set english as the language mySociety::Locale::negotiate_language( # - 'en-gb,English,en_GB|cy,Cymraeg,cy_GB', 'en_GB' + 'en-gb,English,en_GB|nb,Norwegian,nb_NO', 'en_GB' ); -mySociety::Locale::gettext_domain( 'FixMyStreet-EmptyHomes', 1 ); +mySociety::Locale::gettext_domain( 'FixMyStreet', 1 ); mySociety::Locale::change(); is _($english), $english, "english to english"; -# set to welsh and check for translation -mySociety::Locale::change('cy'); -is _($english), $welsh, "english to welsh"; +mySociety::Locale::change('nb'); +is _($english), $norwegian, "english to norwegian"; # check that being in a deep directory does not confuse the code chdir FixMyStreet->path_to('t/app/controller') . ''; -mySociety::Locale::gettext_domain( 'FixMyStreet-EmptyHomes', 1, +mySociety::Locale::gettext_domain( 'FixMyStreet', 1, FixMyStreet->path_to('locale')->stringify ); -mySociety::Locale::change('cy'); -is _($english), $welsh, "english to welsh (deep directory)"; +mySociety::Locale::change('nb'); +is _($english), $norwegian, "english to norwegian (deep directory)"; # test that sorting works as expected in the right circumstances... my @random_sorted = qw( Å Z Ø A ); @@ -59,14 +55,10 @@ my @EN_sorted = qw( A Å Ø Z ); my @NO_sorted = qw( A Z Ø Å ); my @default_sorted = qw( A Z Å Ø ); -sub utf8_diag { - diag encode_utf8( Dumper(@_) ); -} - -{ +SKIP: { mySociety::Locale::negotiate_language( # - 'en-gb,English,en_GB|cy,Cymraeg,cy_GB', 'en_GB' + 'en-gb,English,en_GB', 'en_GB' ); mySociety::Locale::change(); @@ -78,14 +70,18 @@ sub utf8_diag { is_deeply( [ keysort { $_ } @random_sorted ], \@default_sorted, "keysort correctly with no locale" ); + skip 'Will not pass on Mac', 1 if $^O eq 'darwin'; + # Note - this obeys the locale is_deeply( [ sort { strcoll( $a, $b ) } @random_sorted ], \@EN_sorted, "sort strcoll correctly with no locale (to 'en_GB')" ); } -{ +SKIP: { + skip 'Will not pass on Mac', 2 if $^O eq 'darwin'; + mySociety::Locale::negotiate_language( # - 'en-gb,English,en_GB|cy,Cymraeg,cy_GB', 'en_GB' + 'en-gb,English,en_GB', 'en_GB' ); mySociety::Locale::change(); use locale; @@ -100,7 +96,9 @@ sub utf8_diag { \@EN_sorted, "sort strcoll correctly with use locale 'en_GB'" ); } -{ +SKIP: { + skip 'Will not pass on Mac', 2 if $^O eq 'darwin'; + mySociety::Locale::negotiate_language( # 'nb-no,Norwegian,nb_NO', 'nb_NO' ); diff --git a/t/map/tilma/original.t b/t/map/tilma/original.t index 04c4d578c..f16f5b244 100644 --- a/t/map/tilma/original.t +++ b/t/map/tilma/original.t @@ -1,38 +1,34 @@ -#!/usr/bin/perl - use strict; use warnings; use Test::More; -use FixMyStreet::App; +use FixMyStreet::DB; use FixMyStreet::Map; use FixMyStreet::TestMech; use DateTime; use mySociety::Locale; +use Catalyst::Test 'FixMyStreet::App'; + my $mech = FixMyStreet::TestMech->new; mySociety::Locale::gettext_domain('FixMyStreet'); FixMyStreet::Map::set_map_class(); -my $r = Catalyst::Request->new( { base => URI->new('/'), uri => URI->new('http://fixmystreet.com/test'), parameters => { bbox => '-7.6,49.7,-7.5,49.8' } } ); - -my $c = FixMyStreet::App->new( { - request => $r, -}); +my $c = ctx_request('http://fixmystreet.com/test?bbox=-7.6,49.7,-7.5,49.8'); $mech->delete_user('test@example.com'); my $user = - FixMyStreet::App->model('DB::User') + FixMyStreet::DB->resultset('User') ->find_or_create( { email => 'test@example.com', name => 'Test User' } ); ok $user, "created test user"; my $dt = DateTime->now(); -my $report = FixMyStreet::App->model('DB::Problem')->find_or_create( +my $report = FixMyStreet::DB->resultset('Problem')->find_or_create( { postcode => 'SW1A 1AA', - council => '2504', + bodies_str => '2504', areas => ',105255,11806,11828,2247,2504,', category => 'Other', title => 'Test 2', @@ -71,10 +67,26 @@ for my $test ( colour => 'yellow', }, { + state => 'duplicate', + colour => 'yellow', + }, + { + state => 'unable to fix', + colour => 'yellow', + }, + { + state => 'not responsible', + colour => 'yellow', + }, + { state => 'investigating', colour => 'yellow', }, { + state => 'action scheduled', + colour => 'yellow', + }, + { state => 'planned', colour => 'yellow', }, @@ -87,18 +99,18 @@ for my $test ( $report->state($test->{state}); $report->update; - my ( $pins, $around_map_list, $nearby, $dist ) = - FixMyStreet::Map::map_pins( $c, 0, 0, 0, 0 ); + my ( $on_map_all, $on_map_list, $nearby, $dist ) = + FixMyStreet::Map::map_features($c, bbox => "0,0,0,0"); - ok $pins; - ok $around_map_list; + ok $on_map_list; ok $nearby; ok $dist; my $id = $report->id; my $colour = $test->{colour}; - is $pins->[0][2], $colour, 'pin colour'; + my $pin_colour = $c->cobrand->pin_colour($on_map_all->[0], 'around'); + is $pin_colour, $colour, 'pin colour'; }; } diff --git a/t/open311.t b/t/open311.t index 2371c53bc..e6ea9b6fc 100644 --- a/t/open311.t +++ b/t/open311.t @@ -1,19 +1,18 @@ #!/usr/bin/env perl +use utf8; use strict; use warnings; +use File::Temp 'tempdir'; +use Path::Tiny; use Test::More; use Test::Warn; -use FixMyStreet::App; +use FixMyStreet::DB; use CGI::Simple; use HTTP::Response; use DateTime; use DateTime::Format::W3CDTF; -use FindBin; -use lib "$FindBin::Bin/../perllib"; -use lib "$FindBin::Bin/../commonlib/perllib"; - use_ok( 'Open311' ); my $o = Open311->new(); @@ -27,31 +26,39 @@ EOT is $o->_process_error( $err_text ), "400: Service Code cannot be null -- can't proceed with the request.\n", 'error text parsing'; is $o->_process_error( '503 - service unavailable' ), 'unknown error', 'error text parsing of bad error'; -my $o2 = Open311->new( endpoint => 'http://192.168.50.1/open311/', jurisdiction => 'example.org' ); - -my $u = FixMyStreet::App->model('DB::User')->new( { email => 'test@example.org', name => 'A User' } ); - -my $p = FixMyStreet::App->model('DB::Problem')->new( { - latitude => 1, - longitude => 1, - title => 'title', - detail => 'detail', - user => $u, - id => 1, -} ); - -my $expected_error = qr{Failed to submit problem 1 over Open311}ism; - -warning_like {$o2->send_service_request( $p, { url => 'http://example.com/' }, 1 )} $expected_error, 'warning generated on failed call'; +my $o2 = Open311->new( endpoint => 'http://127.0.0.1/open311/', jurisdiction => 'example.org' ); + +my $u = FixMyStreet::DB->resultset('User')->new( { email => 'test@example.org', name => 'A User' } ); + +for my $sfc (0..2) { + my $p = FixMyStreet::DB->resultset('Problem')->new( { + latitude => 1, + longitude => 1, + title => 'title', + detail => 'detail', + user => $u, + id => 1, + name => 'A User', + cobrand => 'fixmystreet', + send_fail_count => $sfc, + } ); + my $expected_error = qr{Failed to submit problem 1 over Open311}ism; + + if ($sfc == 2) { + warning_like {$o2->send_service_request( $p, { url => 'http://example.com/' }, 1 )} $expected_error, 'warning generated on failed call'; + } else { + warning_like {$o2->send_service_request( $p, { url => 'http://example.com/' }, 1 )} undef, 'no warning generated on failed call'; + } +} my $dt = DateTime->now(); -my $user = FixMyStreet::App->model('DB::User')->new( { +my $user = FixMyStreet::DB->resultset('User')->new( { name => 'Test User', email => 'test@example.com', } ); -my $problem = FixMyStreet::App->model('DB::Problem')->new( { +my $problem = FixMyStreet::DB->resultset('Problem')->new( { id => 80, external_id => 81, state => 'confirmed', @@ -61,19 +68,20 @@ my $problem = FixMyStreet::App->model('DB::Problem')->new( { latitude => 1, longitude => 2, user => $user, + name => 'Test User', + cobrand => 'fixmystreet', } ); subtest 'posting service request' => sub { my $extra = { url => 'http://example.com/report/1', + easting => 'SET', }; my $results = make_service_req( $problem, $extra, $problem->category, '<?xml version="1.0" encoding="utf-8"?><service_requests><request><service_request_id>248</service_request_id></request></service_requests>' ); is $results->{ res }, 248, 'got request id'; - my $req = $o->test_req_used; - my $description = <<EOT; title: a problem @@ -86,13 +94,14 @@ EOT ; my $c = CGI::Simple->new( $results->{ req }->content ); + (my $c_description = $c->param('description')) =~ s/\r\n/\n/g; is $c->param('email'), $user->email, 'correct email'; is $c->param('first_name'), 'Test', 'correct first name'; is $c->param('last_name'), 'User', 'correct last name'; is $c->param('lat'), 1, 'latitide correct'; is $c->param('long'), 2, 'longitude correct'; - is $c->param('description'), $description, 'description correct'; + is $c_description, $description, 'description correct'; is $c->param('service_code'), 'pothole', 'service code correct'; }; @@ -106,13 +115,11 @@ subtest 'posting service request with basic_description' => sub { $extra, $problem->category, '<?xml version="1.0" encoding="utf-8"?><service_requests><request><service_request_id>248</service_request_id></request></service_requests>', - { basic_description => 1 }, + { extended_description => 0 }, ); is $results->{ res }, 248, 'got request id'; - my $req = $o->test_req_used; - my $c = CGI::Simple->new( $results->{ req }->content ); is $c->param('description'), $problem->detail, 'description correct'; @@ -151,7 +158,7 @@ for my $test ( ], }, { - title => 'magic fms_extra parameters handled correctly', + desc => 'magic fms_extra parameters handled correctly', extra => [ { name => 'fms_extra_title', @@ -176,7 +183,6 @@ for my $test ( my $results = make_service_req( $problem, $extra, $problem->category, '<?xml version="1.0" encoding="utf-8"?><service_requests><request><service_request_id>248</service_request_id></request></service_requests>' ); - my $req = $o->test_req_used; my $c = CGI::Simple->new( $results->{req}->content ); for my $param ( @{ $test->{params} } ) { @@ -185,13 +191,38 @@ for my $test ( }; } -my $comment = FixMyStreet::App->model('DB::Comment')->new( { +for my $test ( + { + desc => 'Check uses report name over user name', + name => 'Nom de Report', + first_name => 'Nom', + last_name => 'de Report', + }, +) { + subtest $test->{desc} => sub { + $problem->extra( undef ); + $problem->name( $test->{name} ); + my $extra = { url => 'http://example.com/report/1', }; + + my $results = make_service_req( $problem, $extra, $problem->category, +'<?xml version="1.0" encoding="utf-8"?><service_requests><request><service_request_id>248</service_request_id></request></service_requests>' + ); + my $c = CGI::Simple->new( $results->{req}->content ); + + is $c->param( 'first_name' ), $test->{first_name}, 'correct first name'; + is $c->param( 'last_name' ), $test->{last_name}, 'correct last name'; + }; +} + + +my $comment = FixMyStreet::DB->resultset('Comment')->new( { id => 38362, user => $user, problem => $problem, anonymous => 0, text => 'this is a comment', confirmed => $dt, + problem_state => 'confirmed', extra => { title => 'Mr', email_alerts_requested => 0 }, } ); @@ -200,8 +231,6 @@ subtest 'basic request update post parameters' => sub { is $results->{ res }, 248, 'got update id'; - my $req = $o->test_req_used; - my $c = CGI::Simple->new( $results->{ req }->content ); is $c->param('description'), 'this is a comment', 'email correct'; @@ -216,12 +245,10 @@ subtest 'basic request update post parameters' => sub { }; subtest 'extended request update post parameters' => sub { - my $results = make_update_req( $comment, '<?xml version="1.0" encoding="utf-8"?><service_request_updates><request_update><update_id>248</update_id></request_update></service_request_updates>', 1 ); + my $results = make_update_req( $comment, '<?xml version="1.0" encoding="utf-8"?><service_request_updates><request_update><update_id>248</update_id></request_update></service_request_updates>', { use_extended_updates => 1 } ); is $results->{ res }, 248, 'got update id'; - my $req = $o->test_req_used; - my $c = CGI::Simple->new( $results->{ req }->content ); is $c->param('description'), 'this is a comment', 'email correct'; @@ -239,18 +266,26 @@ subtest 'extended request update post parameters' => sub { }; subtest 'check media url set' => sub { - $comment->photo(1); - $comment->cobrand('fixmystreet'); + my $UPLOAD_DIR = tempdir( CLEANUP => 1 ); - my $results = make_update_req( $comment, '<?xml version="1.0" encoding="utf-8"?><service_request_updates><request_update><update_id>248</update_id></request_update></service_request_updates>' ); + my $image_path = path('t/app/controller/sample.jpg'); + $image_path->copy( path( $UPLOAD_DIR, '0123456789012345678901234567890123456789.jpeg' ) ); - is $results->{ res }, 248, 'got update id'; + $comment->photo("0123456789012345678901234567890123456789"); + $comment->cobrand('fixmystreet'); - my $req = $o->test_req_used; + FixMyStreet::override_config { + UPLOAD_DIR => $UPLOAD_DIR, + }, sub { + my $results = make_update_req( $comment, '<?xml version="1.0" encoding="utf-8"?><service_request_updates><request_update><update_id>248</update_id></request_update></service_request_updates>' ); - my $c = CGI::Simple->new( $results->{ req }->content ); - my $expected_path = '/c/' . $comment->id . '.full.jpeg'; - like $c->param('media_url'), qr/$expected_path/, 'image url included'; + is $results->{ res }, 248, 'got update id'; + + my $c = CGI::Simple->new( $results->{ req }->content ); + my $expected_path = '/c/' . $comment->id . '.0.full.jpeg'; + like $c->param('media_url'), qr/$expected_path/, 'image url included'; + }; + $comment->photo(undef); }; foreach my $test ( @@ -258,16 +293,47 @@ foreach my $test ( desc => 'comment with fixed state sends status of CLOSED', state => 'fixed', status => 'CLOSED', + extended => 'FIXED', }, { desc => 'comment with fixed - user state sends status of CLOSED', state => 'fixed - user', status => 'CLOSED', + extended => 'FIXED', }, { desc => 'comment with fixed - council state sends status of CLOSED', state => 'fixed - council', status => 'CLOSED', + extended => 'FIXED', + }, + { + desc => 'comment with duplicate state sends status of CLOSED', + state => 'duplicate', + anon => 0, + status => 'CLOSED', + extended => 'DUPLICATE', + }, + { + desc => 'comment with not reponsible state sends status of CLOSED', + state => 'not responsible', + anon => 0, + status => 'CLOSED', + extended => 'NOT_COUNCILS_RESPONSIBILITY', + }, + { + desc => 'comment with unable to fix state sends status of CLOSED', + state => 'unable to fix', + anon => 0, + status => 'CLOSED', + extended => 'NO_FURTHER_ACTION', + }, + { + desc => 'comment with internal referral state sends status of CLOSED', + state => 'internal referral', + anon => 0, + status => 'CLOSED', + extended => 'INTERNAL_REFERRAL', }, { desc => 'comment with closed state sends status of CLOSED', @@ -278,29 +344,42 @@ foreach my $test ( desc => 'comment with investigating state sends status of OPEN', state => 'investigating', status => 'OPEN', + extended => 'INVESTIGATING', }, { desc => 'comment with planned state sends status of OPEN', state => 'planned', status => 'OPEN', + extended => 'ACTION_SCHEDULED', }, { - desc => 'comment with in progress state sends status of OPEN', - state => 'in progress', + desc => 'comment with action scheduled state sends status of OPEN', + state => 'action scheduled', + anon => 0, status => 'OPEN', + extended => 'ACTION_SCHEDULED', }, { - state => 'confirmed', + desc => 'comment with in progress state sends status of OPEN', + state => 'in progress', status => 'OPEN', + extended => 'IN_PROGRESS', }, ) { subtest $test->{desc} => sub { + $comment->problem_state( $test->{state} ); $comment->problem->state( $test->{state} ); my $results = make_update_req( $comment, '<?xml version="1.0" encoding="utf-8"?><service_request_updates><request_update><update_id>248</update_id></request_update></service_request_updates>' ); my $c = CGI::Simple->new( $results->{ req }->content ); is $c->param('status'), $test->{status}, 'correct status'; + + if ( $test->{extended} ) { + my $results = make_update_req( $comment, '<?xml version="1.0" encoding="utf-8"?><service_request_updates><request_update><update_id>248</update_id></request_update></service_request_updates>', { extended_statuses => 1 } ); + my $c = CGI::Simple->new( $results->{ req }->content ); + is $c->param('status'), $test->{extended}, 'correct extended status'; + } }; } @@ -319,16 +398,70 @@ for my $test ( }, ) { subtest $test->{desc} => sub { + $comment->problem_state( $test->{state} ); $comment->problem->state( $test->{state} ); $comment->anonymous( $test->{anon} ); - my $results = make_update_req( $comment, '<?xml version="1.0" encoding="utf-8"?><service_request_updates><request_update><update_id>248</update_id></request_update></service_request_updates>', 1 ); + my $results = make_update_req( $comment, '<?xml version="1.0" encoding="utf-8"?><service_request_updates><request_update><update_id>248</update_id></request_update></service_request_updates>', { use_extended_updates => 1 } ); my $c = CGI::Simple->new( $results->{ req }->content ); is $c->param('public_anonymity_required'), $test->{anon} ? 'TRUE' : 'FALSE', 'correct anonymity'; }; } +my $dt2 = $dt->clone; +$dt2->add( 'minutes' => 1 ); + +my $comment2 = FixMyStreet::DB->resultset('Comment')->new( { + id => 38363, + user => $user, + problem => $problem, + anonymous => 0, + text => 'this is a comment', + confirmed => $dt, + problem_state => 'confirmed', + extra => { title => 'Mr', email_alerts_requested => 0 }, +} ); + +for my $test ( + { + desc => 'comment with fixed - council state sends status of CLOSED even if problem is open', + state => 'fixed - council', + problem_state => 'confirmed', + status => 'CLOSED', + extended => 'FIXED', + }, + { + desc => 'comment marked open sends status of OPEN even if problem is closed', + state => 'confirmed', + problem_state => 'fixed - council', + status => 'OPEN', + extended => 'OPEN', + }, + { + desc => 'comment with no problem state falls back to report state', + state => '', + problem_state => 'fixed - council', + status => 'CLOSED', + extended => 'FIXED', + }, +) { + subtest $test->{desc} => sub { + $comment->problem_state( $test->{state} ); + $comment->problem->state( $test->{problem_state} ); + my $results = make_update_req( $comment, '<?xml version="1.0" encoding="utf-8"?><service_request_updates><request_update><update_id>248</update_id></request_update></service_request_updates>' ); + + my $c = CGI::Simple->new( $results->{ req }->content ); + is $c->param('status'), $test->{status}, 'correct status'; + + if ( $test->{extended} ) { + my $results = make_update_req( $comment, '<?xml version="1.0" encoding="utf-8"?><service_request_updates><request_update><update_id>248</update_id></request_update></service_request_updates>', { extended_statuses => 1 } ); + my $c = CGI::Simple->new( $results->{ req }->content ); + is $c->param('status'), $test->{extended}, 'correct extended status'; + } + }; +} + for my $test ( { @@ -445,6 +578,9 @@ for my $test ( }; } +$problem->send_fail_count(2); +$comment->send_fail_count(2); + subtest 'No request id in reponse' => sub { my $results; warning_like { @@ -534,24 +670,45 @@ for my $test ( }; } +subtest 'check FixaMinGata' => sub { + $problem->cobrand('fixamingata'); + $problem->detail("MØØse"); + my $extra = { + url => 'http://example.com/report/1', + }; + my $results = make_service_req( $problem, $extra, $problem->category, '<?xml version="1.0" encoding="utf-8"?><service_requests><request><service_request_id>248</service_request_id></request></service_requests>' ); + is $results->{ res }, 248, 'got request id'; + my $description = <<EOT; +Beskrivning: MØØse + +Länk till ärendet: http://example.com/report/1 + +Skickad via FixaMinGata +EOT +; + my $c = CGI::Simple->new( $results->{ req }->content ); + (my $c_description = $c->param('description')) =~ s/\r\n/\n/g; + utf8::decode($c_description); + is $c_description, $description, 'description correct'; +}; + done_testing(); + sub make_update_req { my $comment = shift; my $xml = shift; - my $extended = shift; + my $open311_args = shift || {}; my $params = { - object => $comment, - xml => $xml, - method => 'post_service_request_update', - path => 'servicerequestupdates.xml', + object => $comment, + xml => $xml, + method => 'post_service_request_update', + path => 'servicerequestupdates.xml', + open311_conf => $open311_args, }; - if ( $extended ) { - $params->{ open311_conf } = { use_extended_updates => 1 }; - } - return make_req( $params ); + return _make_req( $params ); } sub make_service_req { @@ -561,7 +718,7 @@ sub make_service_req { my $xml = shift; my $open311_args = shift || {}; - return make_req( + return _make_req( { object => $problem, xml => $xml, @@ -573,7 +730,7 @@ sub make_service_req { ); } -sub make_req { +sub _make_req { my $args = shift; my $object = $args->{object}; diff --git a/t/open311/getservicerequestupdates.t b/t/open311/getservicerequestupdates.t index aba811e58..8876a3ccf 100644 --- a/t/open311/getservicerequestupdates.t +++ b/t/open311/getservicerequestupdates.t @@ -4,23 +4,27 @@ use strict; use warnings; use Test::More; use CGI::Simple; - -use FindBin; -use lib "$FindBin::Bin/../perllib"; -use lib "$FindBin::Bin/../commonlib/perllib"; +use LWP::Protocol::PSGI; +use t::Mock::Static; use_ok( 'Open311' ); use_ok( 'Open311::GetServiceRequestUpdates' ); use DateTime; -use FixMyStreet::App; +use DateTime::Format::W3CDTF; +use FixMyStreet::DB; -my $user = FixMyStreet::App->model('DB::User')->find_or_create( +my $user = FixMyStreet::DB->resultset('User')->find_or_create( { email => 'system_user@example.com' } ); +my %bodies = ( + 2482 => FixMyStreet::DB->resultset("Body")->new({ id => 2482 }), + 2651 => FixMyStreet::DB->resultset("Body")->new({ id => 2651 }), +); + my $requests_xml = qq{<?xml version="1.0" encoding="utf-8"?> <service_requests_updates> <request_update> @@ -34,7 +38,7 @@ UPDATED_DATETIME }; -my $dt = DateTime->now; +my $dt = DateTime->now(formatter => DateTime::Format::W3CDTF->new); # basic xml -> perl object tests for my $test ( @@ -48,13 +52,13 @@ for my $test ( desc => 'basic parsing - empty element', updated_datetime => '<updated_datetime />', res => { update_id => 638344, service_request_id => 1, - status => 'open', description => 'This is a note', updated_datetime => {} } , + status => 'open', description => 'This is a note', updated_datetime => undef } , }, { desc => 'basic parsing - element with no content', updated_datetime => '<updated_datetime></updated_datetime>', res => { update_id => 638344, service_request_id => 1, - status => 'open', description => 'This is a note', updated_datetime => {} } , + status => 'open', description => 'This is a note', updated_datetime => undef } , }, { desc => 'basic parsing - element with content', @@ -102,7 +106,7 @@ subtest 'check extended request parsed correctly' => sub { }; -my $problem_rs = FixMyStreet::App->model('DB::Problem'); +my $problem_rs = FixMyStreet::DB->resultset('Problem'); my $problem = $problem_rs->new( { postcode => 'EH99 1SP', @@ -123,7 +127,7 @@ my $problem = $problem_rs->new( lastupdate => DateTime->now()->subtract( days => 1 ), anonymous => 1, external_id => time(), - council => 2482, + bodies_str => 2482, } ); @@ -131,71 +135,212 @@ $problem->insert; for my $test ( { - desc => 'element with content', - updated_datetime => sprintf( '<updated_datetime>%s</updated_datetime>', $dt ), + desc => 'OPEN status for confirmed problem does not change state', description => 'This is a note', external_id => 638344, start_state => 'confirmed', - close_comment => 0, + comment_status => 'OPEN', mark_fixed=> 0, mark_open => 0, problem_state => undef, end_state => 'confirmed', }, { - desc => 'comment closes report', - updated_datetime => sprintf( '<updated_datetime>%s</updated_datetime>', $dt ), + 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', - close_comment => 1, + 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 => 'comment re-opens fixed report', - updated_datetime => sprintf( '<updated_datetime>%s</updated_datetime>', $dt ), + 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', - close_comment => 0, + comment_status => 'OPEN', mark_fixed => 0, mark_open => 0, problem_state => 'confirmed', end_state => 'confirmed', }, { - desc => 'comment re-opens closed report', - updated_datetime => sprintf( '<updated_datetime>%s</updated_datetime>', $dt ), + 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 => 'closed', - close_comment => 0, + start_state => 'not responsible', + comment_status => 'OPEN', mark_fixed => 0, mark_open => 0, problem_state => 'confirmed', end_state => 'confirmed', }, { - desc => 'comment leaves report closed', - updated_datetime => sprintf( '<updated_datetime>%s</updated_datetime>', $dt ), + 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 => 'closed', - close_comment => 1, + start_state => 'confirmed', + comment_status => 'NO_FURTHER_ACTION', mark_fixed => 0, mark_open => 0, - end_state => 'closed', + 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; - $local_requests_xml =~ s/UPDATED_DATETIME/$test->{updated_datetime}/; + my $updated_datetime = sprintf( '<updated_datetime>%s</updated_datetime>', $dt ); + $local_requests_xml =~ s/UPDATED_DATETIME/$updated_datetime/; $local_requests_xml =~ s#<service_request_id>\d+</service_request_id>#<service_request_id>@{[$problem->external_id]}</service_request_id>#; $local_requests_xml =~ s#<service_request_id_ext>\d+</service_request_id_ext>#<service_request_id_ext>@{[$problem->id]}</service_request_id_ext>#; - $local_requests_xml =~ s#<status>\w+</status>#<status>closed</status># if $test->{close_comment}; + $local_requests_xml =~ s#<status>\w+</status>#<status>$test->{comment_status}</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 } ); @@ -204,14 +349,13 @@ for my $test ( $problem->state( $test->{start_state} ); $problem->update; - my $council_details = { areaid => 2482 }; my $update = Open311::GetServiceRequestUpdates->new( system_user => $user ); - $update->update_comments( $o, $council_details ); + $update->update_comments( $o, $bodies{2482} ); is $problem->comments->count, 1, 'comment count'; $problem->discard_changes; - my $c = FixMyStreet::App->model('DB::Comment')->search( { external_id => $test->{external_id} } )->first; + my $c = FixMyStreet::DB->resultset('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'; @@ -221,6 +365,31 @@ for my $test ( }; } +subtest 'Update with media_url includes image in update' => sub { + my $guard = LWP::Protocol::PSGI->register(t::Mock::Static->to_psgi_app, host => 'example.com'); + + my $local_requests_xml = $requests_xml; + my $updated_datetime = sprintf( '<updated_datetime>%s</updated_datetime>', $dt ); + $local_requests_xml =~ s/UPDATED_DATETIME/$updated_datetime/; + $local_requests_xml =~ s#<service_request_id>\d+</service_request_id># + <service_request_id>@{[$problem->external_id]}</service_request_id> + <media_url>http://example.com/image.jpeg</media_url>#; + + 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('confirmed'); + $problem->update; + + my $update = Open311::GetServiceRequestUpdates->new( system_user => $user ); + $update->update_comments( $o, $bodies{2482} ); + + is $problem->comments->count, 1, 'comment count'; + my $c = $problem->comments->first; + is $c->external_id, 638344; + is $c->photo, '74e3362283b6ef0c48686fb0e161da4043bbcc97.jpeg', 'photo exists'; +}; foreach my $test ( { @@ -234,7 +403,8 @@ foreach my $test ( $dt->subtract( minutes => 10 ); my $local_requests_xml = $requests_xml; - my $updated = sprintf( '<updated_datetime>%s</updated_datetime>', $dt ); + my $updated = sprintf( '<updated_datetime>%s</updated_datetime>', DateTime::Format::W3CDTF->format_datetime( $dt ) ); + $local_requests_xml =~ s/UPDATED_DATETIME/$updated/; $local_requests_xml =~ s#<service_request_id>\d+</service_request_id>#<service_request_id>@{[$problem->external_id]}</service_request_id>#; $local_requests_xml =~ s#<service_request_id_ext>\d+</service_request_id_ext>#<service_request_id_ext>@{[$problem->id]}</service_request_id_ext>#; @@ -243,9 +413,8 @@ foreach my $test ( $problem->comments->delete; - my $council_details = { areaid => 2482 }; my $update = Open311::GetServiceRequestUpdates->new( system_user => $user ); - $update->update_comments( $o, $council_details ); + $update->update_comments( $o, $bodies{2482} ); my $comment = $problem->comments->first; is $comment->created, $dt, 'created date set to date from XML'; @@ -273,7 +442,7 @@ my $problem2 = $problem_rs->new( lastupdate => DateTime->now(), anonymous => 1, external_id => $problem->external_id, - council => 2651, + bodies_str => 2651, } ); @@ -312,9 +481,8 @@ for my $test ( my $o = Open311->new( jurisdiction => 'mysociety', endpoint => 'http://example.com', test_mode => 1, test_get_returns => { 'servicerequestupdates.xml' => $local_requests_xml } ); - my $council_details = { areaid => $test->{area_id} }; my $update = Open311::GetServiceRequestUpdates->new( system_user => $user ); - $update->update_comments( $o, $council_details ); + $update->update_comments( $o, $bodies{$test->{area_id}} ); is $problem->comments->count, $test->{p1_comments}, 'comment count for first problem'; is $problem2->comments->count, $test->{p2_comments}, 'comment count for second problem'; @@ -352,8 +520,7 @@ subtest 'using start and end date' => sub { end_date => $end_dt, ); - my $council_details = { areaid => 2482 }; - $update->update_comments( $o, $council_details ); + $update->update_comments( $o, $bodies{2482} ); my $start = $start_dt . ''; my $end = $end_dt . ''; @@ -387,7 +554,7 @@ subtest 'check that existing comments are not duplicated' => sub { $problem->comments->delete; - my $comment = FixMyStreet::App->model('DB::Comment')->new( + my $comment = FixMyStreet::DB->resultset('Comment')->new( { problem => $problem, external_id => 638344, @@ -404,7 +571,8 @@ subtest 'check that existing comments are not duplicated' => sub { is $problem->comments->count, 1, 'one comment before fetching updates'; $requests_xml =~ s/UPDATED_DATETIME2/$dt/; - $requests_xml =~ s/UPDATED_DATETIME/@{[ $comment->confirmed ]}/; + my $confirmed = DateTime::Format::W3CDTF->format_datetime($comment->confirmed); + $requests_xml =~ s/UPDATED_DATETIME/$confirmed/; my $o = Open311->new( jurisdiction => 'mysociety', endpoint => 'http://example.com', test_mode => 1, test_get_returns => { 'servicerequestupdates.xml' => $requests_xml } ); @@ -412,30 +580,29 @@ subtest 'check that existing comments are not duplicated' => sub { system_user => $user, ); - my $council_details = { areaid => 2482 }; - $update->update_comments( $o, $council_details ); + $update->update_comments( $o, $bodies{2482} ); $problem->discard_changes; is $problem->comments->count, 2, 'two comments after fetching updates'; - $update->update_comments( $o, $council_details ); + $update->update_comments( $o, $bodies{2482} ); $problem->discard_changes; is $problem->comments->count, 2, 're-fetching updates does not add comments'; $problem->comments->delete; - $update->update_comments( $o, $council_details ); + $update->update_comments( $o, $bodies{2482} ); $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 ), + dt1 => $dt->clone->subtract( hours => 1 ), dt2 => $dt, }, { desc => 'check that old comments do not change problem status', - dt1 => $dt->subtract( hours => 2 ), + dt1 => $dt->clone->subtract( minutes => 90 ), dt2 => $dt, } ) { @@ -461,7 +628,7 @@ foreach my $test ( { $problem->comments->delete; $problem->state( 'confirmed' ); - $problem->lastupdate( $dt->subtract( hours => 3 ) ); + $problem->lastupdate( $dt->clone->subtract( hours => 3 ) ); $problem->update; $requests_xml =~ s/UPDATED_DATETIME/$test->{dt1}/; @@ -473,8 +640,7 @@ foreach my $test ( { system_user => $user, ); - my $council_details = { areaid => 2482 }; - $update->update_comments( $o, $council_details ); + $update->update_comments( $o, $bodies{2482} ); $problem->discard_changes; is $problem->comments->count, 2, 'two comments after fetching updates'; @@ -484,10 +650,22 @@ foreach my $test ( { 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, } ) { @@ -506,15 +684,17 @@ foreach my $test ( { $problem->comments->delete; $problem->state( 'confirmed' ); - $problem->lastupdate( $dt->subtract( hours => 3 ) ); + $problem->lastupdate( $dt->clone->subtract( hours => 3 ) ); $problem->update; - my $alert = FixMyStreet::App->model('DB::Alert')->find_or_create( { - alert_type => 'new_updates', - parameter => $problem->id, - confirmed => 1, - user_id => $problem->user->id, - } ); + my @alerts = map { + my $alert = FixMyStreet::DB->resultset('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/; @@ -525,25 +705,26 @@ foreach my $test ( { suppress_alerts => $test->{suppress_alerts}, ); - my $council_details = { areaid => 2482 }; - $update->update_comments( $o, $council_details ); + $update->update_comments( $o, $bodies{2482} ); $problem->discard_changes; - my $alerts_sent = FixMyStreet::App->model('DB::AlertSent')->search( + my $alerts_sent = FixMyStreet::DB->resultset('AlertSent')->search( { - alert_id => $alert->id, + alert_id => [ map $_->id, @alerts ], parameter => $problem->comments->first->id, } ); if ( $test->{suppress_alerts} ) { - ok $alerts_sent->count(), 'alerts suppressed'; + is $alerts_sent->count(), $test->{num_alerts}, 'alerts suppressed'; } else { is $alerts_sent->count(), 0, 'alerts not suppressed'; } $alerts_sent->delete; - $alert->delete; + for my $alert (@alerts) { + $alert->delete; + } } } diff --git a/t/open311/getupdates.t b/t/open311/getupdates.t new file mode 100644 index 000000000..0e31db482 --- /dev/null +++ b/t/open311/getupdates.t @@ -0,0 +1,260 @@ +#!/usr/bin/env perl + +use strict; +use warnings; +use Test::More; +use URI::Split qw(uri_split); + +use FixMyStreet; +use FixMyStreet::DB; + +use_ok( 'Open311::GetUpdates' ); +use_ok( 'Open311' ); + +my $user = FixMyStreet::DB->resultset('User')->find_or_create( + { + email => 'system_user@example.com' + } +); + +my $body = FixMyStreet::DB->resultset('Body')->new( { + name => 'Test Body', +} ); + +my $updates = Open311::GetUpdates->new( system_user => $user ); +ok $updates, 'created object'; + +my $requests_xml = qq{<?xml version="1.0" encoding="utf-8"?> +<service_requests> +<request> +<service_request_id>638344</service_request_id> +<status>open</status> +<status_notes>This is a note.</status_notes> +<service_name>Sidewalk and Curb Issues</service_name> +<service_code>006</service_code> +<description></description> +<agency_responsible></agency_responsible> +<service_notice></service_notice> +<requested_datetime>2010-04-14T06:37:38-08:00</requested_datetime> +UPDATED_DATETIME +<expected_datetime>2010-04-15T06:37:38-08:00</expected_datetime> +<lat>37.762221815</lat> +<long>-122.4651145</long> +</request> +</service_requests> +}; + +my $problem_rs = FixMyStreet::DB->resultset('Problem'); +my $problem = $problem_rs->new( + { + postcode => 'EH99 1SP', + latitude => 1, + longitude => 1, + areas => 1, + title => '', + detail => '', + used_map => 1, + name => '', + state => 'confirmed', + cobrand => 'default', + user => $user, + created => DateTime->now()->subtract( days => 1 ), + lastupdate => DateTime->now()->subtract( days => 1 ), + anonymous => 1, + external_id => 638344, + } +); + +$problem->insert; + +for my $test ( + { + desc => 'element missing', + updated_datetime => '', + comment_count => 0, + }, + { + desc => 'empty element', + updated_datetime => '<updated_datetime />', + comment_count => 0, + }, + { + desc => 'element with no content', + updated_datetime => '<updated_datetime></updated_datetime>', + comment_count => 0, + }, + { + desc => 'element with old content', + updated_datetime => sprintf( '<updated_datetime>%s</updated_datetime>', DateTime->now->subtract( days => 3 ) ), + comment_count => 0, + }, + { + desc => 'element with new content', + updated_datetime => sprintf( '<updated_datetime>%s</updated_datetime>', DateTime->now ), + comment_count => 1, + }, +) { + subtest $test->{desc} => sub { + $problem->comments->delete; + $problem->lastupdate(DateTime->now()->subtract( days => 1 ) ), + $problem->update; + + 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 => { 'requests.xml' => $local_requests_xml } ); + + ok $updates->update_reports( [ 638344 ], $o, $body ), 'Updated reports'; + my @parts = uri_split($o->test_uri_used); + is $parts[2], '/requests.xml', 'path matches'; + my @qs = sort split '&', $parts[3]; + is_deeply(\@qs, [ 'jurisdiction_id=mysociety', 'service_request_id=638344' ], 'query string matches'); + + is $problem->comments->count, $test->{comment_count}, 'added a comment'; + }; +} + +$requests_xml = qq{<?xml version="1.0" encoding="utf-8"?> +<service_requests> +<request> +<service_request_id>638344</service_request_id> +<status>open</status> +<status_notes>This is a note.</status_notes> +<service_name>Sidewalk and Curb Issues</service_name> +<service_code>006</service_code> +<description></description> +<agency_responsible></agency_responsible> +<service_notice></service_notice> +<requested_datetime>2010-04-14T06:37:38-08:00</requested_datetime> +<updated_datetime>UPDATED_DATETIME</updated_datetime> +<expected_datetime>2010-04-15T06:37:38-08:00</expected_datetime> +<lat>37.762221815</lat> +<long>-122.4651145</long> +</request> +<request> +<service_request_id>638345</service_request_id> +<status>open</status> +<status_notes>This is a for a different issue.</status_notes> +<service_name>Sidewalk and Curb Issues</service_name> +<service_code>006</service_code> +<description></description> +<agency_responsible></agency_responsible> +<service_notice></service_notice> +<requested_datetime>2010-04-14T06:37:38-08:00</requested_datetime> +<updated_datetime>UPDATED_DATETIME2</updated_datetime> +<expected_datetime>2010-04-15T06:37:38-08:00</expected_datetime> +<lat>37.762221815</lat> +<long>-122.4651145</long> +</request> +</service_requests> +}; + +my $problem2 = $problem_rs->create( + { + postcode => 'EH99 1SP', + latitude => 1, + longitude => 1, + areas => 1, + title => '', + detail => '', + used_map => 1, + name => '', + state => 'confirmed', + cobrand => 'default', + user => $user, + created => DateTime->now()->subtract( days => 1 ), + lastupdate => DateTime->now()->subtract( days => 1 ), + anonymous => 1, + external_id => 638345, + } +); + +$problem->comments->delete; +subtest 'update with two requests' => sub { + $problem->comments->delete; + $problem->lastupdate(DateTime->now()->subtract( days => 1 ) ), + + my $date1 = DateTime::Format::W3CDTF->new->format_datetime( DateTime->now() ); + my $date2 = DateTime::Format::W3CDTF->new->format_datetime( DateTime->now->subtract( hours => 1) ); + my $local_requests_xml = $requests_xml; + $local_requests_xml =~ s/UPDATED_DATETIME2/$date2/; + $local_requests_xml =~ s/UPDATED_DATETIME/$date1/; + + my $o = Open311->new( jurisdiction => 'mysociety', endpoint => 'http://example.com', test_mode => 1, test_get_returns => { 'requests.xml' => $local_requests_xml } ); + + ok $updates->update_reports( [ 638344,638345 ], $o, $body ), 'Updated reports'; + my @parts = uri_split($o->test_uri_used); + is $parts[2], '/requests.xml', 'path matches'; + my @qs = sort split '&', $parts[3]; + is_deeply(\@qs, [ 'jurisdiction_id=mysociety', 'service_request_id=638344%2C638345' ], 'query string matches'); + + is $problem->comments->count, 1, 'added a comment to first problem'; + is $problem2->comments->count, 1, 'added a comment to second problem'; +}; + +# No status_notes field now, so that static string in code is used. +$requests_xml = qq{<?xml version="1.0" encoding="utf-8"?> +<service_requests> +<request> +<service_request_id>638346</service_request_id> +<status>closed</status> +<service_name>Sidewalk and Curb Issues</service_name> +<service_code>006</service_code> +<description></description> +<agency_responsible></agency_responsible> +<service_notice></service_notice> +<requested_datetime>2010-04-14T06:37:38-08:00</requested_datetime> +UPDATED_DATETIME +<expected_datetime>2010-04-15T06:37:38-08:00</expected_datetime> +<lat>37.762221815</lat> +<long>-122.4651145</long> +</request> +</service_requests> +}; + +my $problem3 = $problem_rs->create( { + postcode => 'EH99 1SP', + latitude => 1, + longitude => 1, + areas => 1, + title => 'Title', + detail => 'Details', + used_map => 1, + name => '', + state => 'confirmed', + cobrand => 'fixamingata', + user => $user, + created => DateTime->now()->subtract( days => 1 ), + lastupdate => DateTime->now()->subtract( days => 1 ), + anonymous => 1, + external_id => 638346, +} ); + +subtest 'test translation of auto-added comment from old-style Open311 update' => sub { + my $dt = sprintf( '<updated_datetime>%s</updated_datetime>', DateTime->now ); + $requests_xml =~ s/UPDATED_DATETIME/$dt/; + + my $o = Open311->new( jurisdiction => 'mysociety', endpoint => 'http://example.com', test_mode => 1, test_get_returns => { 'requests.xml' => $requests_xml } ); + + FixMyStreet::override_config { + ALLOWED_COBRANDS => [ 'fixamingata' ], + }, sub { + ok $updates->update_reports( [ 638346 ], $o, $body ), 'Updated reports'; + }; + my @parts = uri_split($o->test_uri_used); + is $parts[2], '/requests.xml', 'path matches'; + my @qs = sort split '&', $parts[3]; + is_deeply(\@qs, [ 'jurisdiction_id=mysociety', 'service_request_id=638346' ], 'query string matches'); + + is $problem3->comments->count, 1, 'added a comment'; + is $problem3->comments->first->text, "St\xe4ngd av kommunen", 'correct comment text'; +}; + +END { + if ($user) { + $user->comments->delete; + $user->problems->delete; + $user->delete; + } + done_testing(); +} diff --git a/t/open311/populate-service-list.t b/t/open311/populate-service-list.t index ec6c175f9..606bcbc44 100644 --- a/t/open311/populate-service-list.t +++ b/t/open311/populate-service-list.t @@ -4,42 +4,50 @@ use strict; use warnings; use Test::More; -use FixMyStreet::App; - -use FindBin; -use lib "$FindBin::Bin/../perllib"; -use lib "$FindBin::Bin/../commonlib/perllib"; +use FixMyStreet::DB; use_ok( 'Open311::PopulateServiceList' ); use_ok( 'Open311' ); -my $processor = Open311::PopulateServiceList->new( council_list => [] ); +my $processor = Open311::PopulateServiceList->new(); ok $processor, 'created object'; - +my $body = FixMyStreet::DB->resultset('Body')->find_or_create( { + id => 1, + name => 'Body Numero Uno', +} ); +$body->body_areas->find_or_create({ + area_id => 1 +} ); + +my $BROMLEY = 'Bromley Council'; +my $bromley = FixMyStreet::DB->resultset('Body')->find_or_create( { + id => 2482, + name => $BROMLEY, +} ); +$bromley->update({ name => $BROMLEY }); +$bromley->body_areas->find_or_create({ + area_id => 2482 +} ); subtest 'check basic functionality' => sub { - FixMyStreet::App->model('DB::Contact')->search( { body_id => 1 } )->delete(); + FixMyStreet::DB->resultset('Contact')->search( { body_id => 1 } )->delete(); my $service_list = get_xml_simple_object( get_standard_xml() ); - my $council = FixMyStreet::App->model('DB::Body')->new( { - area_id => 1 - } ); - - my $processor = Open311::PopulateServiceList->new( council_list => [] ); - $processor->_current_council( $council ); + my $processor = Open311::PopulateServiceList->new(); + $processor->_current_body( $body ); $processor->process_services( $service_list ); - my $contact_count = FixMyStreet::App->model('DB::Contact')->search( { body_id => 1 } )->count(); + my $contact_count = FixMyStreet::DB->resultset('Contact')->search( { body_id => 1 } )->count(); is $contact_count, 3, 'correct number of contacts'; }; subtest 'check non open311 contacts marked as deleted' => sub { - FixMyStreet::App->model('DB::Contact')->search( { body_id => 1 } )->delete(); + FixMyStreet::DB->resultset('Contact')->search( { body_id => 1 } )->delete(); - my $contact = FixMyStreet::App->model('DB::Contact')->create( + my $contact = FixMyStreet::DB->resultset('Contact')->create( { body_id => 1, email => 'contact@example.com', @@ -47,32 +55,28 @@ subtest 'check non open311 contacts marked as deleted' => sub { confirmed => 1, deleted => 0, editor => $0, - whenedited => \'ms_current_timestamp()', + whenedited => \'current_timestamp', note => 'test contact', } ); my $service_list = get_xml_simple_object( get_standard_xml() ); - my $council = FixMyStreet::App->model('DB::Body')->new( { - area_id => 1 - } ); - - my $processor = Open311::PopulateServiceList->new( council_list => [] ); - $processor->_current_council( $council ); + my $processor = Open311::PopulateServiceList->new(); + $processor->_current_body( $body ); $processor->process_services( $service_list ); - my $contact_count = FixMyStreet::App->model('DB::Contact')->search( { body_id => 1 } )->count(); + my $contact_count = FixMyStreet::DB->resultset('Contact')->search( { body_id => 1 } )->count(); is $contact_count, 4, 'correct number of contacts'; - $contact_count = FixMyStreet::App->model('DB::Contact')->search( { body_id => 1, deleted => 1 } )->count(); + $contact_count = FixMyStreet::DB->resultset('Contact')->search( { body_id => 1, deleted => 1 } )->count(); is $contact_count, 1, 'correct number of deleted contacts'; }; subtest 'check email changed if matching category' => sub { - FixMyStreet::App->model('DB::Contact')->search( { body_id => 1 } )->delete(); + FixMyStreet::DB->resultset('Contact')->search( { body_id => 1 } )->delete(); - my $contact = FixMyStreet::App->model('DB::Contact')->create( + my $contact = FixMyStreet::DB->resultset('Contact')->create( { body_id => 1, email => '009', @@ -80,7 +84,7 @@ subtest 'check email changed if matching category' => sub { confirmed => 1, deleted => 0, editor => $0, - whenedited => \'ms_current_timestamp()', + whenedited => \'current_timestamp', note => 'test contact', } ); @@ -89,12 +93,8 @@ subtest 'check email changed if matching category' => sub { my $service_list = get_xml_simple_object( get_standard_xml() ); - my $council = FixMyStreet::App->model('DB::Body')->new( { - area_id => 1 - } ); - - my $processor = Open311::PopulateServiceList->new( council_list => [] ); - $processor->_current_council( $council ); + my $processor = Open311::PopulateServiceList->new(); + $processor->_current_body( $body ); $processor->process_services( $service_list ); $contact->discard_changes; @@ -102,14 +102,14 @@ subtest 'check email changed if matching category' => sub { is $contact->confirmed, 1, 'contact still confirmed'; is $contact->deleted, 0, 'contact still not deleted'; - my $contact_count = FixMyStreet::App->model('DB::Contact')->search( { body_id => 1 } )->count(); + my $contact_count = FixMyStreet::DB->resultset('Contact')->search( { body_id => 1 } )->count(); is $contact_count, 3, 'correct number of contacts'; }; subtest 'check category name changed if updated' => sub { - FixMyStreet::App->model('DB::Contact')->search( { body_id => 1 } )->delete(); + FixMyStreet::DB->resultset('Contact')->search( { body_id => 1 } )->delete(); - my $contact = FixMyStreet::App->model('DB::Contact')->create( + my $contact = FixMyStreet::DB->resultset('Contact')->create( { body_id => 1, email => '001', @@ -117,7 +117,7 @@ subtest 'check category name changed if updated' => sub { confirmed => 1, deleted => 0, editor => $0, - whenedited => \'ms_current_timestamp()', + whenedited => \'current_timestamp', note => 'test contact', } ); @@ -126,12 +126,8 @@ subtest 'check category name changed if updated' => sub { my $service_list = get_xml_simple_object( get_standard_xml() ); - my $council = FixMyStreet::App->model('DB::Body')->new( { - area_id => 1 - } ); - - my $processor = Open311::PopulateServiceList->new( council_list => [] ); - $processor->_current_council( $council ); + my $processor = Open311::PopulateServiceList->new(); + $processor->_current_body( $body ); $processor->process_services( $service_list ); $contact->discard_changes; @@ -140,14 +136,14 @@ subtest 'check category name changed if updated' => sub { is $contact->confirmed, 1, 'contact still confirmed'; is $contact->deleted, 0, 'contact still not deleted'; - my $contact_count = FixMyStreet::App->model('DB::Contact')->search( { body_id => 1 } )->count(); + my $contact_count = FixMyStreet::DB->resultset('Contact')->search( { body_id => 1 } )->count(); is $contact_count, 3, 'correct number of contacts'; }; subtest 'check conflicting contacts not changed' => sub { - FixMyStreet::App->model('DB::Contact')->search( { body_id => 1 } )->delete(); + FixMyStreet::DB->resultset('Contact')->search( { body_id => 1 } )->delete(); - my $contact = FixMyStreet::App->model('DB::Contact')->create( + my $contact = FixMyStreet::DB->resultset('Contact')->create( { body_id => 1, email => 'existing@example.com', @@ -155,14 +151,14 @@ subtest 'check conflicting contacts not changed' => sub { confirmed => 1, deleted => 0, editor => $0, - whenedited => \'ms_current_timestamp()', + whenedited => \'current_timestamp', note => 'test contact', } ); ok $contact, 'contact created'; - my $contact2 = FixMyStreet::App->model('DB::Contact')->create( + my $contact2 = FixMyStreet::DB->resultset('Contact')->create( { body_id => 1, email => '001', @@ -170,7 +166,7 @@ subtest 'check conflicting contacts not changed' => sub { confirmed => 1, deleted => 0, editor => $0, - whenedited => \'ms_current_timestamp()', + whenedited => \'current_timestamp', note => 'test contact', } ); @@ -179,12 +175,8 @@ subtest 'check conflicting contacts not changed' => sub { my $service_list = get_xml_simple_object( get_standard_xml() ); - my $council = FixMyStreet::App->model('DB::Body')->new( { - area_id => 1 - } ); - - my $processor = Open311::PopulateServiceList->new( council_list => [] ); - $processor->_current_council( $council ); + my $processor = Open311::PopulateServiceList->new(); + $processor->_current_body( $body ); $processor->process_services( $service_list ); $contact->discard_changes; @@ -199,12 +191,12 @@ subtest 'check conflicting contacts not changed' => sub { is $contact2->confirmed, 1, 'second contact contact still confirmed'; is $contact2->deleted, 0, 'second contact contact still not deleted'; - my $contact_count = FixMyStreet::App->model('DB::Contact')->search( { body_id => 1 } )->count(); + my $contact_count = FixMyStreet::DB->resultset('Contact')->search( { body_id => 1 } )->count(); is $contact_count, 4, 'correct number of contacts'; }; subtest 'check meta data population' => sub { - my $processor = Open311::PopulateServiceList->new( council_list => [] ); + my $processor = Open311::PopulateServiceList->new(); my $meta_xml = '<?xml version="1.0" encoding="utf-8"?> <service_definition> @@ -223,7 +215,7 @@ subtest 'check meta data population' => sub { </service_definition> '; - my $contact = FixMyStreet::App->model('DB::Contact')->find_or_create( + my $contact = FixMyStreet::DB->resultset('Contact')->find_or_create( { body_id => 1, email => '001', @@ -231,7 +223,7 @@ subtest 'check meta data population' => sub { confirmed => 1, deleted => 0, editor => $0, - whenedited => \'ms_current_timestamp()', + whenedited => \'current_timestamp', note => 'test contact', } ); @@ -243,12 +235,8 @@ subtest 'check meta data population' => sub { test_get_returns => { 'services/100.xml' => $meta_xml } ); - my $council = FixMyStreet::App->model('DB::Body')->new( { - area_id => 2482 - } ); - $processor->_current_open311( $o ); - $processor->_current_council( $council ); + $processor->_current_body( $bromley ); $processor->_current_service( { service_code => 100 } ); $processor->_add_meta_to_contact( $contact ); @@ -266,14 +254,14 @@ subtest 'check meta data population' => sub { $contact->discard_changes; - is_deeply $contact->extra, $extra, 'meta data saved'; + is_deeply $contact->get_extra_fields, $extra, 'meta data saved'; }; for my $test ( { desc => 'check meta data added to existing contact', has_meta => 1, - orig_meta => undef, + orig_meta => [], end_meta => [ { variable => 'true', code => 'type', @@ -344,7 +332,7 @@ for my $test ( { desc => 'check meta data removed', has_meta => 0, - end_meta => undef, + end_meta => [], orig_meta => [ { variable => 'true', code => 'type', @@ -375,8 +363,8 @@ for my $test ( { desc => 'check empty meta data handled', has_meta => 1, - orig_meta => undef, - end_meta => undef, + orig_meta => [], + end_meta => [], meta_xml => '<?xml version="1.0" encoding="utf-8"?> <service_definition> <service_code>100</service_code> @@ -387,7 +375,7 @@ for my $test ( }, ) { subtest $test->{desc} => sub { - my $processor = Open311::PopulateServiceList->new( council_list => [] ); + my $processor = Open311::PopulateServiceList->new(); my $services_xml = '<?xml version="1.0" encoding="utf-8"?> <services> @@ -407,7 +395,7 @@ for my $test ( $services_xml =~ s/metadata>false/metadata>true/ms; } - my $contact = FixMyStreet::App->model('DB::Contact')->find_or_create( + my $contact = FixMyStreet::DB->resultset('Contact')->find_or_create( { body_id => 1, email => '100', @@ -415,12 +403,13 @@ for my $test ( confirmed => 1, deleted => 0, editor => $0, - whenedited => \'ms_current_timestamp()', + whenedited => \'current_timestamp', note => 'test contact', } ); - $contact->update( { extra => $test->{orig_meta} } ); + $contact->set_extra_fields(@{$test->{orig_meta}}); + $contact->update; my $o = Open311->new( jurisdiction => 'mysociety', @@ -430,25 +419,20 @@ for my $test ( ); my $service_list = get_xml_simple_object( $services_xml ); - $service_list = { service => [ $service_list->{ service } ] }; - - my $council = FixMyStreet::App->model('DB::Body')->new( { - area_id => 1 - } ); $processor->_current_open311( $o ); - $processor->_current_council( $council ); + $processor->_current_body( $body ); $processor->process_services( $service_list ); $contact->discard_changes; - is_deeply $contact->extra, $test->{end_meta}, 'meta data saved'; + is_deeply $contact->get_extra_fields, $test->{end_meta}, 'meta data saved'; }; } subtest 'check attribute ordering' => sub { - my $processor = Open311::PopulateServiceList->new( council_list => [] ); + my $processor = Open311::PopulateServiceList->new(); my $meta_xml = '<?xml version="1.0" encoding="utf-8"?> <service_definition> @@ -485,7 +469,7 @@ subtest 'check attribute ordering' => sub { </service_definition> '; - my $contact = FixMyStreet::App->model('DB::Contact')->find_or_create( + my $contact = FixMyStreet::DB->resultset('Contact')->find_or_create( { body_id => 1, email => '001', @@ -493,7 +477,7 @@ subtest 'check attribute ordering' => sub { confirmed => 1, deleted => 0, editor => $0, - whenedited => \'ms_current_timestamp()', + whenedited => \'current_timestamp', note => 'test contact', } ); @@ -505,12 +489,8 @@ subtest 'check attribute ordering' => sub { test_get_returns => { 'services/100.xml' => $meta_xml } ); - my $council = FixMyStreet::App->model('DB::Body')->new( { - area_id => 1 - } ); - $processor->_current_open311( $o ); - $processor->_current_council( $council ); + $processor->_current_body( $body ); $processor->_current_service( { service_code => 100 } ); $processor->_add_meta_to_contact( $contact ); @@ -550,11 +530,11 @@ subtest 'check attribute ordering' => sub { $contact->discard_changes; - is_deeply $contact->extra, $extra, 'meta data re-ordered correctly'; + is_deeply $contact->get_extra_fields, $extra, 'meta data re-ordered correctly'; }; subtest 'check bromely skip code' => sub { - my $processor = Open311::PopulateServiceList->new( council_list => [] ); + my $processor = Open311::PopulateServiceList->new(); my $meta_xml = '<?xml version="1.0" encoding="utf-8"?> <service_definition> @@ -591,7 +571,7 @@ subtest 'check bromely skip code' => sub { </service_definition> '; - my $contact = FixMyStreet::App->model('DB::Contact')->find_or_create( + my $contact = FixMyStreet::DB->resultset('Contact')->find_or_create( { body_id => 1, email => '001', @@ -599,7 +579,7 @@ subtest 'check bromely skip code' => sub { confirmed => 1, deleted => 0, editor => $0, - whenedited => \'ms_current_timestamp()', + whenedited => \'current_timestamp', note => 'test contact', } ); @@ -611,12 +591,8 @@ subtest 'check bromely skip code' => sub { test_get_returns => { 'services/100.xml' => $meta_xml } ); - my $council = FixMyStreet::App->model('DB::Body')->new( { - area_id => 2482 - } ); - $processor->_current_open311( $o ); - $processor->_current_council( $council ); + $processor->_current_body( $bromley ); $processor->_current_service( { service_code => 100 } ); $processor->_add_meta_to_contact( $contact ); @@ -634,11 +610,9 @@ subtest 'check bromely skip code' => sub { $contact->discard_changes; - is_deeply $contact->extra, $extra, 'only non std bromley meta data saved'; + is_deeply $contact->get_extra_fields, $extra, 'only non std bromley meta data saved'; - $council->area_id(1); - - $processor->_current_council( $council ); + $processor->_current_body( $body ); $processor->_add_meta_to_contact( $contact ); $extra = [ @@ -676,7 +650,7 @@ subtest 'check bromely skip code' => sub { $contact->discard_changes; - is_deeply $contact->extra, $extra, 'all meta data saved for non bromley'; + is_deeply $contact->get_extra_fields, $extra, 'all meta data saved for non bromley'; }; sub get_standard_xml { @@ -715,17 +689,7 @@ sub get_standard_xml { sub get_xml_simple_object { my $xml = shift; - - my $simple = XML::Simple->new(); - my $obj; - - eval { - $obj = $simple->XMLin( $xml ); - }; - - die $@ if $@; - - return $obj; + return Open311->_get_xml_object($xml); } done_testing(); diff --git a/t/sendreport/open311.t b/t/sendreport/open311.t new file mode 100644 index 000000000..c40b64d12 --- /dev/null +++ b/t/sendreport/open311.t @@ -0,0 +1,50 @@ +use strict; +use warnings; +use Test::More; + +use CGI::Simple; +use FixMyStreet::TestMech; +my $mech = FixMyStreet::TestMech->new; + +my $user = $mech->create_user_ok( 'eh@example.com' ); +my $body = $mech->create_body_ok( 2342, 'East Hertfordshire Council'); +my $contact = $mech->create_contact_ok( body_id => $body->id, category => 'Potholes', email => 'POT' ); +$contact->set_extra_fields( + { code => 'easting', datatype => 'number' }, + { code => 'northing', datatype => 'number' }, + { code => 'fixmystreet_id', datatype => 'number' }, +); +$contact->update; + +my ($report) = $mech->create_problems_for_body( 1, $body->id, 'Test', { + cobrand => 'fixmystreet', + category => 'Potholes', + user => $user, +}); + +subtest 'testing Open311 behaviour', sub { + $body->update( { send_method => 'Open311', endpoint => 'http://endpoint.example.com', jurisdiction => 'FMS', api_key => 'test' } ); + my $test_data; + FixMyStreet::override_config { + STAGING_FLAGS => { send_reports => 1 }, + ALLOWED_COBRANDS => [ 'fixmystreet' ], + }, sub { + $test_data = FixMyStreet::DB->resultset('Problem')->send_reports(); + }; + $report->discard_changes; + ok $report->whensent, 'Report marked as sent'; + is $report->send_method_used, 'Open311', 'Report sent via Open311'; + is $report->external_id, 248, 'Report has right external ID'; + + my $req = $test_data->{test_req_used}; + my $c = CGI::Simple->new($req->content); + is $c->param('attribute[easting]'), 529025, 'Request had easting'; + is $c->param('attribute[northing]'), 179716, 'Request had northing'; + is $c->param('attribute[fixmystreet_id]'), $report->id, 'Request had correct ID'; + is $c->param('jurisdiction_id'), 'FMS', 'Request had correct jurisdiction'; +}; + +# Clean up +$mech->delete_user($user); +$mech->delete_body($body); +done_testing(); @@ -4,9 +4,8 @@ use strict; use warnings; use Test::More; -use FindBin; -use lib "$FindBin::Bin/../perllib"; -use lib "$FindBin::Bin/../commonlib/perllib"; +use mySociety::Locale; +mySociety::Locale::gettext_domain('FixMyStreet'); use Utils; @@ -38,9 +37,14 @@ foreach my $test (@convert_en_to_latlon_tests) { [ Utils::convert_en_to_latlon_truncated( $e, $n ) ], # [ $lat, $lon ], # "convert ($e,$n) to ($lat,$lon)"; + is_deeply + [ Utils::convert_latlon_to_en( $lat, $lon ) ], + [ $e, $n ], + "convert ($lat,$lon) to ($e,$n)"; } my @cleanup_tests = ( + [ '', '', '' ], [ 'dog shit', 'Dog poo', 'dog poo' ], [ 'dog shit', 'Dog poo', 'with spaces' ], [ 'dog shite', 'Dog poo', 'with extra e' ], @@ -61,4 +65,44 @@ foreach my $test ( @cleanup_tests ) { is Utils::cleanup_text( "This has new\n\n\nlines in it", { allow_multiline => 1 } ), "This has new\n\nLines in it", 'new lines allowed'; + +is Utils::prettify_dt(), "[unknown time]"; +my $dt = DateTime->now; +is Utils::prettify_dt($dt), $dt->strftime("%H:%M today"); + +# Same week test +if ($dt->day_of_week == 7) { # Sunday + $dt = DateTime->now->add(days => 1); +} else { + $dt = DateTime->now->subtract(days => 1); +} +is Utils::prettify_dt($dt), $dt->strftime("%H:%M, %A"); + +if ($dt->month == 1) { # January + $dt = DateTime->now->add(days => 30); +} else { + $dt = DateTime->now->subtract(days => 30); +} +is Utils::prettify_dt($dt), $dt->strftime("%H:%M, %A %e %B %Y"); +is Utils::prettify_dt($dt, "date"), $dt->strftime("%A %e %B %Y"); +is Utils::prettify_dt($dt, "zurich"), $dt->strftime("%H:%M, %e. %B %Y"); +is Utils::prettify_dt($dt, "short"), $dt->strftime("%H:%M, %e %b %Y"); +is Utils::prettify_dt($dt, 1), $dt->strftime("%H:%M, %e %b %Y"); +$dt = DateTime->now->subtract(days => 400); +is Utils::prettify_dt($dt), $dt->strftime("%H:%M, %a %e %B %Y"); + +is Utils::prettify_duration(12*5*7*86400+3600+60+1, 'year'), '1 year'; +is Utils::prettify_duration(25*5*7*86400+3600+60+1, 'year'), '2 years'; +is Utils::prettify_duration(5*7*86400+3600+60+1, 'month'), '1 month'; +is Utils::prettify_duration(7*86400+3600+60+1, 'week'), '1 week'; +is Utils::prettify_duration(86400+3600+60+1, 'day'), '1 day'; +is Utils::prettify_duration(86400+3600+60+1, 'hour'), '1 day, 1 hour'; +is Utils::prettify_duration(86400+3600+60+1, 'minute'), '1 day, 1 hour, 1 minute'; +is Utils::prettify_duration(20, 'minute'), 'less than a minute'; +# prettify_duration should choose a $nearest sensibly if it's not given +is Utils::prettify_duration(12*5*7*86400+3600+60+1), '1 year'; +is Utils::prettify_duration(7*86400+3600+60+1), '1 week'; +is Utils::prettify_duration(14*86400+3600+60+1), '2 weeks'; +is Utils::prettify_duration(1800), '30 minutes'; + done_testing(); diff --git a/t/utils/email.t b/t/utils/email.t new file mode 100644 index 000000000..9e556a865 --- /dev/null +++ b/t/utils/email.t @@ -0,0 +1,37 @@ +#!/usr/bin/env perl + +use strict; +use warnings; +use Test::More; +use Test::MockModule; + +use Utils::Email; + +my $resolver = Test::MockModule->new('Net::DNS::Resolver'); +$resolver->mock('send', sub { + my ($self, $domain, $type) = @_; + my @rrs; + is $type, 'TXT'; + if ($domain eq '_dmarc.yahoo.com') { + @rrs = ( + Net::DNS::RR->new(name => '_dmarc.yahoo.com', type => 'TXT', txtdata => 'p=reject'), + Net::DNS::RR->new(name => '_dmarc.yahoo.com', type => 'A'), + ); + } elsif ($domain eq 'cname.example.com') { + @rrs = Net::DNS::RR->new(name => 'cname.example.com', type => 'TXT', txtdata => 'p=none'); + } else { + @rrs = Net::DNS::RR->new(name => '_dmarc.example.net', type => 'CNAME', cname => 'cname.example.com'); + } + my $pkt = Net::DNS::Packet->new; + push @{$pkt->{answer}}, @rrs; + return $pkt; +}); + +is Utils::Email::test_dmarc('BAD'), undef; +is Utils::Email::test_dmarc('test@yahoo.com'), 1; +is Utils::Email::test_dmarc('test@example.net'), undef; + +is Utils::Email::same_domain(['test@example.net', ''], [ ['to@example.net', ''], ['to@example.com', ''] ]), 1; +is Utils::Email::same_domain(['test@example.org', ''], [ ['to@example.net', ''] ]), ''; + +done_testing(); |