aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--perllib/Open311/Endpoint.pm17
-rw-r--r--perllib/Open311/Endpoint/Role/mySociety.pm152
-rw-r--r--perllib/Open311/Endpoint/Schema.pm3
-rw-r--r--perllib/Open311/Endpoint/Service/Request.pm3
-rw-r--r--perllib/Open311/Endpoint/Service/Request/Update.pm57
-rw-r--r--perllib/Open311/Endpoint/Service/Request/mySociety.pm52
-rw-r--r--perllib/Open311/Endpoint/Spark.pm3
-rw-r--r--t/open311/endpoint/Endpoint1.pm5
-rw-r--r--t/open311/endpoint/Endpoint2.pm29
-rw-r--r--t/open311/endpoint/ServiceType1.pm2
-rw-r--r--t/open311/endpoint/mysociety.t161
11 files changed, 478 insertions, 6 deletions
diff --git a/perllib/Open311/Endpoint.pm b/perllib/Open311/Endpoint.pm
index 8a440e4ae..0abb52aab 100644
--- a/perllib/Open311/Endpoint.pm
+++ b/perllib/Open311/Endpoint.pm
@@ -38,7 +38,11 @@ and provide the necessary methods.
=head1 SUBCLASSING
-See also t/open311/endpoint/Endpoint1.pm as an example.
+ package My::Open311::Endpoint;
+ use Web::Simple;
+ extends 'Open311::Endpoint';
+
+See also t/open311/endpoint/Endpoint1.pm and Endpoint2.pm as examples.
=head2 methods to override
@@ -202,6 +206,11 @@ has schema => (
},
);
+sub learn_additional_types {
+ # my ($self, $rx) = @_;
+ ## no-op, but override in ::Role or implementation!
+}
+
has spark => (
is => 'lazy',
default => sub {
@@ -232,6 +241,12 @@ has w3_dt => (
default => sub { DateTime::Format::W3CDTF->new },
);
+sub maybe_inflate_datetime {
+ my ($self, $dt) = @_;
+ return unless $dt;
+ return $self->w3_dt->parse_datetime($dt);
+}
+
=head2 Dispatching
The method dispatch_request returns a list of all the dispatcher routines
diff --git a/perllib/Open311/Endpoint/Role/mySociety.pm b/perllib/Open311/Endpoint/Role/mySociety.pm
new file mode 100644
index 000000000..42afe73bb
--- /dev/null
+++ b/perllib/Open311/Endpoint/Role/mySociety.pm
@@ -0,0 +1,152 @@
+package Open311::Endpoint::Role::mySociety;
+
+=head1 NAME
+
+Open311::Endpoint::Role::mySociety - mySociety's proposed Open311 extensions
+
+=head1 SYNOPSIS
+
+See mySociety's
+L<blog post|https://www.mysociety.org/2013/02/20/open311-extended/>
+and
+L<proposal|https://github.com/mysociety/FixMyStreet/wiki/Open311-FMS---Proposed-differences-to-Open311>
+for a full explanation of the spec extension.
+
+You can use the extensions as follows:
+
+ package My::Open311::Endpoint;
+ use Web::Simple;
+ extends 'Open311::Endpoint';
+ with 'Open311::Endpoint::Role::mySociety';
+
+You will have to provide implementations of
+
+ get_service_request_updates
+ post_service_request_update
+
+You will need to return L<Open311::Endpoint::Service::Request::Update>
+objects. However, the root L<Open311::Endpoint::Service::Request> is not
+aware of updates, so you may may find it easier to ensure that the ::Service
+objects you create (with get_service_request etc.) return
+L<Open311::Endpoint::Service::Request::mySociety> objects.
+
+=cut
+
+use Moo::Role;
+no warnings 'illegalproto';
+
+around dispatch_request => sub {
+ my ($orig, $self, @args) = @_;
+ my @dispatch = $self->$orig(@args);
+ return (
+ @dispatch,
+
+ sub (GET + /servicerequestupdates + ?*) {
+ my ($self, $args) = @_;
+ $self->call_api( GET_Service_Request_Updates => $args );
+ },
+
+ sub (POST + /servicerequestupdates + ?*) {
+ my ($self, $args) = @_;
+ $self->call_api( POST_Service_Request_Update => $args );
+ },
+
+ );
+};
+
+sub GET_Service_Request_Updates_input_schema {
+ my $self = shift;
+ return {
+ type => '//rec',
+ required => {
+ $self->get_jurisdiction_id_required_clause,
+ },
+ optional => {
+ $self->get_jurisdiction_id_optional_clause,,
+ start_date => '/open311/datetime',
+ end_date => '/open311/datetime',
+ }
+ };
+}
+
+sub GET_Service_Request_Updates_output_schema {
+ my $self = shift;
+ return {
+ type => '//rec',
+ required => {
+ service_request_updates => {
+ type => '//arr',
+ contents => '/open311/service_request_update',
+ },
+ },
+ };
+}
+
+sub GET_Service_Request_Updates {
+ my ($self, $args) = @_;
+
+ my @updates = $self->get_service_request_updates({
+ jurisdiction_id => $args->{jurisdiction_id},
+ start_date => $args->{start_date},
+ end_date => $args->{end_date},
+ });
+
+ $self->format_updates(@updates);
+}
+
+sub format_updates {
+ my ($self, @updates) = @_;
+ return {
+ service_request_updates => [
+ map {
+ my $update = $_;
+ +{
+ (
+ map {
+ $_ => $update->$_,
+ }
+ qw/
+ update_id
+ service_request_id
+ status
+ description
+ media_url
+ /
+ ),
+ (
+ map {
+ $_ => $self->w3_dt->format_datetime( $update->$_ ),
+ }
+ qw/
+ updated_datetime
+ /
+ ),
+ }
+ } @updates
+ ]
+ };
+}
+
+sub get_service_request_updates {
+ my ($self, $args) = @_;
+ die "abstract method get_service_request_updates not overridden";
+}
+
+sub learn_additional_types {
+ my ($self, $schema) = @_;
+ $schema->learn_type( 'tag:wiki.open311.org,GeoReport_v2:rx/service_request_update',
+ {
+ type => '//rec',
+ required => {
+ service_request_id => $self->get_identifier_type('service_request_id'),
+ update_id => $self->get_identifier_type('update_id'),
+ status => '/open311/status',
+ updated_datetime => '/open311/datetime',
+ description => '//str',
+ media_url => '//str',
+ },
+ }
+ );
+}
+
+1;
diff --git a/perllib/Open311/Endpoint/Schema.pm b/perllib/Open311/Endpoint/Schema.pm
index e30f9ad90..9a2ad81e5 100644
--- a/perllib/Open311/Endpoint/Schema.pm
+++ b/perllib/Open311/Endpoint/Schema.pm
@@ -12,6 +12,7 @@ has endpoint => (
get_jurisdiction_id_required_clause
get_jurisdiction_id_optional_clause
get_identifier_type
+ learn_additional_types
/],
);
@@ -167,6 +168,8 @@ has schema => (
}
);
+ $self->learn_additional_types($schema);
+
return $schema;
},
);
diff --git a/perllib/Open311/Endpoint/Service/Request.pm b/perllib/Open311/Endpoint/Service/Request.pm
index b56cee393..1d835f2d5 100644
--- a/perllib/Open311/Endpoint/Service/Request.pm
+++ b/perllib/Open311/Endpoint/Service/Request.pm
@@ -1,5 +1,6 @@
package Open311::Endpoint::Service::Request;
use Moo;
+use MooX::HandlesVia;
use Types::Standard ':all';
use namespace::clean;
@@ -57,7 +58,7 @@ has requested_datetime => (
);
has updated_datetime => (
- is => 'ro',
+ is => 'rw',
isa => Maybe[ InstanceOf['DateTime'] ],
);
diff --git a/perllib/Open311/Endpoint/Service/Request/Update.pm b/perllib/Open311/Endpoint/Service/Request/Update.pm
new file mode 100644
index 000000000..b881af9ce
--- /dev/null
+++ b/perllib/Open311/Endpoint/Service/Request/Update.pm
@@ -0,0 +1,57 @@
+package Open311::Endpoint::Service::Request::Update;
+use Moo;
+use Types::Standard ':all';
+use namespace::clean;
+
+sub BUILDARGS {
+ my ($class, %args) = @_;
+ my $service_request = delete $args{service_request};
+
+ if (! $args{status}) {
+ $args{status} = $service_request->status;
+ }
+
+ return \%args;
+}
+
+has update_id => (
+ is => 'ro',
+ isa => Maybe[Str],
+ predicate => 1,
+);
+
+has service_request_id => (
+ is => 'ro',
+ isa => Maybe[Str],
+ predicate => 1,
+);
+
+has token => (
+ is => 'ro',
+ isa => Maybe[Str],
+ predicate => 1,
+);
+
+has status => (
+ is => 'ro',
+ isa => Enum[qw/ open closed /],
+);
+
+has description => (
+ is => 'ro',
+ isa => Maybe[Str],
+);
+
+has media_url => (
+ is => 'ro',
+ isa => Str,
+ default => sub { '' },
+);
+
+has updated_datetime => (
+ is => 'ro',
+ isa => InstanceOf['DateTime'],
+ default => sub { DateTime->now() },
+);
+
+1;
diff --git a/perllib/Open311/Endpoint/Service/Request/mySociety.pm b/perllib/Open311/Endpoint/Service/Request/mySociety.pm
new file mode 100644
index 000000000..748065ad8
--- /dev/null
+++ b/perllib/Open311/Endpoint/Service/Request/mySociety.pm
@@ -0,0 +1,52 @@
+package Open311::Endpoint::Service::Request::mySociety;
+use Moo;
+use MooX::HandlesVia;
+extends 'Open311::Endpoint::Service::Request';
+
+use DateTime;
+use Open311::Endpoint::Service::Request::Update;
+use Types::Standard ':all';
+
+has updates => (
+ is => 'rw',
+ isa => ArrayRef[InstanceOf['Open311::Endpoint::Service::Request::Update']],
+ default => sub { [] },
+ handles_via => 'Array',
+ handles => {
+ _add_update => 'push',
+ get_updates => 'elements',
+ get_update => 'get',
+ has_updates => 'count',
+ filter_updates => 'grep',
+ }
+);
+
+sub add_update {
+ my ($self, %args) = @_;
+ my $update = Open311::Endpoint::Service::Request::Update->new(
+ %args,
+ service_request => $self,
+ service_request_id => $self->service_request_id,
+ );
+ $self->_add_update($update);
+}
+
+
+sub last_update {
+ my $self = shift;
+ return $self->has_updates ? $self->get_update(-1) : undef;
+}
+
+around updated_datetime => sub {
+ my ($orig, $self) = @_;
+ my $last_update = $self->last_update or return;
+ return $last_update->updated_datetime;
+};
+
+around status => sub {
+ my ($orig, $self) = @_;
+ my $last_update = $self->last_update or return 'open';
+ return $last_update->status;
+};
+
+1;
diff --git a/perllib/Open311/Endpoint/Spark.pm b/perllib/Open311/Endpoint/Spark.pm
index ae179cecc..292a66996 100644
--- a/perllib/Open311/Endpoint/Spark.pm
+++ b/perllib/Open311/Endpoint/Spark.pm
@@ -1,6 +1,5 @@
package Open311::Endpoint::Spark;
use Moo;
-use Data::Visitor::Callback;
=head1 NAME
@@ -102,7 +101,9 @@ sub _process_for_xml {
my %singular_map = (
service_requests => 'request',
+ service_request_updates => 'request_update',
);
+
sub _singularize {
my $name = shift;
return $singular_map{ $name }
diff --git a/t/open311/endpoint/Endpoint1.pm b/t/open311/endpoint/Endpoint1.pm
index ccd16c238..257a94b64 100644
--- a/t/open311/endpoint/Endpoint1.pm
+++ b/t/open311/endpoint/Endpoint1.pm
@@ -7,6 +7,9 @@ use MooX::HandlesVia;
use Open311::Endpoint::Service;
use t::open311::endpoint::ServiceType1;
use Open311::Endpoint::Service::Attribute;
+use Open311::Endpoint::Service::Request;
+
+use constant request_class => 'Open311::Endpoint::Service::Request';
sub services {
return (
@@ -73,7 +76,7 @@ sub services {
sub post_service_request {
my ($self, $service, $args) = @_;
- my $request = Open311::Endpoint::Service::Request->new(
+ my $request = $self->request_class->new(
# NB: possible race condition between next_request_id and _add_request
# (this is fine for synchronous test-cases)
diff --git a/t/open311/endpoint/Endpoint2.pm b/t/open311/endpoint/Endpoint2.pm
new file mode 100644
index 000000000..2b55b1e10
--- /dev/null
+++ b/t/open311/endpoint/Endpoint2.pm
@@ -0,0 +1,29 @@
+package t::open311::endpoint::Endpoint2;
+use Web::Simple;
+extends 't::open311::endpoint::Endpoint1';
+with 'Open311::Endpoint::Role::mySociety';
+
+use Open311::Endpoint::Service::Request::mySociety;
+
+use constant request_class => 'Open311::Endpoint::Service::Request::mySociety';
+
+sub get_service_request_updates {
+ my ($self, $args) = @_;
+
+ my $start_date = $self->maybe_inflate_datetime($args->{start_date});
+ my $end_date = $self->maybe_inflate_datetime($args->{end_date});
+
+ my @requests = $self->filter_requests( sub { $_[0]->has_updates } );
+
+ return map {
+ $_->filter_updates( sub {
+ my $update = shift;
+ my $updated_datetime = $update->updated_datetime or return;
+ if ($start_date) { return unless $updated_datetime >= $start_date }
+ if ($end_date) { return unless $updated_datetime <= $end_date }
+ return 1;
+ });
+ } @requests;
+}
+
+1;
diff --git a/t/open311/endpoint/ServiceType1.pm b/t/open311/endpoint/ServiceType1.pm
index e73b15b7d..e35e44fda 100644
--- a/t/open311/endpoint/ServiceType1.pm
+++ b/t/open311/endpoint/ServiceType1.pm
@@ -3,8 +3,6 @@ use Moo;
extends 'Open311::Endpoint::Service';
use DateTime;
-use Open311::Endpoint::Service::Request;
-
has '+default_service_notice' => (
default => 'This is a test service',
);
diff --git a/t/open311/endpoint/mysociety.t b/t/open311/endpoint/mysociety.t
new file mode 100644
index 000000000..ea4f72820
--- /dev/null
+++ b/t/open311/endpoint/mysociety.t
@@ -0,0 +1,161 @@
+use strict; use warnings;
+
+use Test::More;
+use Test::LongString;
+use Test::MockTime ':all';
+
+use Open311::Endpoint;
+use Data::Dumper;
+use JSON;
+
+use t::open311::endpoint::Endpoint2;
+
+my $endpoint = t::open311::endpoint::Endpoint2->new;
+my $json = JSON->new;
+
+subtest "POST OK" => sub {
+ diag "Serves as sanity test of subclassing, as well as preparing our data";
+ # TODO, refactor repeated code lifted from t/open311/endpoint.t
+
+ set_fixed_time('2014-01-01T12:00:00Z');
+ my $res = $endpoint->run_test_request(
+ POST => '/requests.json',
+ api_key => 'test',
+ service_code => 'POT',
+ address_string => '22 Acacia Avenue',
+ first_name => 'Bob',
+ last_name => 'Mould',
+ 'attribute[depth]' => 100,
+ 'attribute[shape]' => 'triangle',
+ );
+ ok $res->is_success, 'valid request'
+ or diag $res->content;
+
+ is_deeply $json->decode($res->content),
+ [ {
+ "service_notice" => "This is a test service",
+ "service_request_id" => 0
+ } ], 'correct json returned';
+
+ set_fixed_time('2014-02-01T12:00:00Z');
+ $res = $endpoint->run_test_request(
+ POST => '/requests.xml',
+ api_key => 'test',
+ service_code => 'POT',
+ address_string => '22 Acacia Avenue',
+ first_name => 'Bob',
+ last_name => 'Mould',
+ 'attribute[depth]' => 100,
+ 'attribute[shape]' => 'triangle',
+ );
+
+ ok $res->is_success, 'valid request'
+ or diag $res->content;
+};
+
+subtest "GET Service Request Updates" => sub {
+
+ my $empty_xml = <<CONTENT;
+<?xml version="1.0" encoding="utf-8"?>
+<service_request_updates>
+</service_request_updates>
+CONTENT
+
+ my $update_0_xml = <<CONTENT;
+<?xml version="1.0" encoding="utf-8"?>
+<service_request_updates>
+ <request_updates>
+ <description>Fixed</description>
+ <media_url></media_url>
+ <service_request_id>0</service_request_id>
+ <status>closed</status>
+ <update_id>1</update_id>
+ <updated_datetime>2014-01-01T13:00:00Z</updated_datetime>
+ </request_updates>
+</service_request_updates>
+CONTENT
+
+my $updates_xml = <<CONTENT;
+<?xml version="1.0" encoding="utf-8"?>
+<service_request_updates>
+ <request_updates>
+ <description>Fixed</description>
+ <media_url></media_url>
+ <service_request_id>0</service_request_id>
+ <status>closed</status>
+ <update_id>1</update_id>
+ <updated_datetime>2014-01-01T13:00:00Z</updated_datetime>
+ </request_updates>
+ <request_updates>
+ <description>Have investigated. Looks tricky!</description>
+ <media_url></media_url>
+ <service_request_id>1</service_request_id>
+ <status>open</status>
+ <update_id>2</update_id>
+ <updated_datetime>2014-03-01T13:00:00Z</updated_datetime>
+ </request_updates>
+</service_request_updates>
+CONTENT
+
+ subtest 'No updates' => sub {
+ my $res = $endpoint->run_test_request( GET => '/servicerequestupdates.xml', );
+ ok $res->is_success, 'valid request' or diag $res->content;
+
+ is_string $res->content, $empty_xml, 'xml string ok'
+ or diag $res->content;
+ };
+
+ subtest 'Updated 1 ticket' => sub {
+ # an agent updates the first ticket
+ set_fixed_time('2014-01-01T13:00:00Z');
+ my $request = $endpoint->get_request(0);
+ $request->add_update(
+ update_id => 1,
+ status => 'closed',
+ description => 'Fixed',
+ );
+
+ is $request->status, 'closed', 'Status updated';
+
+ my $before='2014-01-01T12:00:00Z';
+ my $after ='2014-01-01T14:00:00Z';
+
+ for my $scenario (
+ [ '', $update_0_xml, 'Basic test', ],
+ [ "?start_date=$before", $update_0_xml, 'start date' ],
+ [ "?end_date=$after", $update_0_xml, 'end_date' ],
+ [ "?start_date=$before&end_date=$after", $update_0_xml, 'both dates' ],
+ [ "?start_date=$after", $empty_xml, 'Not found if start date after update' ],
+ [ "?end_date=$before", $empty_xml, 'Not found if end date before update' ]
+ ) {
+ my ($query, $xml, $description) = @$scenario;
+
+ my $res = $endpoint->run_test_request( GET => '/servicerequestupdates.xml' . $query, );
+ ok $res->is_success, 'valid request' or diag $res->content;
+ is_string $res->content, $xml, $description;
+ }
+ };
+
+ subtest 'Updated another ticket' => sub {
+ set_fixed_time('2014-03-01T13:00:00Z');
+ my $request = $endpoint->get_request(1);
+ $request->add_update(
+ update_id => 2,
+ description => 'Have investigated. Looks tricky!',
+ );
+
+ for my $scenario (
+ [ '', $updates_xml, 'Both reports', ],
+ [ "?end_date=2014-01-01T14:00:00Z", $update_0_xml, 'end_date before second update' ],
+ ) {
+ my ($query, $xml, $description) = @$scenario;
+
+ my $res = $endpoint->run_test_request( GET => '/servicerequestupdates.xml' . $query, );
+ ok $res->is_success, 'valid request' or diag $res->content;
+ is_string $res->content, $xml, $description or diag $res->content;
+ }
+ };
+};
+
+restore_time();
+done_testing;