aboutsummaryrefslogtreecommitdiffstats
path: root/t
diff options
context:
space:
mode:
Diffstat (limited to 't')
-rw-r--r--t/app/controller/about.t2
-rw-r--r--t/app/controller/admin.t49
-rw-r--r--t/app/controller/alert_new.t32
-rw-r--r--t/app/controller/dashboard.t639
-rw-r--r--t/app/controller/questionnaire.t132
-rw-r--r--t/app/controller/report_import.t2
-rw-r--r--t/app/controller/report_new.t228
-rw-r--r--t/app/controller/report_updates.t20
-rw-r--r--t/app/controller/reports.t4
-rw-r--r--t/app/model/problem.t130
-rw-r--r--t/app/uri_for.t57
-rw-r--r--t/cobrand/get_council_sender.t30
-rw-r--r--t/open311.t451
-rw-r--r--t/open311/getservicerequestupdates.t537
-rw-r--r--t/open311/populate-service-list.t298
15 files changed, 2472 insertions, 139 deletions
diff --git a/t/app/controller/about.t b/t/app/controller/about.t
index ea7b1af20..4e49cdac9 100644
--- a/t/app/controller/about.t
+++ b/t/app/controller/about.t
@@ -13,7 +13,7 @@ $mech->content_contains('html class="no-js" lang="en-gb"');
SKIP: {
skip( "Need 'emptyhomes' in ALLOWED_COBRANDS config", 8 )
- unless FixMyStreet::App->config->{ALLOWED_COBRANDS} =~ m{emptyhomes};
+ unless FixMyStreet::Cobrand->exists('emptyhomes');
# check that geting the page as EHA produces a different page
ok $mech->host("reportemptyhomes.co.uk"), 'change host to reportemptyhomes';
diff --git a/t/app/controller/admin.t b/t/app/controller/admin.t
index d8a1c24a1..09d99cfdf 100644
--- a/t/app/controller/admin.t
+++ b/t/app/controller/admin.t
@@ -105,34 +105,39 @@ subtest 'check summary counts' => sub {
$mech->content_contains( "$q_count questionnaires sent" );
- ok $mech->host('barnet.fixmystreet.com');
+ SKIP: {
+ skip( "Need 'barnet' in ALLOWED_COBRANDS config", 7 )
+ unless FixMyStreet::Cobrand->exists('barnet');
- $mech->get_ok('/admin');
- $mech->title_like(qr/Summary/);
+ ok $mech->host('barnet.fixmystreet.com');
- my ($num_live) = $mech->content =~ /(\d+)<\/strong> live problems/;
- my ($num_alerts) = $mech->content =~ /(\d+) confirmed alerts/;
- my ($num_qs) = $mech->content =~ /(\d+) questionnaires sent/;
+ $mech->get_ok('/admin');
+ $mech->title_like(qr/Summary/);
- $report->council(2489);
- $report->cobrand('barnet');
- $report->update;
+ my ($num_live) = $mech->content =~ /(\d+)<\/strong> live problems/;
+ my ($num_alerts) = $mech->content =~ /(\d+) confirmed alerts/;
+ my ($num_qs) = $mech->content =~ /(\d+) questionnaires sent/;
- $alert->cobrand('barnet');
- $alert->update;
+ $report->council(2489);
+ $report->cobrand('barnet');
+ $report->update;
- $mech->get_ok('/admin');
+ $alert->cobrand('barnet');
+ $alert->update;
- $mech->content_contains( ($num_live+1) . "</strong> live problems" );
- $mech->content_contains( ($num_alerts+1) . " confirmed alerts" );
- $mech->content_contains( ($num_qs+1) . " questionnaires sent" );
+ $mech->get_ok('/admin');
- $report->council(2504);
- $report->cobrand('');
- $report->update;
+ $mech->content_contains( ($num_live+1) . "</strong> live problems" );
+ $mech->content_contains( ($num_alerts+1) . " confirmed alerts" );
+ $mech->content_contains( ($num_qs+1) . " questionnaires sent" );
+
+ $report->council(2504);
+ $report->cobrand('');
+ $report->update;
- $alert->cobrand('');
- $alert->update;
+ $alert->cobrand('');
+ $alert->update;
+ }
FixMyStreet::App->model('DB::Problem')->search( { council => 1 } )->update( { council => 2489 } );
ok $mech->host('fixmystreet.com');
@@ -222,6 +227,8 @@ subtest 'check open311 configuring' => sub {
api_key => 'api key',
endpoint => 'http://example.com/open311',
jurisdiction => 'mySociety',
+ send_comments => 0,
+ send_method => 'Open311',
}
}
);
@@ -244,6 +251,8 @@ subtest 'check open311 configuring' => sub {
api_key => 'new api key',
endpoint => 'http://example.org/open311',
jurisdiction => 'open311',
+ send_comments => 0,
+ send_method => 'Open311',
}
}
);
diff --git a/t/app/controller/alert_new.t b/t/app/controller/alert_new.t
index 3a4c2ef81..7ba887824 100644
--- a/t/app/controller/alert_new.t
+++ b/t/app/controller/alert_new.t
@@ -142,25 +142,18 @@ foreach my $test (
}
foreach my $test (
- {
- email => 'test-new@example.com',
- type => 'area',
- content => 'your alert will not be activated',
- email_text => 'confirm the alert',
- uri =>
-'/alert/subscribe?type=local&rznvy=test-new@example.com&feed=area:1000:A_Location',
- param1 => 1000
- }
+ { exist => 0 },
+ { exist => 1 },
)
{
- subtest "use existing unlogged in user in a alert" => sub {
+ subtest "use existing unlogged in user in a alert ($test->{exist})" => sub {
$mech->log_out_ok();
- my $type = $test->{type} . '_problems';
+ my $type = 'area_problems';
my $user =
FixMyStreet::App->model('DB::User')
- ->find_or_create( { email => $test->{email} } );
+ ->find_or_create( { email => 'test-new@example.com' } );
my $alert = FixMyStreet::App->model('DB::Alert')->find(
{
@@ -169,24 +162,26 @@ foreach my $test (
}
);
# clear existing data so we can be sure we're creating it
- ok $alert->delete() if $alert;
+ ok $alert->delete() if $alert && !$test->{exist};
- $mech->get_ok( $test->{uri} );
+ $mech->get_ok( '/alert/subscribe?type=local&rznvy=test-new@example.com&feed=area:1000:A_Location' );
$alert = FixMyStreet::App->model('DB::Alert')->find(
{
user => $user,
alert_type => $type,
- parameter => $test->{param1},
- parameter2 => $test->{param2},
- confirmed => 0,
+ parameter => 1000,
+ parameter2 => undef,
+ confirmed => $test->{exist},
}
);
$mech->content_contains( 'Now check your email' );
+ $alert->confirm();
ok $alert, 'New alert created with existing user';
- $mech->delete_user($user);
+
+ $mech->delete_user($user) if $test->{exist};
};
}
@@ -445,6 +440,7 @@ subtest "Test normal alert signups and that alerts are sent" => sub {
ok $update, "created test update - $update_id";
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;
diff --git a/t/app/controller/dashboard.t b/t/app/controller/dashboard.t
new file mode 100644
index 000000000..7033fa02c
--- /dev/null
+++ b/t/app/controller/dashboard.t
@@ -0,0 +1,639 @@
+use strict;
+use warnings;
+use Test::More;
+
+use FixMyStreet::TestMech;
+use Web::Scraper;
+
+my $mech = FixMyStreet::TestMech->new;
+
+my $test_user = 'council_user@example.com';
+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 $p_user = FixMyStreet::App->model('DB::User')->find_or_create( {
+ email => 'p_user@example.com'
+} );
+
+$mech->not_logged_in_ok;
+$mech->get_ok('/dashboard');
+
+$mech->content_contains( 'sign in' );
+
+$mech->submit_form(
+ with_fields => { email => $test_user, password_sign_in => $test_pass }
+);
+
+is $mech->status, '404', 'If not council user get 404';
+
+$user->from_council( $test_council );
+$user->update;
+
+$mech->log_out_ok;
+$mech->get_ok('/dashboard');
+$mech->submit_form_ok( {
+ with_fields => { email => $test_user, password_sign_in => $test_pass }
+} );
+
+$mech->content_contains( 'Summary Statistics for City of Edinburgh' );
+
+FixMyStreet::App->model('DB::Contact')->search( { area_id => $test_council } )
+ ->delete;
+
+delete_problems();
+
+my @cats = qw( Grafitti Litter Potholes );
+for my $contact ( @cats ) {
+ FixMyStreet::App->model('DB::Contact')->create(
+ {
+ area_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'
+ },
+};
+
+my $expected_cats = [ 'All', '-- Pick a category --', @cats, 'Other' ];
+my $res = $categories->scrape( $mech->content );
+is_deeply( $res->{cats}, $expected_cats, 'correct list of categories' );
+
+foreach my $row ( @{ $res->{rows} }[1 .. 11] ) {
+ foreach my $col ( @{ $row->{cols} } ) {
+ is $col, 0;
+ }
+}
+
+for my $reports ( @{ $res->{report_lists} } ) {
+ is_deeply $reports, {}, 'No reports';
+}
+
+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} );
+ };
+}
+
+delete_problems();
+
+foreach my $test (
+ {
+ desc => 'user fixed today',
+ confirm_dt => DateTime->now->subtract( days => 1 ),
+ mark_dt => DateTime->now,
+ state => 'fixed - user',
+ counts => {
+ totals => [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 => [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 => [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 => [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 => [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 => [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 => [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 => [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 => [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 => [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 );
+
+ 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],
+ },
+ {
+ 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],
+ },
+ {
+ 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],
+ },
+ {
+ 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],
+ },
+ {
+ 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],
+ },
+ {
+ 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],
+ },
+) {
+ subtest $test->{desc} => sub {
+ make_problem( $test->{p1} ) if $test->{p1};
+ make_problem( $test->{p2} ) if $test->{p2};
+
+ $mech->get_ok('/dashboard');
+
+ $res = $categories->scrape( $mech->content );
+
+ foreach my $row ( keys %{ $test->{counts} } ) {
+ check_row( $res, $row, $test->{counts}->{$row} );
+ }
+
+ check_report_counts( $res, $test->{report_counts} );
+
+ $mech->submit_form_ok( {
+ with_fields => {
+ category => $test->{category},
+ ward => $test->{ward},
+ }
+ } );
+
+ $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} );
+ };
+}
+
+delete_problems();
+
+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],
+ },
+ {
+ 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',
+ },
+ p2 => {
+ state => 'fixed',
+ conf_dt => DateTime->now(),
+ category => 'Potholes',
+ },
+ 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};
+
+ $mech->get_ok('/dashboard');
+
+ $res = $categories->scrape( $mech->content );
+
+ check_report_counts( $res, $test->{report_counts} );
+
+ $mech->submit_form_ok( {
+ with_fields => {
+ state => $test->{state},
+ }
+ } );
+
+ $res = $categories->scrape( $mech->content );
+
+ check_report_counts( $res, $test->{report_counts_after} );
+ };
+}
+
+sub make_problem {
+ my $args = shift;
+
+ my $p = FixMyStreet::App->model('DB::Problem')->create( {
+ title => 'a problem',
+ name => 'a user',
+ anonymous => 1,
+ detail => 'some detail',
+ state => $args->{state},
+ confirmed => $args->{conf_dt},
+ whensent => $args->{conf_dt},
+ lastupdate => $args->{mark_dt} || $args->{conf_dt},
+ council => $test_council,
+ postcode => 'EH99 1SP',
+ latitude => '51',
+ longitude => '1',
+ areas => $args->{areas} || $test_ward,
+ used_map => 0,
+ user_id => $p_user->id,
+ category => $args->{category} || 'Other',
+ } );
+
+ if ( $args->{state} ne 'confirmed' ) {
+ my $c = FixMyStreet::App->model('DB::Comment')->create( {
+ problem => $p,
+ user_id => $p_user->id,
+ state => 'confirmed',
+ problem_state => $args->{state} =~ /^fixed - user|fixed$/ ? undef : $args->{state},
+ confirmed => $args->{mark_dt},
+ text => 'an update',
+ mark_fixed => $args->{state} =~ /fixed/ ? 1 : 0,
+ anonymous => 1,
+ } );
+ }
+}
+
+sub check_row {
+ my $res = shift;
+ my $row = shift;
+ my $totals = shift;
+
+ is $res->{ $row }->[0], $totals->[0], "Correct count in $row for WTD";
+ is $res->{ $row }->[1], $totals->[1], "Correct count in $row for last 7 days";
+ is $res->{ $row }->[2], $totals->[2], "Correct count in $row for last 4 weeks";
+ is $res->{ $row }->[3], $totals->[3], "Correct count in $row for YTD";
+}
+
+sub check_report_counts {
+ my $res = shift;
+ my $counts = shift;
+
+ for my $i ( 0 .. 2 ) {
+ if ( $counts->[$i] == 0 ) {
+ is_deeply $res->{report_lists}->[$i], {}, "No reports for column $i";
+ } else {
+ if ( ref( $res->{report_lists}->[$i]->{reports} ) eq 'ARRAY' ) {
+ is scalar @{ $res->{report_lists}->[$i]->{reports} }, $counts->[$i], "Correct report count for column $i";
+ } else {
+ fail "Correct report count for column $i ( no reports )";
+ }
+ }
+ }
+}
+
+sub delete_problems {
+ FixMyStreet::App->model('DB::Comment')
+ ->search( { 'problem.council' => $test_council }, { join => 'problem' } )
+ ->delete;
+ FixMyStreet::App->model('DB::Problem')
+ ->search( { council => $test_council } )->delete();
+}
+
+done_testing;
diff --git a/t/app/controller/questionnaire.t b/t/app/controller/questionnaire.t
index 0e2a71184..2d4fdb711 100644
--- a/t/app/controller/questionnaire.t
+++ b/t/app/controller/questionnaire.t
@@ -322,67 +322,79 @@ for my $test (
};
}
-# 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?
-$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;
-
-like $email->body, qr/fill in this short questionnaire/i, "got questionnaire email";
-($token) = $email->body =~ m{http://.*?/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(
- {
- problem_id => $report->id,
- whensent => $dt->ymd . ' ' . $dt->hms,
- ever_reported => 1,
- }
-);
-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';
-
-# I18N Unicode extra testing using FiksGataMi
-$report->send_questionnaire( 1 );
-$report->cobrand( 'fiksgatami' );
-$report->update;
-$questionnaire->delete;
-$questionnaire2->delete;
-FixMyStreet::App->model('DB::Questionnaire')->send_questionnaires( { site => 'fixmystreet' } ); # It's either fixmystreet or emptyhomes
-$email = $mech->get_email;
-ok $email, "got an email";
-$mech->clear_emails_ok;
+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?
+ $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;
+
+ like $email->body, qr/fill in this short questionnaire/i, "got questionnaire email";
+ ($token) = $email->body =~ m{http://.*?/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(
+ {
+ problem_id => $report->id,
+ whensent => $dt->ymd . ' ' . $dt->hms,
+ ever_reported => 1,
+ }
+ );
+ 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;
+}
-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';
+SKIP: {
+ skip( "Need 'fiksgatami' in ALLOWED_COBRANDS config", 5 )
+ unless FixMyStreet::Cobrand->exists('fiksgatami');
+
+ # I18N Unicode extra testing using FiksGataMi
+ $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
+ $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';
+}
$mech->delete_user('test@example.com');
done_testing();
diff --git a/t/app/controller/report_import.t b/t/app/controller/report_import.t
index 934bf0346..eb686b44e 100644
--- a/t/app/controller/report_import.t
+++ b/t/app/controller/report_import.t
@@ -266,7 +266,7 @@ subtest "Submit a correct entry (with location) to cobrand" => sub {
SKIP: {
skip( "Need 'fiksgatami' in ALLOWED_COBRANDS config", 20 )
- unless FixMyStreet::App->config->{ALLOWED_COBRANDS} =~ m{fiksgatami};
+ unless FixMyStreet::Cobrand->exists('fiksgatami');
mySociety::MaPit::configure('http://mapit.nuug.no/');
ok $mech->host("fiksgatami.no"), 'change host to fiksgatami';
diff --git a/t/app/controller/report_new.t b/t/app/controller/report_new.t
index 625c7531f..29fb650e5 100644
--- a/t/app/controller/report_new.t
+++ b/t/app/controller/report_new.t
@@ -6,10 +6,14 @@ use utf8;
use FixMyStreet::TestMech;
use Web::Scraper;
+use Path::Class;
my $mech = FixMyStreet::TestMech->new;
$mech->get_ok('/report/new');
+my $sample_file = file(__FILE__)->parent->file("sample.jpg")->stringify;
+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');
@@ -48,9 +52,23 @@ my $contact3 = FixMyStreet::App->model('DB::Contact')->find_or_create( {
category => 'Trees',
email => 'trees@example.com',
} );
+my $contact4 = FixMyStreet::App->model('DB::Contact')->find_or_create( {
+ %contact_params,
+ area_id => 2482, # Bromley
+ category => 'Trees',
+ email => 'trees@example.com',
+} );
+my $contact5 = FixMyStreet::App->model('DB::Contact')->find_or_create( {
+ %contact_params,
+ area_id => 2651, # Edinburgh
+ category => 'Trees',
+ email => 'trees@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";
# test that the various bit of form get filled in and errors correctly
# generated.
@@ -282,6 +300,69 @@ foreach my $test (
},
errors => [ 'Please enter a subject', 'Please enter some details', ],
},
+ {
+ msg => 'non-photo upload gives error',
+ pc => 'SW1A 1AA',
+ fields => {
+ title => 'Title',
+ detail => 'Detail',
+ photo => [ [ undef, 'bad.txt', Content => 'This is not a JPEG', Content_Type => 'text/plain' ], 1 ],
+ name => 'Bob Jones',
+ may_show_name => '1',
+ email => 'bob@example.com',
+ phone => '',
+ category => 'Street lighting',
+ password_sign_in => '',
+ password_register => '',
+ remember_me => undef,
+ },
+ changes => {
+ photo => '',
+ },
+ errors => [ "Please upload a JPEG image only" ],
+ },
+ {
+ msg => 'bad photo upload gives error',
+ pc => 'SW1A 1AA',
+ fields => {
+ title => 'Title',
+ detail => 'Detail',
+ photo => [ [ undef, 'fake.jpeg', Content => 'This is not a JPEG', Content_Type => 'image/jpeg' ], 1 ],
+ name => 'Bob Jones',
+ may_show_name => '1',
+ email => 'bob@example.com',
+ phone => '',
+ category => 'Street lighting',
+ password_sign_in => '',
+ password_register => '',
+ remember_me => undef,
+ },
+ changes => {
+ photo => '',
+ },
+ errors => [ "That image doesn't appear to have uploaded correctly (Please upload a JPEG image only ), please try again." ],
+ },
+ {
+ msg => 'photo with octet-stream gets through okay',
+ pc => 'SW1A 1AA',
+ fields => {
+ title => '',
+ detail => 'Detail',
+ photo => [ [ $sample_file, undef, Content_Type => 'application/octet-stream' ], 1 ],
+ name => 'Bob Jones',
+ may_show_name => '1',
+ email => 'bob@example.com',
+ phone => '',
+ category => 'Street lighting',
+ password_sign_in => '',
+ password_register => '',
+ remember_me => undef,
+ },
+ changes => {
+ photo => '',
+ },
+ errors => [ "Please enter a subject" ],
+ },
)
{
subtest "check form errors where $test->{msg}" => sub {
@@ -293,7 +374,7 @@ foreach my $test (
is_deeply $mech->form_errors, [], "no errors for pc '$test->{pc}'";
# click through to the report page
- $mech->follow_link_ok( { text => 'skip this step', },
+ $mech->follow_link_ok( { text_regex => qr/skip this step/i, },
"follow 'skip this step' link" );
# submit the main form
@@ -473,7 +554,7 @@ subtest "test password errors for a user who is signing in as they report" => su
"submit location" );
# click through to the report page
- $mech->follow_link_ok( { text => 'Skip this step', },
+ $mech->follow_link_ok( { text_regex => qr/skip this step/i, },
"follow 'skip this step' link" );
$mech->submit_form_ok(
@@ -520,7 +601,7 @@ subtest "test report creation for a user who is signing in as they report" => su
"submit location" );
# click through to the report page
- $mech->follow_link_ok( { text => 'Skip this step', },
+ $mech->follow_link_ok( { text_regex => qr/skip this step/i, },
"follow 'skip this step' link" );
$mech->submit_form_ok(
@@ -614,7 +695,7 @@ foreach my $test (
"submit location" );
# click through to the report page
- $mech->follow_link_ok( { text => 'Skip this step', },
+ $mech->follow_link_ok( { text_regex => qr/skip this step/i, },
"follow 'skip this step' link" );
# check that the fields are correctly prefilled
@@ -717,7 +798,7 @@ subtest "check that a lat/lon off coast leads to /around" => sub {
is $mech->uri->path, '/around', "redirected to '/around'";
is_deeply #
- $mech->page_errors,
+ $mech->form_errors,
[ 'That spot does not appear to be covered by a council. If you have'
. ' tried to report an issue past the shoreline, for example, please'
. ' specify the closest point on land.' ], #
@@ -725,8 +806,145 @@ 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',
+ fms_extra_title => '',
+ extra => undef,
+ user_title => undef,
+ },
+ {
+ desc => 'title shown for bromley problem on main site',
+ host => 'http://www.fixmystreet.com',
+ postcode => 'BR1 3UH',
+ fms_extra_title => 'MR',
+ extra => [
+ {
+ name => 'fms_extra_title',
+ value => 'MR',
+ description => 'FMS_EXTRA_TITLE',
+ },
+ ],
+ user_title => 'MR',
+ },
+ {
+ desc =>
+ 'title, first and last name shown for bromley problem on cobrand',
+ host => 'http://bromley.fixmystreet.com',
+ postcode => 'BR1 3UH',
+ first_name => 'Test',
+ last_name => 'User',
+ fms_extra_title => 'MR',
+ extra => [
+ {
+ name => 'fms_extra_title',
+ value => 'MR',
+ description => 'FMS_EXTRA_TITLE',
+ },
+ {
+ name => 'first_name',
+ value => 'Test',
+ description => 'FIRST_NAME',
+ },
+ {
+ name => 'last_name',
+ value => 'User',
+ description => 'LAST_NAME',
+ },
+ ],
+ user_title => 'MR',
+ },
+ )
+{
+ subtest $test->{desc} => sub {
+ $mech->host( $test->{host} );
+
+ $mech->log_out_ok;
+ $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"
+ );
+
+ my $fields = $mech->visible_form_values('mapSkippedForm');
+ if ( $test->{fms_extra_title} ) {
+ ok exists( $fields->{fms_extra_title} ), 'user title field displayed';
+ } else {
+ ok !exists( $fields->{fms_extra_title} ), 'user title field not displayed';
+ }
+ if ( $test->{first_name} ) {
+ ok exists( $fields->{first_name} ), 'first name field displayed';
+ ok exists( $fields->{last_name} ), 'last name field displayed';
+ ok !exists( $fields->{name} ), 'no name field displayed';
+ }
+ else {
+ ok !exists( $fields->{first_name} ),
+ 'first name field not displayed';
+ ok !exists( $fields->{last_name} ), 'last name field not displayed';
+ ok exists( $fields->{name} ), 'name field displayed';
+ }
+
+ my $submission_fields = {
+ title => "Test Report",
+ detail => 'Test report details.',
+ photo => '',
+ email => 'firstlast@example.com',
+ may_show_name => '1',
+ phone => '07903 123 456',
+ category => 'Trees',
+ password_register => '',
+ };
+
+ $submission_fields->{fms_extra_title} = $test->{fms_extra_title}
+ if $test->{fms_extra_title};
+
+ if ( $test->{first_name} ) {
+ $submission_fields->{first_name} = $test->{first_name};
+ $submission_fields->{last_name} = $test->{last_name};
+ }
+ else {
+ $submission_fields->{name} = 'Test User';
+ }
+
+ $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";
+
+ my ($url) = $email->body =~ m{(https?://\S+)};
+ ok $url, "extracted confirm url '$url'";
+
+ # confirm token in order to update the user details
+ $mech->get_ok($url);
+
+ my $user =
+ FixMyStreet::App->model('DB::User')
+ ->find( { email => 'firstlast@example.com' } );
+
+ my $report = $user->problems->first;
+ ok $report, "Found the report";
+ my $extras = $report->extra;
+ is $user->title, $test->{'user_title'}, 'user title correct';
+ is_deeply $extras, $test->{extra}, 'extra contains correct values';
+
+ $user->problems->delete;
+ $user->alerts->delete;
+ $user->delete;
+ };
+}
+
$contact1->delete;
$contact2->delete;
$contact3->delete;
+$contact4->delete;
+$contact5->delete;
done_testing();
diff --git a/t/app/controller/report_updates.t b/t/app/controller/report_updates.t
index 9c2b0861b..0337a881b 100644
--- a/t/app/controller/report_updates.t
+++ b/t/app/controller/report_updates.t
@@ -872,7 +872,11 @@ for my $test (
is_deeply $values, $test->{initial_values}, 'initial form values';
- is $mech->extract_problem_banner->{text}, $test->{initial_banner}, 'initial banner';
+ if ( !defined( $test->{initial_banner} ) ) {
+ is $mech->extract_problem_banner->{text}, undef, 'initial banner';
+ } else {
+ like $mech->extract_problem_banner->{text}, qr/@{[ $test->{initial_banner} ]}/i, 'initial banner';
+ }
$mech->submit_form_ok(
{
@@ -883,7 +887,11 @@ for my $test (
is $mech->uri->path, "/report/" . $report_id, "redirected to report page";
- is $mech->extract_problem_banner->{text}, $test->{endstate_banner}, 'submitted banner';
+ if ( !defined( $test->{endstate_banner} ) ) {
+ is $mech->extract_problem_banner->{text}, undef, 'endstate banner';
+ } else {
+ like $mech->extract_problem_banner->{text}, qr/@{[ $test->{endstate_banner} ]}/i, 'endstate banner';
+ }
$mech->email_count_is(0);
@@ -1029,8 +1037,12 @@ foreach my $test (
is_deeply $values, $test->{initial_values}, 'initial form values';
- is $mech->extract_problem_banner->{text}, $test->{initial_banner},
- 'initial banner';
+ if ( !defined( $test->{initial_banner} ) ) {
+ is $mech->extract_problem_banner->{text}, undef, 'initial banner';
+ } else {
+ like $mech->extract_problem_banner->{text}, qr/@{[ $test->{initial_banner} ]}/i,
+ 'initial banner';
+ }
$mech->submit_form_ok( { with_fields => $test->{fields}, },
'submit update' );
diff --git a/t/app/controller/reports.t b/t/app/controller/reports.t
index 58803d778..801dbeddb 100644
--- a/t/app/controller/reports.t
+++ b/t/app/controller/reports.t
@@ -17,14 +17,14 @@ $mech->follow_link_ok( { text_regex => qr/Birmingham/ } );
SKIP: {
skip( "Need 'emptyhomes' in ALLOWED_COBRANDS config", 8 )
- unless FixMyStreet::App->config->{ALLOWED_COBRANDS} =~ m{emptyhomes};
+ 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');
skip( "Need 'fiksgatami' in ALLOWED_COBRANDS config", 8 )
- unless FixMyStreet::App->config->{ALLOWED_COBRANDS} =~ m{fiksgatami};
+ 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');
diff --git a/t/app/model/problem.t b/t/app/model/problem.t
index 638e89200..a7415851b 100644
--- a/t/app/model/problem.t
+++ b/t/app/model/problem.t
@@ -7,6 +7,7 @@ use Test::More;
use FixMyStreet;
use FixMyStreet::App;
+use FixMyStreet::TestMech;
use mySociety::Locale;
mySociety::Locale::gettext_domain('FixMyStreet');
@@ -16,8 +17,8 @@ my $problem_rs = FixMyStreet::App->model('DB::Problem');
my $problem = $problem_rs->new(
{
postcode => 'EH99 1SP',
- latitude => 1,
- longitude => 1,
+ latitude => '51.5016605453401',
+ longitude => '-0.142497580865087',
areas => 1,
title => '',
detail => '',
@@ -345,6 +346,131 @@ for my $test (
};
}
+my $mech = FixMyStreet::TestMech->new();
+
+FixMyStreet::App->model('DB::Contact')->find_or_create(
+ {
+ area_id => 2651,
+ category => 'potholes',
+ email => 'test@example.org',
+ confirmed => 1,
+ deleted => 0,
+ editor => 'test',
+ whenedited => \'ms_current_timestamp()',
+ note => '',
+ }
+);
+
+FixMyStreet::App->model('DB::Contact')->find_or_create(
+ {
+ area_id => 2226,
+ category => 'potholes',
+ email => '2226@example.org',
+ confirmed => 1,
+ deleted => 0,
+ editor => 'test',
+ whenedited => \'ms_current_timestamp()',
+ note => '',
+ }
+);
+
+FixMyStreet::App->model('DB::Contact')->find_or_create(
+ {
+ area_id => 2326,
+ category => 'potholes',
+ email => '2326@example.org',
+ confirmed => 1,
+ deleted => 0,
+ editor => 'test',
+ whenedited => \'ms_current_timestamp()',
+ note => '',
+ }
+);
+
+foreach my $test ( {
+ desc => 'sends an email',
+ unset_whendef => 1,
+ email_count => 1,
+ email => 'system_user@example.com',
+ name => 'Andrew Smith',
+ dear => qr'Dear City of Edinburgh Council',
+ to => qr'City of Edinburgh Council',
+ council => 2651,
+ },
+ {
+ desc => 'no email sent if no unsent problems',
+ unset_whendef => 0,
+ email_count => 0,
+ email => 'system_user@example.com',
+ name => 'Andrew Smith',
+ council => 2651,
+ },
+ {
+ desc => 'email to two tier council',
+ unset_whendef => 1,
+ email_count => 1,
+ email => 'system_user@example.com',
+ name => 'Andrew Smith',
+ to => qr'Gloucestershire County Council.*Cheltenham Borough Council',
+ dear => qr'Dear Gloucestershire County Council and Cheltenham Borough',
+ council => '2226,2326',
+ multiple => 1,
+ },
+ {
+ desc => 'email to two tier council with one missing details',
+ unset_whendef => 1,
+ email_count => 1,
+ email => 'system_user@example.com',
+ name => 'Andrew Smith',
+ to => qr'Gloucestershire County Council',
+ dear => qr'Dear Gloucestershire County Council,',
+ council => '2226|2649',
+ missing => qr'problem might be the responsibility of Fife.*Council'ms,
+ },
+) {
+ subtest $test->{ desc } => sub {
+ $mech->clear_emails_ok;
+
+ FixMyStreet::App->model('DB::Problem')->search(
+ {
+ whensent => undef
+ }
+ )->update( { whensent => \'ms_current_timestamp()' } );
+
+ $problem->discard_changes;
+ $problem->update( {
+ council => $test->{ council },
+ state => 'confirmed',
+ confirmed => \'ms_current_timestamp()',
+ whensent => $test->{ unset_whendef } ? undef : \'ms_current_timestamp()',
+ category => 'potholes',
+ name => $test->{ name },
+ } );
+
+ FixMyStreet::App->model('DB::Problem')->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';
+ 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';
+
+ if ( $test->{multiple} ) {
+ like $email->body, qr/This email has been sent to several councils /, 'multiple council text correct';
+ } elsif ( $test->{ missing } ) {
+ like $email->body, $test->{ missing }, 'missing council information correct';
+ }
+
+ $problem->discard_changes;
+ ok defined( $problem->whensent ), 'whensent set';
+ }
+ };
+}
+
$problem->comments->delete;
$problem->delete;
$user->delete;
diff --git a/t/app/uri_for.t b/t/app/uri_for.t
index 51a6e8a0e..eecf30e32 100644
--- a/t/app/uri_for.t
+++ b/t/app/uri_for.t
@@ -78,31 +78,36 @@ is(
'FiksGataMi url with lat not zoom'
);
-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'
-);
+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/get_council_sender.t b/t/cobrand/get_council_sender.t
new file mode 100644
index 000000000..9004a47f5
--- /dev/null
+++ b/t/cobrand/get_council_sender.t
@@ -0,0 +1,30 @@
+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 $c->get_council_sender( '1000', { type => 'DIS' } ), 'Email', 'defaults to email';
+is $c->get_council_sender( '1000', { type => 'LBO' } ), 'London', 'returns london report it if London borough';
+
+my $conf = FixMyStreet::App->model('DB::Open311Conf')->find_or_create(
+ area_id => 1000,
+ endpoint => '',
+ send_method => 'TestMethod'
+);
+
+is $c->get_council_sender( '1000', { type => 'LBO' } ), 'TestMethod', 'uses send_method in preference to London';
+is $c->get_council_sender( '1000', { type => 'DIS' } ), 'TestMethod', 'uses send_method in preference to Email';
+
+$conf->delete;
+
+done_testing();
diff --git a/t/open311.t b/t/open311.t
index 30de330b6..814b11f82 100644
--- a/t/open311.t
+++ b/t/open311.t
@@ -5,6 +5,10 @@ use warnings;
use Test::More;
use Test::Warn;
use FixMyStreet::App;
+use CGI::Simple;
+use HTTP::Response;
+use DateTime;
+use DateTime::Format::W3CDTF;
use FindBin;
use lib "$FindBin::Bin/../perllib";
@@ -39,4 +43,451 @@ my $expected_error = qr{.*request failed: 500 Can.t connect to 192.168.50.1:80 \
warning_like {$o2->send_service_request( $p, { url => 'http://example.com/' }, 1 )} $expected_error, 'warning generated on failed call';
+my $dt = DateTime->now();
+
+my $user = FixMyStreet::App->model('DB::User')->new( {
+ name => 'Test User',
+ email => 'test@example.com',
+} );
+
+my $problem = FixMyStreet::App->model('DB::Problem')->new( {
+ id => 80,
+ external_id => 81,
+ state => 'confirmed',
+ title => 'a problem',
+ detail => 'problem detail',
+ category => 'pothole',
+ latitude => 1,
+ longitude => 2,
+ user => $user,
+} );
+
+subtest 'posting service request' => sub {
+ 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 $req = $o->test_req_used;
+
+ my $description = <<EOT;
+title: a problem
+
+detail: problem detail
+
+url: http://example.com/report/1
+
+Submitted via FixMyStreet
+EOT
+;
+
+ my $c = CGI::Simple->new( $results->{ req }->content );
+
+ 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->param('service_code'), 'pothole', 'service code correct';
+};
+
+subtest 'posting service request with basic_description' => sub {
+ 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>',
+ { basic_description => 1 },
+ );
+
+ 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';
+};
+
+for my $test (
+ {
+ desc => 'extra values in service request',
+ extra => [
+ {
+ name => 'title',
+ value => 'A title',
+ }
+ ],
+ params => [
+ [ 'attribute[title]', 'A title', 'extra paramater used correctly' ]
+ ]
+ },
+ {
+ desc => 'first and last names in extra used correctly',
+ extra => [
+ {
+ name => 'first_name',
+ value => 'First',
+ },
+ {
+ name => 'last_name',
+ value => 'Last',
+ },
+ ],
+ params => [
+ [ 'first_name', 'First', 'first name correct' ],
+ [ 'last_name', 'Last', 'last name correct' ],
+ [ 'attribute[first_name]', undef, 'no first_name attribute param' ],
+ [ 'attribute[last_name]', 'Last', 'last_name attribute param correct' ],
+ ],
+ },
+ {
+ title => 'magic fms_extra parameters handled correctly',
+ extra => [
+ {
+ name => 'fms_extra_title',
+ value => 'Extra title',
+ }
+ ],
+ params => [
+ [
+ 'attribute[title]',
+ 'Extra title',
+ 'fms_extra extra param used correctly'
+ ]
+ ],
+ },
+ )
+{
+ subtest $test->{desc} => sub {
+ $problem->extra( $test->{extra} );
+
+ 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 $req = $o->test_req_used;
+ my $c = CGI::Simple->new( $results->{req}->content );
+
+ for my $param ( @{ $test->{params} } ) {
+ is $c->param( $param->[0] ), $param->[1], $param->[2];
+ }
+ };
+}
+
+my $comment = FixMyStreet::App->model('DB::Comment')->new( {
+ id => 38362,
+ user => $user,
+ problem => $problem,
+ anonymous => 0,
+ text => 'this is a comment',
+ confirmed => $dt,
+ extra => { title => 'Mr', email_alerts_requested => 0 },
+} );
+
+subtest 'basic 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>' );
+
+ 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';
+ is $c->param('email'), 'test@example.com', 'email correct';
+ is $c->param('status'), 'OPEN', 'status correct';
+ is $c->param('service_request_id_ext'), 80, 'external request id correct';
+ is $c->param('service_request_id'), 81, 'request id correct';
+ is $c->param('public_anonymity_required'), 'FALSE', 'anon status correct';
+ is $c->param('updated_datetime'), DateTime::Format::W3CDTF->format_datetime($dt), 'correct date';
+ is $c->param('title'), 'Mr', 'correct title';
+ is $c->param('last_name'), 'User', 'correct first name';
+ is $c->param('first_name'), 'Test', 'correct second name';
+ is $c->param('email_alerts_requested'), 'FALSE', 'email alerts flag correct';
+ is $c->param('media_url'), undef, 'no media url';
+};
+
+subtest 'check media url set' => sub {
+ $comment->photo(1);
+ $comment->cobrand('fixmystreet');
+
+ 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>' );
+
+ is $results->{ res }, 248, 'got update id';
+
+ my $req = $o->test_req_used;
+
+ 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';
+};
+
+foreach my $test (
+ {
+ desc => 'comment with fixed state sends status of CLOSED',
+ state => 'fixed',
+ anon => 0,
+ status => 'CLOSED',
+ },
+ {
+ desc => 'comment with fixed - user state sends status of CLOSED',
+ state => 'fixed - user',
+ anon => 0,
+ status => 'CLOSED',
+ },
+ {
+ desc => 'comment with fixed - council state sends status of CLOSED',
+ state => 'fixed - council',
+ anon => 0,
+ status => 'CLOSED',
+ },
+ {
+ desc => 'comment with closed state sends status of CLOSED',
+ state => 'closed',
+ anon => 0,
+ status => 'CLOSED',
+ },
+ {
+ desc => 'comment with investigating state sends status of OPEN',
+ state => 'investigating',
+ anon => 0,
+ status => 'OPEN',
+ },
+ {
+ desc => 'comment with planned state sends status of OPEN',
+ state => 'planned',
+ anon => 0,
+ status => 'OPEN',
+ },
+ {
+ desc => 'comment with in progress state sends status of OPEN',
+ state => 'in progress',
+ anon => 0,
+ status => 'OPEN',
+ },
+ {
+ desc => 'anonymous commment sets public_anonymity_required to true',
+ state => 'confirmed',
+ anon => 1,
+ status => 'OPEN',
+ },
+) {
+ subtest $test->{desc} => sub {
+ $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>' );
+
+ my $c = CGI::Simple->new( $results->{ req }->content );
+ is $c->param('status'), $test->{status}, 'correct status';
+ is $c->param('public_anonymity_required'), $test->{anon} ? 'TRUE' : 'FALSE', 'correct anonymity';
+ };
+}
+
+
+for my $test (
+ {
+ desc => 'update name name taken from comment over user',
+ comment_name => 'First Last',
+ user_name => 'Personal Family',
+ extra => undef,
+ first_name => 'First',
+ last_name => 'Last'
+ },
+ {
+ desc => 'update name name taken from user if no comment name',
+ comment_name => '',
+ user_name => 'Personal Family',
+ extra => undef,
+ first_name => 'Personal',
+ last_name => 'Family'
+ },
+ {
+ desc => 'update name taken from extra if available',
+ comment_name => 'First Last',
+ user_name => 'Personal Family',
+ extra => { first_name => 'Forename', last_name => 'Surname' },
+ first_name => 'Forename',
+ last_name => 'Surname'
+ },
+ )
+{
+ subtest $test->{desc} => sub {
+ $comment->name( $test->{comment_name} );
+ $user->name( $test->{user_name} );
+ $comment->extra( $test->{ extra } );
+
+ 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('first_name'), $test->{first_name}, 'first name correct';
+ is $c->param('last_name'), $test->{last_name}, 'last name correct';
+ };
+}
+
+for my $test (
+ {
+ desc => 'use lat long forces lat long even if map not used',
+ use_latlong => 1,
+ postcode => 'EH99 1SP',
+ used_map => 0,
+ includes_latlong => 1,
+ },
+ {
+ desc => 'no use lat long and no map sends address instead of lat long',
+ use_latlong => 0,
+ postcode => 'EH99 1SP',
+ used_map => 0,
+ includes_latlong => 0,
+ },
+ {
+ desc => 'no use lat long but used map sends lat long',
+ use_latlong => 0,
+ postcode => 'EH99 1SP',
+ used_map => 1,
+ includes_latlong => 1,
+ },
+ {
+ desc => 'no use lat long, no map and no postcode sends lat long',
+ use_latlong => 0,
+ postcode => '',
+ used_map => 0,
+ includes_latlong => 1,
+ },
+ {
+ desc => 'no use lat long, no map and no postcode sends lat long',
+ use_latlong => 0,
+ notpinpoint => 1,
+ postcode => '',
+ used_map => 0,
+ includes_latlong => 0,
+ }
+) {
+ subtest $test->{desc} => sub {
+ my $extra = { url => 'http://example.com/report/1', };
+ $problem->used_map( $test->{used_map} );
+ $problem->postcode( $test->{postcode} );
+
+ 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>',
+ { always_send_latlong => $test->{use_latlong},
+ send_notpinpointed => $test->{notpinpoint} },
+ );
+
+ is $results->{ res }, 248, 'got request id';
+
+ my $c = CGI::Simple->new( $results->{ req }->content );
+
+ if ( $test->{notpinpoint} ) {
+ is $c->param('lat'), undef, 'no latitude';
+ is $c->param('long'), undef, 'no longitude';
+ is $c->param('address_string'), undef, 'no address';
+ is $c->param('address_id'), '#NOTPINPOINTED#', 'has not pinpointed';
+ } elsif ( $test->{includes_latlong} ) {
+ ok $c->param('lat'), 'has latitude';
+ ok $c->param('long'), 'has longitude';
+ is $c->param('address_string'), undef, 'no address';
+ } else {
+ is $c->param('lat'), undef, 'no latitude';
+ is $c->param('long'), undef, 'no latitude';
+ is $c->param('address_string'), $test->{postcode}, 'has address';
+ }
+ };
+}
+
+subtest 'No update id in reponse' => sub {
+ my $results;
+ warning_like {
+ $results = make_update_req( $comment, '<?xml version="1.0" encoding="utf-8"?><service_request_updates><request_update><update_id></update_id></request_update></service_request_updates>' )
+ } qr/Failed to submit comment \d+ over Open311/, 'correct error message';
+
+ is $results->{ res }, 0, 'No update_id is a failure';
+};
+
+subtest 'error reponse' => sub {
+ my $results;
+ warning_like {
+ $results = make_update_req( $comment, '<?xml version="1.0" encoding="utf-8"?><errors><error><code>400</code><description>There was an error</description</error></errors>' )
+ } qr/Failed to submit comment \d+ over Open311.*There was an error/, 'correct error messages';
+
+ is $results->{ res }, 0, 'error in response is a failure';
+};
+
done_testing();
+
+sub make_update_req {
+ my $comment = shift;
+ my $xml = shift;
+
+ return make_req(
+ {
+ object => $comment,
+ xml => $xml,
+ method => 'post_service_request_update',
+ path => 'update.xml',
+ }
+ );
+}
+
+sub make_service_req {
+ my $problem = shift;
+ my $extra = shift;
+ my $service_code = shift;
+ my $xml = shift;
+ my $open311_args = shift || {};
+
+ return make_req(
+ {
+ object => $problem,
+ xml => $xml,
+ method => 'send_service_request',
+ path => 'requests.xml',
+ method_args => [ $extra, $service_code ],
+ open311_conf => $open311_args,
+ }
+ );
+}
+
+sub make_req {
+ my $args = shift;
+
+ my $object = $args->{object};
+ my $xml = $args->{xml};
+ my $method = $args->{method};
+ my $path = $args->{path};
+ my %open311_conf = %{ $args->{open311_conf} || {} };
+ my @args = @{ $args->{method_args} || [] };
+
+ $open311_conf{'test_mode'} = 1;
+ $open311_conf{'end_point'} = 'http://localhost/o311';
+ my $o =
+ Open311->new( %open311_conf );
+
+ my $test_res = HTTP::Response->new();
+ $test_res->code(200);
+ $test_res->message('OK');
+ $test_res->content($xml);
+
+ $o->test_get_returns( { $path => $test_res } );
+
+ my $res = $o->$method($object, @args);
+
+ my $req = $o->test_req_used;
+
+ return { res => $res, req => $req };
+}
diff --git a/t/open311/getservicerequestupdates.t b/t/open311/getservicerequestupdates.t
new file mode 100644
index 000000000..7ec8d5ae0
--- /dev/null
+++ b/t/open311/getservicerequestupdates.t
@@ -0,0 +1,537 @@
+#!/usr/bin/env perl
+
+use strict;
+use warnings;
+use Test::More;
+use CGI::Simple;
+
+use FindBin;
+use lib "$FindBin::Bin/../perllib";
+use lib "$FindBin::Bin/../commonlib/perllib";
+
+use_ok( 'Open311' );
+
+use_ok( 'Open311::GetServiceRequestUpdates' );
+use DateTime;
+use FixMyStreet::App;
+
+my $user = FixMyStreet::App->model('DB::User')->find_or_create(
+ {
+ email => 'system_user@example.com'
+ }
+);
+
+my $requests_xml = qq{<?xml version="1.0" encoding="utf-8"?>
+<service_requests_updates>
+<request_update>
+<update_id>638344</update_id>
+<service_request_id>1</service_request_id>
+<service_request_id_ext>1</service_request_id_ext>
+<status>open</status>
+<description>This is a note</description>
+UPDATED_DATETIME
+</request_update>
+</service_requests_updates>
+};
+
+
+my $dt = DateTime->now;
+
+# basic xml -> perl object tests
+for my $test (
+ {
+ desc => 'basic parsing - element missing',
+ updated_datetime => '',
+ res => { update_id => 638344, service_request_id => 1, service_request_id_ext => 1,
+ status => 'open', description => 'This is a note' },
+ },
+ {
+ desc => 'basic parsing - empty element',
+ updated_datetime => '<updated_datetime />',
+ res => { update_id => 638344, service_request_id => 1, service_request_id_ext => 1,
+ status => 'open', description => 'This is a note', updated_datetime => {} } ,
+ },
+ {
+ desc => 'basic parsing - element with no content',
+ updated_datetime => '<updated_datetime></updated_datetime>',
+ res => { update_id => 638344, service_request_id => 1, service_request_id_ext => 1,
+ status => 'open', description => 'This is a note', updated_datetime => {} } ,
+ },
+ {
+ desc => 'basic parsing - element with content',
+ updated_datetime => sprintf( '<updated_datetime>%s</updated_datetime>', $dt ),
+ res => { update_id => 638344, service_request_id => 1, service_request_id_ext => 1,
+ status => 'open', description => 'This is a note', updated_datetime => $dt } ,
+ },
+) {
+ subtest $test->{desc} => sub {
+ my $local_requests_xml = $requests_xml;
+ $local_requests_xml =~ s/UPDATED_DATETIME/$test->{updated_datetime}/;
+
+ my $o = Open311->new( jurisdiction => 'mysociety', endpoint => 'http://example.com', test_mode => 1, test_get_returns => { 'update.xml' => $local_requests_xml } );
+
+ my $res = $o->get_service_request_updates;
+ is_deeply $res->[0], $test->{ res }, 'result looks correct';
+
+ };
+}
+
+my $problem_rs = FixMyStreet::App->model('DB::Problem');
+my $problem = $problem_rs->new(
+ {
+ postcode => 'EH99 1SP',
+ latitude => 1,
+ longitude => 1,
+ areas => 1,
+ title => '',
+ detail => '',
+ used_map => 1,
+ user_id => 1,
+ name => '',
+ state => 'confirmed',
+ service => '',
+ cobrand => 'default',
+ cobrand_data => '',
+ user => $user,
+ created => DateTime->now()->subtract( days => 1 ),
+ lastupdate => DateTime->now()->subtract( days => 1 ),
+ anonymous => 1,
+ external_id => time(),
+ council => 2482,
+ }
+);
+
+$problem->insert;
+
+for my $test (
+ {
+ desc => 'element with content',
+ updated_datetime => sprintf( '<updated_datetime>%s</updated_datetime>', $dt ),
+ description => 'This is a note',
+ external_id => 638344,
+ start_state => 'confirmed',
+ close_comment => 0,
+ 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 ),
+ description => 'This is a note',
+ external_id => 638344,
+ start_state => 'confirmed',
+ close_comment => 1,
+ 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 ),
+ description => 'This is a note',
+ external_id => 638344,
+ start_state => 'fixed - user',
+ close_comment => 0,
+ 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 ),
+ description => 'This is a note',
+ external_id => 638344,
+ start_state => 'closed',
+ close_comment => 0,
+ 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 ),
+ description => 'This is a note',
+ external_id => 638344,
+ start_state => 'closed',
+ close_comment => 1,
+ mark_fixed => 0,
+ mark_open => 0,
+ end_state => 'closed',
+ },
+) {
+ subtest $test->{desc} => sub {
+ my $local_requests_xml = $requests_xml;
+ $local_requests_xml =~ s/UPDATED_DATETIME/$test->{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};
+
+ my $o = Open311->new( jurisdiction => 'mysociety', endpoint => 'http://example.com', test_mode => 1, test_get_returns => { 'update.xml' => $local_requests_xml } );
+
+ $problem->comments->delete;
+ $problem->lastupdate( DateTime->now()->subtract( days => 1 ) );
+ $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 );
+
+ is $problem->comments->count, 1, 'comment count';
+ $problem->discard_changes;
+
+ my $c = FixMyStreet::App->model('DB::Comment')->search( { external_id => $test->{external_id} } )->first;
+ ok $c, 'comment exists';
+ is $c->text, $test->{description}, 'text correct';
+ is $c->mark_fixed, $test->{mark_fixed}, 'mark_closed correct';
+ is $c->problem_state, $test->{problem_state}, 'problem_state correct';
+ is $c->mark_open, $test->{mark_open}, 'mark_open correct';
+ is $problem->state, $test->{end_state}, 'correct problem state';
+ };
+}
+
+
+foreach my $test (
+ {
+ desc => 'date for comment correct',
+ updated_datetime => sprintf( '<updated_datetime>%s</updated_datetime>', $dt ),
+ external_id => 638344,
+ },
+) {
+ subtest $test->{desc} => sub {
+ my $dt = DateTime->now();
+ $dt->subtract( minutes => 10 );
+ my $local_requests_xml = $requests_xml;
+
+ my $updated = sprintf( '<updated_datetime>%s</updated_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>#;
+
+ my $o = Open311->new( jurisdiction => 'mysociety', endpoint => 'http://example.com', test_mode => 1, test_get_returns => { 'update.xml' => $local_requests_xml } );
+
+ $problem->comments->delete;
+
+ my $council_details = { areaid => 2482 };
+ my $update = Open311::GetServiceRequestUpdates->new( system_user => $user );
+ $update->update_comments( $o, $council_details );
+
+ my $comment = $problem->comments->first;
+ is $comment->created, $dt, 'created date set to date from XML';
+ is $comment->confirmed, $dt, 'confirmed date set to date from XML';
+ };
+}
+
+my $problem2 = $problem_rs->new(
+ {
+ postcode => 'EH99 1SP',
+ latitude => 1,
+ longitude => 1,
+ areas => 1,
+ title => '',
+ detail => '',
+ used_map => 1,
+ user_id => 1,
+ name => '',
+ state => 'confirmed',
+ service => '',
+ cobrand => 'default',
+ cobrand_data => '',
+ user => $user,
+ created => DateTime->now(),
+ lastupdate => DateTime->now(),
+ anonymous => 1,
+ external_id => $problem->external_id,
+ council => 2651,
+ }
+);
+
+$problem2->insert();
+$problem->comments->delete;
+$problem2->comments->delete;
+
+for my $test (
+ {
+ desc => 'identical external_ids on problem resolved using council',
+ updated_datetime => sprintf( '<updated_datetime>%s</updated_datetime>', $dt ),
+ external_id => 638344,
+ area_id => 2651,
+ request_id => $problem2->external_id,
+ request_id_ext => $problem2->id,
+ p1_comments => 0,
+ p2_comments => 1,
+ },
+ {
+ desc => 'identical external_ids on comments resolved',
+ updated_datetime => sprintf( '<updated_datetime>%s</updated_datetime>', $dt ),
+ external_id => 638344,
+ area_id => 2482,
+ request_id => $problem->external_id,
+ request_id_ext => $problem->id,
+ p1_comments => 1,
+ p2_comments => 1,
+ },
+) {
+ subtest $test->{desc} => sub {
+ my $local_requests_xml = $requests_xml;
+ $local_requests_xml =~ s/UPDATED_DATETIME/$test->{updated_datetime}/;
+ $local_requests_xml =~ s#<service_request_id>\d+</service_request_id>#<service_request_id>$test->{request_id}</service_request_id>#;
+ $local_requests_xml =~ s#<service_request_id_ext>\d+</service_request_id_ext>#<service_request_id_ext>$test->{request_id_ext}</service_request_id_ext>#;
+
+ my $o = Open311->new( jurisdiction => 'mysociety', endpoint => 'http://example.com', test_mode => 1, test_get_returns => { 'update.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 );
+
+ is $problem->comments->count, $test->{p1_comments}, 'comment count for first problem';
+ is $problem2->comments->count, $test->{p2_comments}, 'comment count for second problem';
+ };
+}
+
+subtest 'using start and end date' => sub {
+ my $local_requests_xml = $requests_xml;
+ my $o = Open311->new( jurisdiction => 'mysociety', endpoint => 'http://example.com', test_mode => 1, test_get_returns => { 'update.xml' => $local_requests_xml } );
+
+ my $start_dt = DateTime->now();
+ $start_dt->subtract( days => 1 );
+ my $end_dt = DateTime->now();
+
+
+ my $update = Open311::GetServiceRequestUpdates->new(
+ system_user => $user,
+ start_date => $start_dt,
+ );
+
+ my $res = $update->update_comments( $o );
+ is $res, 0, 'returns 0 if start but no end date';
+
+ $update = Open311::GetServiceRequestUpdates->new(
+ system_user => $user,
+ end_date => $end_dt,
+ );
+
+ $res = $update->update_comments( $o );
+ is $res, 0, 'returns 0 if end but no start date';
+
+ $update = Open311::GetServiceRequestUpdates->new(
+ system_user => $user,
+ start_date => $start_dt,
+ end_date => $end_dt,
+ );
+
+ my $council_details = { areaid => 2482 };
+ $update->update_comments( $o, $council_details );
+
+ my $start = $start_dt . '';
+ my $end = $end_dt . '';
+
+ my $uri = URI->new( $o->test_uri_used );
+ my $c = CGI::Simple->new( $uri->query );
+
+ is $c->param('start_date'), $start, 'start date used';
+ is $c->param('end_date'), $end, 'end date used';
+};
+
+subtest 'check that existing comments are not duplicated' => sub {
+ my $requests_xml = qq{<?xml version="1.0" encoding="utf-8"?>
+ <service_requests_updates>
+ <request_update>
+ <update_id>638344</update_id>
+ <service_request_id>@{[ $problem->external_id ]}</service_request_id>
+ <service_request_id_ext>@{[ $problem->id ]}</service_request_id_ext>
+ <status>open</status>
+ <description>This is a note</description>
+ <updated_datetime>UPDATED_DATETIME</updated_datetime>
+ </request_update>
+ <request_update>
+ <update_id>638354</update_id>
+ <service_request_id>@{[ $problem->external_id ]}</service_request_id>
+ <service_request_id_ext>@{[ $problem->id ]}</service_request_id_ext>
+ <status>open</status>
+ <description>This is a different note</description>
+ <updated_datetime>UPDATED_DATETIME2</updated_datetime>
+ </request_update>
+ </service_requests_updates>
+ };
+
+ $problem->comments->delete;
+
+ my $comment = FixMyStreet::App->model('DB::Comment')->new(
+ {
+ problem => $problem,
+ external_id => 638344,
+ text => 'This is a note',
+ user => $user,
+ state => 'confirmed',
+ mark_fixed => 0,
+ anonymous => 0,
+ confirmed => $dt,
+ }
+ );
+ $comment->insert;
+
+ is $problem->comments->count, 1, 'one comment before fetching updates';
+
+ $requests_xml =~ s/UPDATED_DATETIME2/$dt/;
+ $requests_xml =~ s/UPDATED_DATETIME/@{[ $comment->confirmed ]}/;
+
+ my $o = Open311->new( jurisdiction => 'mysociety', endpoint => 'http://example.com', test_mode => 1, test_get_returns => { 'update.xml' => $requests_xml } );
+
+ my $update = Open311::GetServiceRequestUpdates->new(
+ system_user => $user,
+ );
+
+ my $council_details = { areaid => 2482 };
+ $update->update_comments( $o, $council_details );
+
+ $problem->discard_changes;
+ is $problem->comments->count, 2, 'two comments after fetching updates';
+
+ $update->update_comments( $o, $council_details );
+ $problem->discard_changes;
+ is $problem->comments->count, 2, 're-fetching updates does not add comments';
+
+ $problem->comments->delete;
+ $update->update_comments( $o, $council_details );
+ $problem->discard_changes;
+ is $problem->comments->count, 2, 'if comments are deleted then they are added';
+};
+
+foreach my $test ( {
+ desc => 'check that closed and then open comment results in correct state',
+ dt1 => $dt->subtract( hours => 1 ),
+ dt2 => $dt,
+ },
+ {
+ desc => 'check that old comments do not change problem status',
+ dt1 => $dt->subtract( hours => 2 ),
+ dt2 => $dt,
+ }
+) {
+ subtest $test->{desc} => sub {
+ my $requests_xml = qq{<?xml version="1.0" encoding="utf-8"?>
+ <service_requests_updates>
+ <request_update>
+ <update_id>638344</update_id>
+ <service_request_id>@{[ $problem->external_id ]}</service_request_id>
+ <service_request_id_ext>@{[ $problem->id ]}</service_request_id_ext>
+ <status>closed</status>
+ <description>This is a note</description>
+ <updated_datetime>UPDATED_DATETIME</updated_datetime>
+ </request_update>
+ <request_update>
+ <update_id>638354</update_id>
+ <service_request_id>@{[ $problem->external_id ]}</service_request_id>
+ <service_request_id_ext>@{[ $problem->id ]}</service_request_id_ext>
+ <status>open</status>
+ <description>This is a different note</description>
+ <updated_datetime>UPDATED_DATETIME2</updated_datetime>
+ </request_update>
+ </service_requests_updates>
+ };
+
+ $problem->comments->delete;
+ $problem->state( 'confirmed' );
+ $problem->lastupdate( $dt->subtract( hours => 3 ) );
+ $problem->update;
+
+ $requests_xml =~ s/UPDATED_DATETIME/$test->{dt1}/;
+ $requests_xml =~ s/UPDATED_DATETIME2/$test->{dt2}/;
+
+ my $o = Open311->new( jurisdiction => 'mysociety', endpoint => 'http://example.com', test_mode => 1, test_get_returns => { 'update.xml' => $requests_xml } );
+
+ my $update = Open311::GetServiceRequestUpdates->new(
+ system_user => $user,
+ );
+
+ my $council_details = { areaid => 2482 };
+ $update->update_comments( $o, $council_details );
+
+ $problem->discard_changes;
+ is $problem->comments->count, 2, 'two comments after fetching updates';
+ is $problem->state, 'confirmed', 'correct problem status';
+ };
+}
+
+foreach my $test ( {
+ desc => 'normally alerts are not suppressed',
+ suppress_alerts => 0,
+ },
+ {
+ desc => 'alerts suppressed if suppress_alerts set',
+ suppress_alerts => 1,
+ }
+) {
+ subtest $test->{desc} => sub {
+ my $requests_xml = qq{<?xml version="1.0" encoding="utf-8"?>
+ <service_requests_updates>
+ <request_update>
+ <update_id>638344</update_id>
+ <service_request_id>@{[ $problem->external_id ]}</service_request_id>
+ <service_request_id_ext>@{[ $problem->id ]}</service_request_id_ext>
+ <status>closed</status>
+ <description>This is a note</description>
+ <updated_datetime>UPDATED_DATETIME</updated_datetime>
+ </request_update>
+ </service_requests_updates>
+ };
+
+ $problem->comments->delete;
+ $problem->state( 'confirmed' );
+ $problem->lastupdate( $dt->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,
+ } );
+
+ $requests_xml =~ s/UPDATED_DATETIME/$dt/;
+
+ my $o = Open311->new( jurisdiction => 'mysociety', endpoint => 'http://example.com', test_mode => 1, test_get_returns => { 'update.xml' => $requests_xml } );
+
+ my $update = Open311::GetServiceRequestUpdates->new(
+ system_user => $user,
+ suppress_alerts => $test->{suppress_alerts},
+ );
+
+ my $council_details = { areaid => 2482 };
+ $update->update_comments( $o, $council_details );
+ $problem->discard_changes;
+
+ my $alerts_sent = FixMyStreet::App->model('DB::AlertSent')->search(
+ {
+ alert_id => $alert->id,
+ parameter => $problem->comments->first->id,
+ }
+ );
+
+ if ( $test->{suppress_alerts} ) {
+ ok $alerts_sent->count(), 'alerts suppressed';
+ } else {
+ is $alerts_sent->count(), 0, 'alerts not suppressed';
+ }
+
+ $alerts_sent->delete;
+ $alert->delete;
+ }
+}
+
+$problem2->comments->delete();
+$problem->comments->delete();
+$problem2->delete;
+$problem->delete;
+$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 bdb7404f9..fbd729f3a 100644
--- a/t/open311/populate-service-list.t
+++ b/t/open311/populate-service-list.t
@@ -203,6 +203,304 @@ subtest 'check conflicting contacts not changed' => sub {
is $contact_count, 4, 'correct number of contacts';
};
+subtest 'check meta data population' => sub {
+ my $processor = Open311::PopulateServiceList->new( council_list => [] );
+
+ my $meta_xml = '<?xml version="1.0" encoding="utf-8"?>
+<service_definition>
+ <service_code>100</service_code>
+ <attributes>
+ <attribute>
+ <variable>true</variable>
+ <code>type</code>
+ <datatype>string</datatype>
+ <required>true</required>
+ <datatype_description>Type of bin</datatype_description>
+ <order>1</order>
+ <description>Type of bin</description>
+ </attribute>
+ </attributes>
+</service_definition>
+ ';
+
+ my $contact = FixMyStreet::App->model('DB::Contact')->find_or_create(
+ {
+ area_id => 1,
+ email => '001',
+ category => 'Bins left out 24x7',
+ confirmed => 1,
+ deleted => 0,
+ editor => $0,
+ whenedited => \'ms_current_timestamp()',
+ note => 'test contact',
+ }
+ );
+
+ my $o = Open311->new(
+ jurisdiction => 'mysociety',
+ endpoint => 'http://example.com',
+ test_mode => 1,
+ test_get_returns => { 'services/100.xml' => $meta_xml }
+ );
+
+ my $council = FixMyStreet::App->model('DB::Open311conf')->new( {
+ area_id => 2482
+ } );
+
+ $processor->_current_open311( $o );
+ $processor->_current_council( $council );
+ $processor->_current_service( { service_code => 100 } );
+
+ $processor->_add_meta_to_contact( $contact );
+
+ my $extra = [ {
+ variable => 'true',
+ code => 'type',
+ datatype => 'string',
+ required => 'true',
+ datatype_description => 'Type of bin',
+ order => 1,
+ description => 'Type of bin'
+
+ } ];
+
+ $contact->discard_changes;
+
+ is_deeply $contact->extra, $extra, 'meta data saved';
+};
+
+subtest 'check attribute ordering' => sub {
+ my $processor = Open311::PopulateServiceList->new( council_list => [] );
+
+ my $meta_xml = '<?xml version="1.0" encoding="utf-8"?>
+<service_definition>
+ <service_code>100</service_code>
+ <attributes>
+ <attribute>
+ <variable>true</variable>
+ <code>type</code>
+ <datatype>string</datatype>
+ <required>true</required>
+ <datatype_description>Type of bin</datatype_description>
+ <order>1</order>
+ <description>Type of bin</description>
+ </attribute>
+ <attribute>
+ <variable>true</variable>
+ <code>colour</code>
+ <datatype>string</datatype>
+ <required>true</required>
+ <datatype_description>Colour of bin</datatype_description>
+ <order>3</order>
+ <description>Colour of bin</description>
+ </attribute>
+ <attribute>
+ <variable>true</variable>
+ <code>size</code>
+ <datatype>string</datatype>
+ <required>true</required>
+ <datatype_description>Size of bin</datatype_description>
+ <order>2</order>
+ <description>Size of bin</description>
+ </attribute>
+ </attributes>
+</service_definition>
+ ';
+
+ my $contact = FixMyStreet::App->model('DB::Contact')->find_or_create(
+ {
+ area_id => 1,
+ email => '001',
+ category => 'Bins left out 24x7',
+ confirmed => 1,
+ deleted => 0,
+ editor => $0,
+ whenedited => \'ms_current_timestamp()',
+ note => 'test contact',
+ }
+ );
+
+ my $o = Open311->new(
+ jurisdiction => 'mysociety',
+ endpoint => 'http://example.com',
+ test_mode => 1,
+ test_get_returns => { 'services/100.xml' => $meta_xml }
+ );
+
+ my $council = FixMyStreet::App->model('DB::Open311conf')->new( {
+ area_id => 1
+ } );
+
+ $processor->_current_open311( $o );
+ $processor->_current_council( $council );
+ $processor->_current_service( { service_code => 100 } );
+
+ $processor->_add_meta_to_contact( $contact );
+
+ my $extra = [
+ {
+ variable => 'true',
+ code => 'type',
+ datatype => 'string',
+ required => 'true',
+ datatype_description => 'Type of bin',
+ order => 1,
+ description => 'Type of bin'
+
+ },
+ {
+ variable => 'true',
+ code => 'size',
+ datatype => 'string',
+ required => 'true',
+ datatype_description => 'Size of bin',
+ order => 2,
+ description => 'Size of bin'
+
+ },
+ {
+ variable => 'true',
+ code => 'colour',
+ datatype => 'string',
+ required => 'true',
+ datatype_description => 'Colour of bin',
+ order => 3,
+ description => 'Colour of bin'
+
+ },
+ ];
+
+ $contact->discard_changes;
+
+ is_deeply $contact->extra, $extra, 'meta data re-ordered correctly';
+};
+
+subtest 'check bromely skip code' => sub {
+ my $processor = Open311::PopulateServiceList->new( council_list => [] );
+
+ my $meta_xml = '<?xml version="1.0" encoding="utf-8"?>
+<service_definition>
+ <service_code>100</service_code>
+ <attributes>
+ <attribute>
+ <variable>true</variable>
+ <code>type</code>
+ <datatype>string</datatype>
+ <required>true</required>
+ <datatype_description>Type of bin</datatype_description>
+ <order>1</order>
+ <description>Type of bin</description>
+ </attribute>
+ <attribute>
+ <variable>true</variable>
+ <code>title</code>
+ <datatype>string</datatype>
+ <required>true</required>
+ <datatype_description>Type of bin</datatype_description>
+ <order>1</order>
+ <description>Type of bin</description>
+ </attribute>
+ <attribute>
+ <variable>true</variable>
+ <code>report_url</code>
+ <datatype>string</datatype>
+ <required>true</required>
+ <datatype_description>Type of bin</datatype_description>
+ <order>1</order>
+ <description>Type of bin</description>
+ </attribute>
+ </attributes>
+</service_definition>
+ ';
+
+ my $contact = FixMyStreet::App->model('DB::Contact')->find_or_create(
+ {
+ area_id => 1,
+ email => '001',
+ category => 'Bins left out 24x7',
+ confirmed => 1,
+ deleted => 0,
+ editor => $0,
+ whenedited => \'ms_current_timestamp()',
+ note => 'test contact',
+ }
+ );
+
+ my $o = Open311->new(
+ jurisdiction => 'mysociety',
+ endpoint => 'http://example.com',
+ test_mode => 1,
+ test_get_returns => { 'services/100.xml' => $meta_xml }
+ );
+
+ my $council = FixMyStreet::App->model('DB::Open311conf')->new( {
+ area_id => 2482
+ } );
+
+ $processor->_current_open311( $o );
+ $processor->_current_council( $council );
+ $processor->_current_service( { service_code => 100 } );
+
+ $processor->_add_meta_to_contact( $contact );
+
+ my $extra = [ {
+ variable => 'true',
+ code => 'type',
+ datatype => 'string',
+ required => 'true',
+ datatype_description => 'Type of bin',
+ order => 1,
+ description => 'Type of bin'
+
+ } ];
+
+ $contact->discard_changes;
+
+ is_deeply $contact->extra, $extra, 'only non std bromley meta data saved';
+
+ $council->area_id(1);
+
+ $processor->_current_council( $council );
+ $processor->_add_meta_to_contact( $contact );
+
+ $extra = [
+ {
+ variable => 'true',
+ code => 'type',
+ datatype => 'string',
+ required => 'true',
+ datatype_description => 'Type of bin',
+ order => 1,
+ description => 'Type of bin'
+
+ },
+ {
+ variable => 'true',
+ code => 'title',
+ datatype => 'string',
+ required => 'true',
+ datatype_description => 'Type of bin',
+ order => 1,
+ description => 'Type of bin'
+
+ },
+ {
+ variable => 'true',
+ code => 'report_url',
+ datatype => 'string',
+ required => 'true',
+ datatype_description => 'Type of bin',
+ order => 1,
+ description => 'Type of bin'
+
+ },
+ ];
+
+ $contact->discard_changes;
+
+ is_deeply $contact->extra, $extra, 'all meta data saved for non bromley';
+};
+
sub get_standard_xml {
return qq{<?xml version="1.0" encoding="utf-8"?>
<services>