diff options
Diffstat (limited to 't')
-rw-r--r-- | t/Mock/MapIt.pm | 1 | ||||
-rw-r--r-- | t/Mock/OpenIDConnect.pm | 48 | ||||
-rw-r--r-- | t/app/controller/admin/report_edit.t | 20 | ||||
-rw-r--r-- | t/app/controller/auth_social.t | 68 | ||||
-rw-r--r-- | t/app/controller/moderate.t | 3 | ||||
-rw-r--r-- | t/cobrand/get_body_sender.t | 18 | ||||
-rw-r--r-- | t/cobrand/hackney.t | 292 | ||||
-rw-r--r-- | t/email.t | 6 |
8 files changed, 429 insertions, 27 deletions
diff --git a/t/Mock/MapIt.pm b/t/Mock/MapIt.pm index b54ba0ddb..ed95e71fc 100644 --- a/t/Mock/MapIt.pm +++ b/t/Mock/MapIt.pm @@ -44,6 +44,7 @@ my @PLACES = ( [ 'NN1 2NS', 52.238301, -0.889992, 2234, 'Northamptonshire County Council', 'CTY', 2397, 'Northampton Borough Council', 'DIS' ], [ '?', 52.238827, -0.894970, 2234, 'Northamptonshire County Council', 'CTY', 2397, 'Northampton Borough Council', 'DIS' ], [ '?', 52.23025, -1.015826, 2234, 'Northamptonshire County Council', 'CTY', 2397, 'Northampton Borough Council', 'DIS' ], + [ 'E8 1DY', 51.552267, -0.063316, 2508, 'Hackney Borough Council', 'LBO' ], [ 'TW7 5JN', 51.482286, -0.328163, 2483, 'Hounslow Borough Council', 'LBO' ], [ '?', 51.48111, -0.327219, 2483, 'Hounslow Borough Council', 'LBO' ], [ '?', 51.482045, -0.327219, 2483, 'Hounslow Borough Council', 'LBO' ], diff --git a/t/Mock/OpenIDConnect.pm b/t/Mock/OpenIDConnect.pm index ba7d03b1d..61a67f329 100644 --- a/t/Mock/OpenIDConnect.pm +++ b/t/Mock/OpenIDConnect.pm @@ -27,6 +27,11 @@ sub dispatch_request { return [ 200, [ 'Content-Type' => 'text/html' ], [ 'OpenID Connect login page' ] ]; }, + sub (GET + /oauth2/v2.0/authorize_google + ?*) { + my ($self) = @_; + return [ 200, [ 'Content-Type' => 'text/html' ], [ 'OpenID Connect login page' ] ]; + }, + sub (GET + /oauth2/v2.0/logout + ?*) { my ($self) = @_; return [ 200, [ 'Content-Type' => 'text/html' ], [ 'OpenID Connect logout page' ] ]; @@ -72,6 +77,49 @@ sub dispatch_request { my $json = $self->json->encode($data); return [ 200, [ 'Content-Type' => 'application/json' ], [ $json ] ]; }, + + sub (POST + /oauth2/v2.0/token_google + ?*) { + my ($self) = @_; + my $header = { + typ => "JWT", + alg => "RS256", + kid => "XXXfakeKEY1234", + }; + my $now = DateTime->now->epoch; + my $payload = { + exp => $now + 3600, + nbf => $now, + locale => 'en-GB', + ver => "1.0", + iss => 'https://accounts.google.com', + sub => "my_google_user_id", + aud => "example_client_id", + iat => $now, + auth_time => $now, + given_name => "Andy", + family_name => "Dwyer", + name => "Andy Dwyer", + nonce => 'MyAwesomeRandomValue', + hd => 'example.org', + }; + $payload->{email} = 'pkg-tappcontrollerauth_socialt-oidc_google@example.org' if $self->returns_email; + $payload->{email_verified} = JSON->true if $self->returns_email; + my $signature = "dummy"; + my $id_token = join(".", ( + encode_base64($self->json->encode($header), ''), + encode_base64($self->json->encode($payload), ''), + encode_base64($signature, '') + )); + my $data = { + id_token => $id_token, + token_type => "Bearer", + not_before => $now, + id_token_expires_in => 3600, + profile_info => encode_base64($self->json->encode({}), ''), + }; + my $json = $self->json->encode($data); + return [ 200, [ 'Content-Type' => 'application/json' ], [ $json ] ]; + }, } __PACKAGE__->run_if_script; diff --git a/t/app/controller/admin/report_edit.t b/t/app/controller/admin/report_edit.t index 01f091412..f8101ab76 100644 --- a/t/app/controller/admin/report_edit.t +++ b/t/app/controller/admin/report_edit.t @@ -686,16 +686,28 @@ subtest "Test display of fields extra data" => sub { $mech->get_ok("/admin/report_edit/$report_id"); $mech->content_contains('Extra data: No'); - $report->push_extra_fields( { - name => 'report_url', - value => 'http://example.com', - }); + $report->push_extra_fields( + { + name => 'report_url', + value => 'http://example.com', + }, + { + name => 'sent_to', + value => [ 'onerecipient@example.org' ], + }, + { + name => 'sent_too', + value => [ 'onemorerecipient@example.org', 'another@example.org' ], + }, + ); $report->update; $report->discard_changes; $mech->get_ok("/admin/report_edit/$report_id"); $mech->content_contains('report_url</strong>: http://example.com'); + $mech->content_contains('sent_to</strong>: onerecipient@example.org'); + $mech->content_contains('sent_too</strong>: onemorerecipient@example.org, another@example.org'); $report->set_extra_fields( { description => 'Report URL', diff --git a/t/app/controller/auth_social.t b/t/app/controller/auth_social.t index 200863029..1f6889dcc 100644 --- a/t/app/controller/auth_social.t +++ b/t/app/controller/auth_social.t @@ -15,9 +15,12 @@ FixMyStreet::App->log->disable('info'); END { FixMyStreet::App->log->enable('info'); } my $body = $mech->create_body_ok(2504, 'Westminster City Council'); +my $body2 = $mech->create_body_ok(2508, 'Hackney Council'); my ($report) = $mech->create_problems_for_body(1, $body->id, 'My Test Report'); my $test_email = $report->user->email; +my ($report2) = $mech->create_problems_for_body(1, $body2->id, 'My Test Report'); +my $test_email2 = $report->user->email; my $contact = $mech->create_contact_ok( body_id => $body->id, category => 'Damaged bin', email => 'BIN', @@ -26,11 +29,21 @@ my $contact = $mech->create_contact_ok( { code => 'bin_service', description => 'Service needed', required => 'False' }, ] ); +$mech->create_contact_ok( + body_id => $body2->id, category => 'Damaged bin', email => 'BIN', + extra => [ + { code => 'bin_type', description => 'Type of bin', required => 'True' }, + { code => 'bin_service', description => 'Service needed', required => 'False' }, + ] +); # Two options, incidentally, so that the template "Only one option, select it" # code doesn't kick in and make the tests pass my $contact2 = $mech->create_contact_ok( body_id => $body->id, category => 'Whatever', email => 'WHATEVER', ); +$mech->create_contact_ok( + body_id => $body2->id, category => 'Whatever', email => 'WHATEVER', +); my $resolver = Test::MockModule->new('Email::Valid'); my $social = Test::MockModule->new('FixMyStreet::App::Controller::Auth::Social'); @@ -88,6 +101,44 @@ for my $test ( user_extras => [ [westminster_account_id => "1c304134-ef12-c128-9212-123908123901"], ], +}, { + type => 'oidc', + config => { + ALLOWED_COBRANDS => 'hackney', + MAPIT_URL => 'http://mapit.uk/', + COBRAND_FEATURES => { + anonymous_account => { + hackney => 'test', + }, + oidc_login => { + hackney => { + client_id => 'example_client_id', + secret => 'example_secret_key', + auth_uri => 'http://oidc.example.org/oauth2/v2.0/authorize_google', + token_uri => 'http://oidc.example.org/oauth2/v2.0/token_google', + allowed_domains => [ 'example.org' ], + } + }, + do_not_reply_email => { + hackney => 'fms-hackney-DO-NOT-REPLY@hackney-example.com', + }, + verp_email_domain => { + hackney => 'hackney-example.com', + }, + } + }, + email => $mech->uniquify_email('oidc_google@example.org'), + uid => "hackney:example_client_id:my_google_user_id", + mock => 't::Mock::OpenIDConnect', + mock_hosts => ['oidc.example.org'], + host => 'oidc.example.org', + error_callback => '/auth/OIDC?error=ERROR', + success_callback => '/auth/OIDC?code=response-code&state=login', + redirect_pattern => qr{oidc\.example\.org/oauth2/v2\.0/authorize_google}, + pc => 'E8 1DY', + # Need to use a different report that's within Hackney + report => $report2, + report_email => $test_email2, } ) { @@ -100,6 +151,7 @@ for my $state ( 'refused', 'no email', 'existing UID', 'okay' ) { next if $page eq 'update' && !$test->{update}; subtest "test $test->{type} '$state' login for page '$page'" => sub { + my $test_report = $test->{report} || $report; # 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; @@ -115,9 +167,9 @@ for my $state ( 'refused', 'no email', 'existing UID', 'okay' ) { $mech->delete_user($test->{email}); } if ($page eq 'my' && $state eq 'existing UID') { - $report->update({ user_id => FixMyStreet::DB->resultset( 'User' )->find( { email => $test->{email} } )->id }); + $test_report->update({ user_id => FixMyStreet::DB->resultset( 'User' )->find( { email => $test->{email} } )->id }); } else { - $report->update({ user_id => FixMyStreet::DB->resultset( 'User' )->find( { email => $test_email } )->id }); + $test_report->update({ user_id => FixMyStreet::DB->resultset( 'User' )->find( { email => ($report->{test_email} || $test_email) } )->id }); } # Set up a mock to catch (most, see below) requests to the OAuth API @@ -139,7 +191,7 @@ for my $state ( 'refused', 'no email', 'existing UID', 'okay' ) { $mech->get_ok('/my'); } elsif ($page eq 'report') { $mech->get_ok('/'); - $mech->submit_form_ok( { with_fields => { pc => 'SW1A1AA' } }, "submit location" ); + $mech->submit_form_ok( { with_fields => { pc => $test->{pc} || 'SW1A1AA' } }, "submit location" ); $mech->follow_link_ok( { text_regex => qr/skip this step/i, }, "follow 'skip this step' link" ); $mech->submit_form(with_fields => { category => 'Damaged bin', @@ -150,7 +202,7 @@ for my $state ( 'refused', 'no email', 'existing UID', 'okay' ) { bin_type => 'Salt bin', }; } else { - $mech->get_ok('/report/' . $report->id); + $mech->get_ok('/report/' . $test_report->id); $fields = { update => 'Test update', }; @@ -243,17 +295,17 @@ for my $state ( 'refused', 'no email', 'existing UID', 'okay' ) { } } if ($state eq 'existing UID') { - my $report_id = $report->id; - $mech->content_contains( $report->title ); + my $report_id = $test_report->id; + $mech->content_contains( $test_report->title ); $mech->content_contains( "/report/$report_id" ); } - if ($test->{type} eq 'oidc') { + if ($test->{type} eq 'oidc' && $test->{password_change_pattern}) { ok $mech->find_link( text => 'Change password', url_regex => $test->{password_change_pattern} ); } } $mech->get('/auth/sign_out'); - if ($test->{type} eq 'oidc' && $state ne 'refused' && $state ne 'no email') { + if ($test->{type} eq 'oidc' && $test->{logout_redirect_pattern} && $state ne 'refused' && $state ne 'no email') { # XXX the 'no email' situation is skipped because of some confusion # with the hosts/sessions that I've not been able to get to the bottom of. # The code does behave as expected when testing manually, however. diff --git a/t/app/controller/moderate.t b/t/app/controller/moderate.t index 8e84bd392..43ae1c980 100644 --- a/t/app/controller/moderate.t +++ b/t/app/controller/moderate.t @@ -51,7 +51,7 @@ sub create_report { longitude => '0.007831', user_id => $user2->id, photo => '74e3362283b6ef0c48686fb0e161da4043bbcc97.jpeg', - extra => { moon => 'waxing full' }, + extra => { moon => 'waxing full', sent_to => [ 'authority@example.org' ] }, }); } my $report = create_report(); @@ -115,6 +115,7 @@ subtest 'Problem moderation' => sub { }}); $mech->base_like( qr{\Q$REPORT_URL\E} ); $mech->content_like(qr/Moderated by Bromley Council/); + $mech->content_lacks('sent_to = ARRAY(0x'); $report->discard_changes; is $report->title, 'Good good'; diff --git a/t/cobrand/get_body_sender.t b/t/cobrand/get_body_sender.t index 06ffb42a5..a1e8f2320 100644 --- a/t/cobrand/get_body_sender.t +++ b/t/cobrand/get_body_sender.t @@ -6,31 +6,21 @@ use_ok 'FixMyStreet::Cobrand'; 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 }); + +my $problem = FixMyStreet::DB->resultset('Problem')->new({}); FixMyStreet::override_config { MAPIT_TYPES => [ 'LBO' ], MAPIT_URL => 'http://mapit.uk/', # Not actually used as no special casing at present }, 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'; + is_deeply $c->get_body_sender( $body, $problem ), { method => 'Email', contact => undef }, 'defaults to email'; }; $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; +is $c->get_body_sender( $body, $problem )->{ method }, 'TestMethod', 'uses send_method in preference to Email'; done_testing(); diff --git a/t/cobrand/hackney.t b/t/cobrand/hackney.t new file mode 100644 index 000000000..b5a629e33 --- /dev/null +++ b/t/cobrand/hackney.t @@ -0,0 +1,292 @@ +use utf8; +use CGI::Simple; +use DateTime; +use Test::MockModule; +use FixMyStreet::TestMech; +use Open311; +use Open311::GetServiceRequests; +use Open311::GetServiceRequestUpdates; +use Open311::PostServiceRequestUpdates; +use FixMyStreet::Script::Alerts; +use FixMyStreet::Script::Reports; + +# disable info logs for this test run +FixMyStreet::App->log->disable('info'); +END { FixMyStreet::App->log->enable('info'); } + +ok( my $mech = FixMyStreet::TestMech->new, 'Created mech object' ); + +my $params = { + send_method => 'Open311', + send_comments => 1, + api_key => 'KEY', + endpoint => 'endpoint', + jurisdiction => 'home', + can_be_devolved => 1, +}; + +my $hackney = $mech->create_body_ok(2508, 'Hackney Council', $params); +my $contact = $mech->create_contact_ok( + body_id => $hackney->id, + category => 'Potholes', + email => 'pothole@example.org', +); +$contact->set_extra_fields( ( { + code => 'urgent', + datatype => 'string', + description => 'question', + variable => 'true', + required => 'false', + order => 1, + datatype_description => 'datatype', +} ) ); +$contact->update; + +my $user = $mech->create_user_ok('user@example.org', name => 'Test User'); +my $hackney_user = $mech->create_user_ok('hackney_user@example.org', name => 'Hackney User', from_body => $hackney); +$hackney_user->user_body_permissions->create({ + body => $hackney, + permission_type => 'moderate', +}); + +my $contact2 = $mech->create_contact_ok( + body_id => $hackney->id, + category => 'Roads', + email => 'roads@example.org', + send_method => 'Email', +); + +my $admin_user = $mech->create_user_ok('admin-user@example.org', name => 'Admin User', from_body => $hackney); + +my @reports = $mech->create_problems_for_body(1, $hackney->id, 'A Hackney report', { + confirmed => '2019-10-25 09:00', + lastupdate => '2019-10-25 09:00', + latitude => 51.552267, + longitude => -0.063316, + user => $user, + external_id => 101202303 +}); + +subtest "check clicking all reports link" => sub { + FixMyStreet::override_config { + MAPIT_URL => 'http://mapit.uk/', + ALLOWED_COBRANDS => ['hackney'], + }, sub { + $mech->get_ok('/'); + $mech->follow_link_ok({ text => 'All reports' }); + }; + + $mech->content_contains("A Hackney report", "Hackney report there"); + $mech->content_contains("Hackney Council", "is still on cobrand"); +}; + +subtest "check moderation label uses correct name" => sub { + my $REPORT_URL = '/report/' . $reports[0]->id; + FixMyStreet::override_config { + MAPIT_URL => 'http://mapit.uk/', + ALLOWED_COBRANDS => ['hackney'], + COBRAND_FEATURES => { + do_not_reply_email => { + hackney => 'fms-hackney-DO-NOT-REPLY@hackney-example.com', + }, + verp_email_domain => { + hackney => 'hackney-example.com', + }, + }, + }, sub { + $mech->log_out_ok; + $mech->log_in_ok( $hackney_user->email ); + $mech->get_ok($REPORT_URL); + $mech->content_lacks('show-moderation'); + $mech->follow_link_ok({ text_regex => qr/^Moderate$/ }); + $mech->content_contains('show-moderation'); + $mech->submit_form_ok({ with_fields => { + problem_title => 'Good good', + problem_detail => 'Good good improved', + }}); + $mech->base_like( qr{\Q$REPORT_URL\E} ); + $mech->content_like(qr/Moderated by Hackney Council/); + }; +}; + +$_->delete for @reports; + +my $system_user = $mech->create_user_ok('system_user@example.org'); + +my ($p) = $mech->create_problems_for_body(1, $hackney->id, '', { cobrand => 'hackney' }); +my $alert = FixMyStreet::DB->resultset('Alert')->create( { + parameter => $p->id, + alert_type => 'new_updates', + user => $user, + cobrand => 'hackney', +} )->confirm; + +subtest "sends branded alert emails" => sub { + $mech->create_comment_for_problem($p, $system_user, 'Other User', 'This is some update text', 'f', 'confirmed', undef); + $mech->clear_emails_ok; + + FixMyStreet::override_config { + MAPIT_URL => 'http://mapit.uk/', + ALLOWED_COBRANDS => ['hackney','fixmystreet'], + }, sub { + FixMyStreet::Script::Alerts::send(); + }; + + my $email = $mech->get_email; + ok $email, "got an email"; + like $mech->get_text_body_from_email($email), qr/Hackney Council/, "emails are branded"; +}; + +$p->comments->delete; +$p->delete; + +subtest "sends branded confirmation emails" => sub { + $mech->log_out_ok; + $mech->clear_emails_ok; + $mech->get_ok('/around'); + FixMyStreet::override_config { + ALLOWED_COBRANDS => [ 'hackney' ], + MAPIT_URL => 'http://mapit.uk/', + COBRAND_FEATURES => { + do_not_reply_email => { + hackney => 'fms-hackney-DO-NOT-REPLY@hackney-example.com', + }, + verp_email_domain => { + hackney => 'hackney-example.com', + }, + }, + }, sub { + $mech->submit_form_ok( { with_fields => { pc => 'E8 1DY', } }, + "submit location" ); + + # 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.', + photo1 => '', + name => 'Joe Bloggs', + username => 'test-1@example.com', + category => 'Roads', + } + }, + "submit good details" + ); + + my $email = $mech->get_email; + ok $email, "got an email"; + like $mech->get_text_body_from_email($email), qr/Hackney Council/, "emails are branded"; + + my $url = $mech->get_link_from_email($email); + $mech->get_ok($url); + $mech->clear_emails_ok; + }; +}; + +FixMyStreet::override_config { + STAGING_FLAGS => { send_reports => 1 }, + MAPIT_URL => 'http://mapit.uk/', + ALLOWED_COBRANDS => ['hackney', 'fixmystreet'], +}, sub { + subtest "special send handling" => sub { + my $cbr = Test::MockModule->new('FixMyStreet::Cobrand::Hackney'); + my $p = FixMyStreet::DB->resultset("Problem")->search(undef, { order_by => { -desc => 'id' } })->first; + $contact2->update({ email => 'park:parks@example;estate:estates@example;other:OTHER', send_method => '' }); + + subtest 'in a park' => sub { + $cbr->mock('_fetch_features', sub { + my ($self, $cfg, $x, $y) = @_; + return [{ + properties => { park_id => 'park' }, + geometry => { + type => 'Polygon', + coordinates => [ [ [ $x-1, $y-1 ], [ $x+1, $y+1 ] ] ], + } + }] if $cfg->{typename} eq 'greenspaces:hackney_park'; + }); + FixMyStreet::Script::Reports::send(); + my $email = $mech->get_email; + is $email->header('To'), '"Hackney Council" <parks@example>'; + $mech->clear_emails_ok; + $p->discard_changes; + $p->update({ whensent => undef }); + }; + + subtest 'in an estate' => sub { + $cbr->mock('_fetch_features', sub { + my ($self, $cfg, $x, $y) = @_; + return [{ + properties => { id => 'estate' }, + geometry => { + type => 'Polygon', + coordinates => [ [ [ $x-1, $y-1 ], [ $x+1, $y+1 ] ] ], + } + }] if $cfg->{typename} eq 'housing:lbh_estate'; + }); + FixMyStreet::Script::Reports::send(); + my $email = $mech->get_email; + is $email->header('To'), '"Hackney Council" <estates@example>'; + $mech->clear_emails_ok; + $p->discard_changes; + $p->update({ whensent => undef }); + }; + + subtest 'elsewhere' => sub { + $cbr->mock('_fetch_features', sub { + my ($self, $cfg, $x, $y) = @_; + return []; # Not in park or estate + }); + my $test_data = FixMyStreet::Script::Reports::send(); + my $req = $test_data->{test_req_used}; + my $c = CGI::Simple->new($req->content); + is $c->param('service_code'), 'OTHER'; + }; + }; +}; + +#subtest "sends branded report sent emails" => sub { + #$mech->clear_emails_ok; + #FixMyStreet::override_config { + #STAGING_FLAGS => { send_reports => 1 }, + #MAPIT_URL => 'http://mapit.uk/', + #ALLOWED_COBRANDS => ['hackney','fixmystreet'], + #}, sub { + #FixMyStreet::Script::Reports::send(); + #}; + #my $email = $mech->get_email; + #ok $email, "got an email"; + #like $mech->get_text_body_from_email($email), qr/Hackney Council/, "emails are branded"; +#}; + +subtest "check category extra uses correct name" => sub { + my @extras = ( { + code => 'test', + datatype => 'string', + description => 'question', + variable => 'true', + required => 'false', + order => 1, + datatype_description => 'datatype', + } ); + $contact2->set_extra_fields( @extras ); + $contact2->update; + + my $extra_details; + + FixMyStreet::override_config { + MAPIT_URL => 'http://mapit.uk/', + ALLOWED_COBRANDS => ['hackney','fixmystreet'], + }, sub { + $extra_details = $mech->get_ok_json('/report/new/category_extras?category=Roads&latitude=51.552267&longitude=-0.063316'); + }; + + like $extra_details->{category_extra}, qr/Hackney Council/, 'correct name in category extras'; +}; + + +done_testing(); @@ -14,4 +14,10 @@ my ($type, $id) = FixMyStreet::Email::check_verp_token($token); is $type, "report", 'Correct type from token'; is $id, 123, 'Correct ID from token'; +my $verpid = FixMyStreet::Email::unique_verp_id([ "report", 123 ]); +is $verpid, 'fms-report-123-8fb274c6@example.org', 'VERP id okay'; + +$verpid = FixMyStreet::Email::unique_verp_id([ "report", 123 ], "example.net"); +is $verpid, 'fms-report-123-8fb274c6@example.net', 'VERP id okay with custom domain'; + done_testing(); |