aboutsummaryrefslogtreecommitdiffstats
path: root/t
diff options
context:
space:
mode:
Diffstat (limited to 't')
-rw-r--r--t/Mock/OpenIDConnect.pm77
-rw-r--r--t/app/controller/auth_social.t198
-rw-r--r--t/app/controller/index.t7
-rw-r--r--t/app/controller/report_new_anon.t164
-rw-r--r--t/app/model/user.t25
-rw-r--r--t/cobrand/bucks.t2
-rw-r--r--t/cobrand/westminster.t218
-rw-r--r--t/open311.t30
8 files changed, 677 insertions, 44 deletions
diff --git a/t/Mock/OpenIDConnect.pm b/t/Mock/OpenIDConnect.pm
new file mode 100644
index 000000000..079c354fc
--- /dev/null
+++ b/t/Mock/OpenIDConnect.pm
@@ -0,0 +1,77 @@
+package t::Mock::OpenIDConnect;
+
+use JSON::MaybeXS;
+use Web::Simple;
+use DateTime;
+use MIME::Base64 qw(encode_base64);
+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 + /oauth2/v2.0/authorize + ?*) {
+ 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' ] ];
+ },
+
+ sub (POST + /oauth2/v2.0/token + ?*) {
+ my ($self) = @_;
+ my $header = {
+ typ => "JWT",
+ alg => "RS256",
+ kid => "XXXfakeKEY1234",
+ };
+ my $now = DateTime->now->epoch;
+ my $payload = {
+ exp => $now + 3600,
+ nbf => $now,
+ ver => "1.0",
+ iss => "https://login.example.org/12345-6789-4321-abcd-12309812309/v2.0/",
+ sub => "my_cool_user_id",
+ aud => "example_client_id",
+ iat => $now,
+ auth_time => $now,
+ given_name => "Andy",
+ family_name => "Dwyer",
+ tfp => "B2C_1_default",
+ extension_CrmContactId => "1c304134-ef12-c128-9212-123908123901",
+ nonce => 'MyAwesomeRandomValue',
+ };
+ $payload->{emails} = ['oidc@example.org'] 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/auth_social.t b/t/app/controller/auth_social.t
index 75eabfc43..160d46f8f 100644
--- a/t/app/controller/auth_social.t
+++ b/t/app/controller/auth_social.t
@@ -5,17 +5,18 @@ use JSON::MaybeXS;
use t::Mock::Facebook;
use t::Mock::Twitter;
+use t::Mock::OpenIDConnect;
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 $body = $mech->create_body_ok(2504, 'Westminster Council');
-my ($report) = $mech->create_problems_for_body(1, $body->id, 'Test');
+my ($report) = $mech->create_problems_for_body(1, $body->id, 'My Test Report');
my $contact = $mech->create_contact_ok(
body_id => $body->id, category => 'Damaged bin', email => 'BIN',
@@ -30,44 +31,101 @@ my $contact2 = $mech->create_contact_ok(
body_id => $body->id, category => 'Whatever', email => 'WHATEVER',
);
-FixMyStreet::override_config {
- FACEBOOK_APP_ID => 'facebook-app-id',
- TWITTER_KEY => 'twitter-key',
- ALLOWED_COBRANDS => [ { fixmystreet => '.' } ],
- MAPIT_URL => 'http://mapit.uk/',
-}, sub {
+my $resolver = Test::MockModule->new('Email::Valid');
+my $social = Test::MockModule->new('FixMyStreet::App::Controller::Auth::Social');
+$social->mock('generate_nonce', sub { 'MyAwesomeRandomValue' });
+
+for my $test (
+ {
+ type => 'facebook',
+ config => {
+ FACEBOOK_APP_ID => 'facebook-app-id',
+ ALLOWED_COBRANDS => [ { fixmystreet => '.' } ],
+ MAPIT_URL => 'http://mapit.uk/',
+ },
+ update => 1,
+ email => 'facebook@example.org',
+ uid => 123456789,
+ mock => 't::Mock::Facebook',
+ mock_hosts => ['www.facebook.com', 'graph.facebook.com'],
+ host => 'www.facebook.com',
+ error_callback => '/auth/Facebook?error_code=ERROR',
+ success_callback => '/auth/Facebook?code=response-code',
+ redirect_pattern => qr{facebook\.com.*dialog/oauth.*facebook-app-id},
+}, {
+ type => 'oidc',
+ config => {
+ ALLOWED_COBRANDS => [ { westminster => '.' } ],
+ MAPIT_URL => 'http://mapit.uk/',
+ COBRAND_FEATURES => {
+ oidc_login => {
+ westminster => {
+ client_id => 'example_client_id',
+ secret => 'example_secret_key',
+ auth_uri => 'http://oidc.example.org/oauth2/v2.0/authorize',
+ token_uri => 'http://oidc.example.org/oauth2/v2.0/token',
+ logout_uri => 'http://oidc.example.org/oauth2/v2.0/logout',
+ display_name => 'MyWestminster'
+ }
+ }
+ }
+ },
+ email => 'oidc@example.org',
+ uid => "westminster:example_client_id:my_cool_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},
+ logout_redirect_pattern => qr{oidc\.example\.org/oauth2/v2\.0/logout},
+ user_extras => [
+ [westminster_account_id => "1c304134-ef12-c128-9212-123908123901"],
+ ],
+}
+) {
-my $fb_email = 'facebook@example.org';
-my $fb_uid = 123456789;
+FixMyStreet::override_config $test->{config}, sub {
-my $resolver = Test::MockModule->new('Email::Valid');
-$resolver->mock('address', sub { 'facebook@example.org' });
+$resolver->mock('address', sub { $test->{email} });
-for my $fb_state ( 'refused', 'no email', 'existing UID', 'okay' ) {
+for my $state ( 'refused', 'no email', 'existing UID', 'okay' ) {
for my $page ( 'my', 'report', 'update' ) {
- subtest "test FB '$fb_state' login for page '$page'" => sub {
+ next if $page eq 'update' && !$test->{update};
+
+ subtest "test $test->{type} '$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 });
+ if ($state eq 'existing UID') {
+ my $user = $mech->create_user_ok($test->{email});
+ if ($test->{type} eq 'facebook') {
+ $user->update({ facebook_id => $test->{uid} });
+ } elsif ($test->{type} eq 'oidc') {
+ $user->update({ oidc_ids => [ $test->{uid} ] });
+ }
} else {
- $mech->delete_user($fb_email);
+ $mech->delete_user($test->{email});
+ }
+ if ($page eq 'my' && $state eq 'existing UID') {
+ $report->update({ user_id => FixMyStreet::App->model( 'DB::User' )->find( { email => $test->{email} } )->id });
+ } else {
+ $report->update({ user_id => FixMyStreet::App->model( 'DB::User' )->find( { email => 'test@example.com' } )->id });
}
- # 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');
+ # Set up a mock to catch (most, see below) requests to the OAuth API
+ my $mock_api = $test->{mock}->new;
+ $mock_api->returns_email(0) if $state eq 'no email' || $state eq 'existing UID';
+ for my $host (@{ $test->{mock_hosts} }) {
+ LWP::Protocol::PSGI->register($mock_api->to_psgi_app, host => $host);
+ }
# 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
+ # the redirect to the OAuth page can mess up the session
+ # cookie. So let's pretend we're always on the API host, which
# sorts that out.
- $mech->host('www.facebook.com');
+ $mech->host($test->{host});
# Fetch the page with the form via which we wish to log in
my $fields;
@@ -91,20 +149,23 @@ for my $fb_state ( 'refused', 'no email', 'existing UID', 'okay' ) {
update => 'Test update',
};
}
- $mech->submit_form(with_fields => $fields, button => 'facebook_sign_in');
+ $mech->submit_form(with_fields => $fields, button => 'social_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');
+ is $mech->res->previous->code, 302, "$test->{type} button redirected";
+ like $mech->res->previous->header('Location'), $test->{redirect_pattern}, "$test->{type} redirect to oauth URL";
+
+ # Okay, now call the callback we'd be sent to
+ # NB: for OIDC these should be post_ok, but that doesn't work because
+ # the session cookie doesn't seem to be included (related to the
+ # cookie issue above perhaps).
+ if ($state eq 'refused') {
+ $mech->get_ok($test->{error_callback});
} else {
- $mech->get_ok('/auth/Facebook?code=response-code');
+ $mech->get_ok($test->{success_callback});
}
# Check we're showing the right form, regardless of what came back
@@ -115,14 +176,14 @@ for my $fb_state ( 'refused', 'no email', 'existing UID', 'okay' ) {
$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.');
+ if ($state eq 'refused') {
+ $mech->content_contains('Sorry, we could not log you in.');
$mech->not_logged_in_ok;
- } elsif ($fb_state eq 'no email') {
+ } elsif ($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
- $fields->{username} = $fb_email;
+ $fields->{username} = $test->{email};
$fields->{name} = 'Ffion Tester' unless $page eq 'my';
$mech->submit_form(with_fields => $fields, $page eq 'my' ? (button => 'sign_in_by_code') : ());
$mech->content_contains('Nearly done! Now check your email');
@@ -131,26 +192,77 @@ for my $fb_state ( 'refused', 'no email', 'existing UID', 'okay' ) {
$mech->clear_emails_ok;
ok $url, "extracted confirm url '$url'";
- my $user = FixMyStreet::App->model( 'DB::User' )->find( { email => $fb_email } );
+ my $user = FixMyStreet::App->model( 'DB::User' )->find( { email => $test->{email} } );
if ($page eq 'my') {
is $user, undef, 'No user yet exists';
} else {
- is $user->facebook_id, undef, 'User has no facebook ID';
+ if ($test->{type} eq 'facebook') {
+ is $user->facebook_id, undef, 'User has no facebook ID';
+ } elsif ($test->{type} eq 'oidc') {
+ is $user->oidc_ids, undef, 'User has no OIDC IDs';
+ }
}
$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';
+ $user = FixMyStreet::App->model( 'DB::User' )->find( { email => $test->{email} } );
+ if ($test->{type} eq 'facebook') {
+ is $user->facebook_id, $test->{uid}, 'User now has correct facebook ID';
+ } elsif ($test->{type} eq 'oidc') {
+ is_deeply $user->oidc_ids, [ $test->{uid} ], 'User now has correct OIDC IDs';
+ }
+ if ($test->{user_extras}) {
+ for my $extra (@{ $test->{user_extras} }) {
+ my ($k, $v) = @$extra;
+ is $user->get_extra_metadata($k), $v, "User has correct $k extra field";
+ }
+ }
} 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;
+ if ($test->{user_extras}) {
+ my $user = FixMyStreet::App->model( 'DB::User' )->find( { email => $test->{email} } );
+ for my $extra (@{ $test->{user_extras} }) {
+ my ($k, $v) = @$extra;
+ is $user->get_extra_metadata($k), $v, "User has correct $k extra field";
+ }
+ }
} else {
is $mech->uri->path, '/my', 'Successfully on /my page';
+ if ($test->{user_extras}) {
+ my $user = FixMyStreet::App->model( 'DB::User' )->find( { email => $test->{email} } );
+ for my $extra (@{ $test->{user_extras} }) {
+ my ($k, $v) = @$extra;
+ is $user->get_extra_metadata($k), $v, "User has correct $k extra field";
+ }
+ }
+ if ($state eq 'existing UID') {
+ my $report_id = $report->id;
+ $mech->content_contains( $report->title );
+ $mech->content_contains( "/report/$report_id" );
+ }
+ }
+
+ $mech->get('/auth/sign_out');
+ if ($test->{type} eq 'oidc' && $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.
+ is $mech->res->previous->code, 302, "$test->{type} sign out redirected";
+ like $mech->res->previous->header('Location'), $test->{logout_redirect_pattern}, "$test->{type} sign out redirect to oauth logout URL";
}
+ $mech->not_logged_in_ok;
}
}
}
+}
+};
+
+FixMyStreet::override_config {
+ TWITTER_KEY => 'twitter-key',
+ ALLOWED_COBRANDS => [ { fixmystreet => '.' } ],
+ MAPIT_URL => 'http://mapit.uk/',
+}, sub {
$resolver->mock('address', sub { 'twitter@example.org' });
@@ -204,7 +316,7 @@ for my $tw_state ( 'refused', 'existing UID', 'no email' ) {
update => 'Test update',
};
}
- $mech->submit_form(with_fields => $fields, button => 'twitter_sign_in');
+ $mech->submit_form(with_fields => $fields, button => 'social_sign_in');
# As well as the cookie issue above, caused by this external
# redirect rewriting the host, the redirect gets handled directly
diff --git a/t/app/controller/index.t b/t/app/controller/index.t
index bd268b3d7..3f3aed48e 100644
--- a/t/app/controller/index.t
+++ b/t/app/controller/index.t
@@ -91,6 +91,13 @@ subtest "prefilters /around if user has categories" => sub {
$mech->content_contains("Cows,Potholes");
};
+subtest "prefilters /around if filter_category given in URL" => sub {
+ $mech->get_ok('/?filter_category=MyUniqueTestCategory&filter_group=MyUniqueTestGroup');
+ # NB can't use visible_form_values because fields are hidden
+ $mech->content_contains("MyUniqueTestCategory");
+ $mech->content_contains("MyUniqueTestGroup");
+};
+
END {
done_testing();
}
diff --git a/t/app/controller/report_new_anon.t b/t/app/controller/report_new_anon.t
new file mode 100644
index 000000000..e9ce23c44
--- /dev/null
+++ b/t/app/controller/report_new_anon.t
@@ -0,0 +1,164 @@
+package FixMyStreet::Cobrand::AnonAllowed;
+use parent 'FixMyStreet::Cobrand::FixMyStreet';
+sub allow_anonymous_reports { 1 }
+sub anonymous_account { { email => 'anon@example.org', name => 'Anonymous' } }
+
+package FixMyStreet::Cobrand::AnonAllowedByButton;
+use parent 'FixMyStreet::Cobrand::FixMyStreet';
+sub allow_anonymous_reports { 'button' }
+sub anonymous_account { { email => 'anonbutton@example.org', name => 'Anonymous Button' } }
+
+package main;
+
+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(2651, 'Edinburgh');
+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 => 'Trees',
+ email => 'trees@example.com',
+);
+
+FixMyStreet::override_config {
+ ALLOWED_COBRANDS => 'anonallowed',
+ MAPIT_URL => 'http://mapit.uk/',
+}, sub {
+
+subtest "check form errors when anonymous account is on" => 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( { with_fields => { category => "Street lighting" } }, "submit form" );
+
+ my @errors = (
+ 'Please enter a subject',
+ 'Please enter some details',
+ # No user errors
+ );
+ is_deeply [ sort @{$mech->page_errors} ], [ sort @errors ], "check errors";
+};
+
+subtest "test report creation anonymously" => 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',
+ category => 'Street lighting',
+ }
+ },
+ "submit good details"
+ );
+ $mech->content_contains('Thank you');
+
+ is_deeply $mech->page_errors, [], "check there were no errors";
+
+ my $report = FixMyStreet::DB->resultset("Problem")->first;
+ ok $report, "Found the report";
+
+ is $report->state, 'confirmed', "report confirmed";
+ $mech->get_ok( '/report/' . $report->id );
+
+ is $report->bodies_str, $body->id;
+ is $report->name, 'Anonymous';
+ is $report->anonymous, 0; # Doesn't change behaviour here, but uses anon account's name always
+
+ my $alert = FixMyStreet::App->model('DB::Alert')->find( {
+ user => $report->user,
+ alert_type => 'new_updates',
+ parameter => $report->id,
+ } );
+ is $alert, undef, "no alert created";
+
+ $mech->not_logged_in_ok;
+};
+
+};
+
+FixMyStreet::override_config {
+ ALLOWED_COBRANDS => 'anonallowedbybutton',
+ MAPIT_URL => 'http://mapit.uk/',
+}, sub {
+
+subtest "test report creation anonymously by button" => 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',
+ category => 'Street lighting',
+ }
+ },
+ "submit good details"
+ );
+
+ is_deeply $mech->page_errors, [
+ 'Please enter your email'
+ ], "check there were no errors";
+
+ $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 => 'report_anonymously',
+ with_fields => {
+ title => 'Test Report',
+ detail => 'Test report details.',
+ category => 'Street lighting',
+ }
+ },
+ "submit good details"
+ );
+ $mech->content_contains('Thank you');
+
+ is_deeply $mech->page_errors, [], "check there were no errors";
+
+ my $report = FixMyStreet::DB->resultset("Problem")->search({}, { order_by => { -desc => 'id' } })->first;
+ ok $report, "Found the report";
+
+ is $report->state, 'confirmed', "report confirmed";
+ $mech->get_ok( '/report/' . $report->id );
+
+ is $report->bodies_str, $body->id;
+ is $report->name, 'Anonymous Button';
+ is $report->anonymous, 1; # Doesn't change behaviour here, but uses anon account's name always
+
+ my $alert = FixMyStreet::App->model('DB::Alert')->find( {
+ user => $report->user,
+ alert_type => 'new_updates',
+ parameter => $report->id,
+ } );
+ is $alert, undef, "no alert created";
+
+ $mech->not_logged_in_ok;
+};
+
+};
+
+done_testing();
diff --git a/t/app/model/user.t b/t/app/model/user.t
index cbc0fe6cf..88b29ca84 100644
--- a/t/app/model/user.t
+++ b/t/app/model/user.t
@@ -75,6 +75,31 @@ subtest 'Check non-existent methods on user object die' => sub {
);
};
+subtest 'OIDC ids can be manipulated correctly' => sub {
+ my $user = $problem->user;
+
+ is $user->oidc_ids, undef, 'user starts with no OIDC ids';
+
+ $user->add_oidc_id("fixmystreet:1234:5678");
+ is_deeply $user->oidc_ids, ["fixmystreet:1234:5678"], 'OIDC id added correctly';
+
+ $user->add_oidc_id("mycobrand:0123:abcd");
+ is_deeply [ sort @{$user->oidc_ids} ], ["fixmystreet:1234:5678", "mycobrand:0123:abcd"], 'Second OIDC id added correctly';
+
+ $user->add_oidc_id("mycobrand:0123:abcd");
+ is_deeply [ sort @{$user->oidc_ids} ], ["fixmystreet:1234:5678", "mycobrand:0123:abcd"], 'Adding existing OIDC id does not add duplicate';
+
+ $user->remove_oidc_id("mycobrand:0123:abcd");
+ is_deeply $user->oidc_ids, ["fixmystreet:1234:5678"], 'OIDC id can be removed OK';
+
+ $user->remove_oidc_id("mycobrand:0123:abcd");
+ is_deeply $user->oidc_ids, ["fixmystreet:1234:5678"], 'Removing non-existent OIDC id has no effect';
+
+ $user->remove_oidc_id("fixmystreet:1234:5678");
+ is $user->oidc_ids, undef, 'Removing last OIDC id results in undef';
+
+};
+
done_testing();
sub create_update {
diff --git a/t/cobrand/bucks.t b/t/cobrand/bucks.t
index 6732eb29c..2d42dcd81 100644
--- a/t/cobrand/bucks.t
+++ b/t/cobrand/bucks.t
@@ -19,7 +19,7 @@ $mech->create_contact_ok(body_id => $district->id, category => 'Graffiti', email
my $cobrand = Test::MockModule->new('FixMyStreet::Cobrand::Buckinghamshire');
$cobrand->mock('lookup_site_code', sub {
- my ($self, $row, $buffer) = @_;
+ my ($self, $row) = @_;
return "Road ID" if $row->latitude == 51.812244;
});
diff --git a/t/cobrand/westminster.t b/t/cobrand/westminster.t
new file mode 100644
index 000000000..2912f6ee5
--- /dev/null
+++ b/t/cobrand/westminster.t
@@ -0,0 +1,218 @@
+use CGI::Simple;
+use Test::MockModule;
+use FixMyStreet::TestMech;
+use FixMyStreet::Script::Reports;
+
+ok( my $mech = FixMyStreet::TestMech->new, 'Created mech object' );
+
+my $cobrand = Test::MockModule->new('FixMyStreet::Cobrand::Westminster');
+$cobrand->mock('lookup_site_code', sub {
+ my ($self, $row) = @_;
+ return "My USRN" if $row->latitude == 51.501009;
+});
+
+my $body = $mech->create_body_ok(2504, 'Westminster City Council', {
+ send_method => 'Open311', api_key => 'key', 'endpoint' => 'e', 'jurisdiction' => 'j' });
+my $superuser = $mech->create_user_ok(
+ 'superuser@example.com',
+ name => 'Test Superuser',
+ is_superuser => 1
+);
+my $staff_user = $mech->create_user_ok(
+ 'westminster@example.com',
+ name => 'Test User',
+ from_body => $body
+);
+my ($report) = $mech->create_problems_for_body(1, $body->id, 'Title');
+
+FixMyStreet::override_config {
+ ALLOWED_COBRANDS => 'westminster',
+ MAPIT_URL => 'http://mapit.uk/',
+ COBRAND_FEATURES => {
+ oidc_login => {
+ westminster => {
+ client_id => 'example_client_id',
+ secret => 'example_secret_key',
+ auth_uri => 'http://oidc.example.org/oauth2/v2.0/authorize',
+ token_uri => 'http://oidc.example.org/oauth2/v2.0/token',
+ display_name => 'MyWestminster'
+ }
+ }
+ }
+}, sub {
+ subtest 'Cobrand allows social auth' => sub {
+ my $cobrand = FixMyStreet::Cobrand->get_class_for_moniker('westminster')->new();
+ ok $cobrand->social_auth_enabled;
+ };
+
+ subtest 'Login button displayed correctly' => sub {
+ $mech->get_ok("/auth");
+ $mech->content_contains("Sign in with MyWestminster");
+ };
+
+ subtest 'Reports do not have update form' => sub {
+ $mech->get_ok('/report/' . $report->id);
+ $mech->content_lacks('Provide an update');
+ };
+};
+
+subtest 'Reports have an update form for superusers' => sub {
+ # Westminster cobrand disables email signin, so we have to
+ # login and *then* set the cobrand.
+ $mech->log_in_ok( $superuser->email );
+
+ FixMyStreet::override_config {
+ ALLOWED_COBRANDS => 'westminster',
+ MAPIT_URL => 'http://mapit.uk/',
+ }, sub {
+ $mech->get_ok('/report/' . $report->id);
+ $mech->content_contains('Provide an update');
+ };
+
+ $mech->log_out_ok();
+};
+
+subtest 'Reports have an update form for staff users' => sub {
+ $mech->log_in_ok( $staff_user->email );
+ FixMyStreet::override_config {
+ ALLOWED_COBRANDS => 'westminster',
+ MAPIT_URL => 'http://mapit.uk/',
+ }, sub {
+ $mech->get_ok('/report/' . $report->id);
+ $mech->content_contains('Provide an update');
+ };
+ $mech->log_out_ok();
+};
+
+for (
+ {
+ ALLOWED_COBRANDS => 'westminster',
+ MAPIT_URL => 'http://mapit.uk/',
+ COBRAND_FEATURES => {
+ oidc_login => {
+ westminster => 0
+ }
+ }
+ },
+ {
+ ALLOWED_COBRANDS => 'westminster',
+ MAPIT_URL => 'http://mapit.uk/',
+ COBRAND_FEATURES => {
+ oidc_login => {
+ hounslow => {
+ client_id => 'example_client_id',
+ secret => 'example_secret_key',
+ auth_uri => 'http://oidc.example.org/oauth2/v2.0/authorize',
+ token_uri => 'http://oidc.example.org/oauth2/v2.0/token',
+ display_name => 'MyHounslow'
+ }
+ }
+ }
+ },
+ {
+ ALLOWED_COBRANDS => 'westminster',
+ MAPIT_URL => 'http://mapit.uk/',
+ }
+) {
+ FixMyStreet::override_config $_, sub {
+ subtest 'Cobrand disallows social auth' => sub {
+ my $cobrand = FixMyStreet::Cobrand->get_class_for_moniker('westminster')->new();
+ ok !$cobrand->social_auth_enabled;
+ };
+
+ subtest 'Login button not displayed' => sub {
+ $mech->get_ok("/auth");
+ $mech->content_lacks("Login with MyWestminster");
+ };
+ };
+}
+
+FixMyStreet::DB->resultset('Problem')->delete_all;
+$mech->create_contact_ok(body_id => $body->id, category => 'Abandoned bike', email => "BIKE");
+($report) = $mech->create_problems_for_body(1, $body->id, 'Bike', {
+ category => "Abandoned bike", cobrand => 'westminster',
+ latitude => 51.501009, longitude => -0.141588, areas => '2504',
+});
+
+FixMyStreet::override_config {
+ ALLOWED_COBRANDS => [ 'westminster' ],
+ MAPIT_URL => 'http://mapit.uk/',
+ STAGING_FLAGS => { send_reports => 1, skip_checks => 0 },
+ COBRAND_FEATURES => { anonymous_account => { westminster => 'anon' } },
+}, sub {
+ subtest 'USRN set correctly' => sub {
+ 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'), 'BIKE';
+ is $c->param('attribute[USRN]'), 'My USRN';
+ };
+};
+
+FixMyStreet::override_config {
+ ALLOWED_COBRANDS => 'westminster',
+ MAPIT_URL => 'http://mapit.uk/',
+}, sub {
+ subtest 'No reporter alert created' => sub {
+ my $user = $mech->log_in_ok('test@example.org');
+ $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" );
+ $mech->submit_form_ok( { with_fields => {
+ title => 'Title', detail => 'Detail', category => 'Abandoned bike', name => 'Test Example',
+ } }, 'submitted okay' );
+ is $user->alerts->count, 0;
+ };
+};
+
+my $westminster = FixMyStreet::Cobrand::Westminster->new;
+subtest 'correct config returned for USRN/UPRN lookup' => sub {
+ my $actual = $westminster->lookup_site_code_config('USRN');
+ delete $actual->{accept_feature}; # is_deeply doesn't like code
+ is_deeply $actual, {
+ buffer => 1000,
+ proxy_url => "https://tilma.staging.mysociety.org/resource-proxy/proxy.php",
+ url => "https://westminster.assets/40/query",
+ property => 'USRN',
+ };
+ $actual = $westminster->lookup_site_code_config('UPRN');
+ delete $actual->{accept_feature}; # is_deeply doesn't like code
+ is_deeply $actual, {
+ buffer => 1000,
+ proxy_url => "https://tilma.staging.mysociety.org/resource-proxy/proxy.php",
+ url => "https://westminster.assets/25/query",
+ property => 'UPRN',
+ accept_types => {
+ Point => 1
+ },
+ };
+};
+
+subtest 'nearest UPRN returns correct point' => sub {
+ my $cfg = {
+ accept_feature => sub { 1 },
+ property => 'UPRN',
+ accept_types => {
+ Point => 1,
+ },
+ };
+ my $features = [
+ # A couple of incorrect geometry types to check they're ignored...
+ { geometry => { type => 'Polygon' } },
+ { geometry => { type => 'LineString',
+ coordinates => [ [ 527735, 181004 ], [ 527755, 181004 ] ] },
+ properties => { fid => '20100024' } },
+ # And two points which are further away than the above linestring,
+ # the second of which is the closest to our testing point.
+ { geometry => { type => 'Point',
+ coordinates => [ 527795, 181024 ] },
+ properties => { UPRN => '10012387122' } },
+ { geometry => { type => 'Point',
+ coordinates => [ 527739, 181009 ] },
+ properties => { UPRN => '10012387123' } },
+ ];
+ is $westminster->_nearest_feature($cfg, 527745, 180994, $features), '10012387123';
+};
+
+
+done_testing();
diff --git a/t/open311.t b/t/open311.t
index 73bb488d5..9524006b8 100644
--- a/t/open311.t
+++ b/t/open311.t
@@ -263,6 +263,36 @@ for my $test (
};
}
+for my $test (
+ {
+ desc => 'account_id handled correctly when present',
+ account_id => '1c304134-ef12-c128-9212-123908123901',
+ },
+ {
+ desc => 'account_id handled correctly when 0',
+ account_id => '0'
+ },
+ {
+ desc => 'account_id handled correctly when missing',
+ account_id => undef
+ }
+) {
+ subtest $test->{desc} => sub {
+ $problem->extra( undef );
+ my $extra = {
+ url => 'http://example.com/report/1',
+ defined $test->{account_id} ? ( account_id => $test->{account_id} ) : ()
+ };
+
+ 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( 'account_id' ), $test->{account_id}, 'correct account_id';
+ };
+}
+
subtest 'test always_send_email' => sub {
my $email = $user->email;
$user->email(undef);