aboutsummaryrefslogtreecommitdiffstats
path: root/t
diff options
context:
space:
mode:
authorHakim Cassimally <hakim@mysociety.org>2014-03-13 16:56:02 +0000
committerHakim Cassimally <hakim@mysociety.org>2014-10-16 16:56:26 +0000
commitd1fee928f02dbc30d3a38b746155ce5b12be4a1b (patch)
tree5e8bdccbd69863e69098b9aa900c1e71745f8eb5 /t
parent592f4c0ba0f822b55bb242cb12768ce771599d09 (diff)
Open311 Endpoint
Subsystems include * ::Spark encoding conventions for xml/json * ::Schema using Rx to validate form of inputs and outputs, including validation for, e.g., dates and CSV as part of Open311 Handles following paths: * Open311 attributes for Service Definition http://wiki.open311.org/GeoReport_v2#GET_Service_Definition * POST service request * GET Service Requests * GET Service Request Objects: * ::Service * ::Service::Request
Diffstat (limited to 't')
-rw-r--r--t/open311/endpoint.t351
-rw-r--r--t/open311/endpoint/Endpoint1.pm114
-rw-r--r--t/open311/endpoint/ServiceType1.pm12
-rw-r--r--t/open311/endpoint/schema.t82
-rw-r--r--t/open311/endpoint/spark.t64
5 files changed, 623 insertions, 0 deletions
diff --git a/t/open311/endpoint.t b/t/open311/endpoint.t
new file mode 100644
index 000000000..fd794feec
--- /dev/null
+++ b/t/open311/endpoint.t
@@ -0,0 +1,351 @@
+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::Endpoint1;
+
+my $endpoint = t::open311::endpoint::Endpoint1->new;
+my $json = JSON->new;
+
+subtest "GET Service List" => sub {
+ my $res = $endpoint->run_test_request( GET => '/services.xml' );
+ ok $res->is_success, 'xml success'
+ or diag $res->content;
+ is_string $res->content, <<CONTENT, 'xml string ok';
+<?xml version="1.0" encoding="utf-8"?>
+<services>
+ <service>
+ <description>Pothole Repairs Service</description>
+ <group>highways</group>
+ <keywords>deep,hole,wow</keywords>
+ <metadata>true</metadata>
+ <service_code>POT</service_code>
+ <service_name>Pothole Repairs</service_name>
+ <type>realtime</type>
+ </service>
+ <service>
+ <description>Bin Enforcement Service</description>
+ <group>sanitation</group>
+ <keywords>bin</keywords>
+ <metadata>false</metadata>
+ <service_code>BIN</service_code>
+ <service_name>Bin Enforcement</service_name>
+ <type>realtime</type>
+ </service>
+</services>
+CONTENT
+
+ $res = $endpoint->run_test_request( GET => '/services.json' );
+ ok $res->is_success, 'json success';
+ is_deeply $json->decode($res->content),
+ [ {
+ "keywords" => "deep,hole,wow",
+ "group" => "highways",
+ "service_name" => "Pothole Repairs",
+ "type" => "realtime",
+ "metadata" => "true",
+ "description" => "Pothole Repairs Service",
+ "service_code" => "POT"
+ }, {
+ "keywords" => "bin",
+ "group" => "sanitation",
+ "service_name" => "Bin Enforcement",
+ "type" => "realtime",
+ "metadata" => "false",
+ "description" => "Bin Enforcement Service",
+ "service_code" => "BIN"
+ } ], 'json structure ok';
+
+};
+
+subtest "GET Service Definition" => sub {
+ my $res = $endpoint->run_test_request( GET => '/services/POT.xml' );
+ ok $res->is_success, 'xml success',
+ or diag $res->content;
+ is_string $res->content, <<CONTENT, 'xml string ok';
+<?xml version="1.0" encoding="utf-8"?>
+<service_definition>
+ <attributes>
+ <attribute>
+ <code>depth</code>
+ <datatype>number</datatype>
+ <datatype_description>an integer</datatype_description>
+ <description>depth of pothole, in centimetres</description>
+ <order>1</order>
+ <required>true</required>
+ <variable>true</variable>
+ </attribute>
+ <attribute>
+ <code>shape</code>
+ <datatype>singlevaluelist</datatype>
+ <datatype_description>square | circle | triangle</datatype_description>
+ <description>shape of the pothole</description>
+ <order>2</order>
+ <required>false</required>
+ <values>
+ <value>
+ <name>Triangle</name>
+ <key>triangle</key>
+ </value>
+ <value>
+ <name>Circle</name>
+ <key>circle</key>
+ </value>
+ <value>
+ <name>Square</name>
+ <key>square</key>
+ </value>
+ </values>
+ <variable>true</variable>
+ </attribute>
+ </attributes>
+ <service_code>POT</service_code>
+</service_definition>
+CONTENT
+
+ $res = $endpoint->run_test_request( GET => '/services/POT.json' );
+ ok $res->is_success, 'json success';
+ is_deeply $json->decode($res->content),
+ {
+ "service_code" => "POT",
+ "attributes" => [
+ {
+ "order" => 1,
+ "code" => "depth",
+ "required" => "true",
+ "variable" => "true",
+ "datatype_description" => "an integer",
+ "description" => "depth of pothole, in centimetres",
+ "datatype" => "number",
+ },
+ {
+ "order" => 2,
+ "code" => "shape",
+ "variable" => "true",
+ "datatype_description" => "square | circle | triangle",
+ "description" => "shape of the pothole",
+ "required" => "false",
+ "datatype" => "singlevaluelist",
+ "values" => [
+ {
+ "name" => "Triangle",
+ "key" => "triangle"
+ },
+ {
+ "name" => "Circle",
+ "key" => "circle"
+ },
+ {
+ "name" => "Square",
+ "key" => "square"
+ }
+ ],
+ }
+ ],
+ }, 'json structure ok';
+};
+
+subtest "POST Service Request validation" => sub {
+ my $res = $endpoint->run_test_request(
+ POST => '/requests.json',
+ );
+ ok ! $res->is_success, 'no service_code';
+
+ $res = $endpoint->run_test_request(
+ POST => '/requests.json',
+ service_code => 'BIN',
+ );
+ ok ! $res->is_success, 'no api_key';
+
+ $res = $endpoint->run_test_request(
+ POST => '/requests.json',
+ api_key => 'test',
+ service_code => 'BADGER', # has moved the goalposts
+ );
+ ok ! $res->is_success, 'bad service_code';
+
+ $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',
+ );
+ ok ! $res->is_success, 'no required attributes';
+
+ $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]' => 'starfish',
+ );
+ ok ! $res->is_success, 'bad attribute';
+};
+
+subtest "POST Service Request valid test" => sub {
+
+ 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;
+
+ is_string $res->content, <<CONTENT, 'xml string ok';
+<?xml version="1.0" encoding="utf-8"?>
+<service_requests>
+ <request>
+ <service_notice>This is a test service</service_notice>
+ <service_request_id>1</service_request_id>
+ </request>
+</service_requests>
+CONTENT
+};
+
+subtest "GET Service Requests" => sub {
+
+ my $res = $endpoint->run_test_request( GET => '/requests.xml', );
+ ok $res->is_success, 'valid request';
+ my $xml = <<CONTENT;
+<?xml version="1.0" encoding="utf-8"?>
+<service_requests>
+ <request>
+ <address>22 Acacia Avenue</address>
+ <address_id></address_id>
+ <lat>0</lat>
+ <long>0</long>
+ <media_url></media_url>
+ <requested_datetime>2014-01-01T12:00:00Z</requested_datetime>
+ <service_code>POT</service_code>
+ <service_name>Pothole Repairs</service_name>
+ <service_request_id>0</service_request_id>
+ <status>open</status>
+ <updated_datetime>2014-01-01T12:00:00Z</updated_datetime>
+ <zipcode></zipcode>
+ </request>
+ <request>
+ <address>22 Acacia Avenue</address>
+ <address_id></address_id>
+ <lat>0</lat>
+ <long>0</long>
+ <media_url></media_url>
+ <requested_datetime>2014-02-01T12:00:00Z</requested_datetime>
+ <service_code>POT</service_code>
+ <service_name>Pothole Repairs</service_name>
+ <service_request_id>1</service_request_id>
+ <status>open</status>
+ <updated_datetime>2014-02-01T12:00:00Z</updated_datetime>
+ <zipcode></zipcode>
+ </request>
+</service_requests>
+CONTENT
+
+ is_string $res->content, $xml, 'xml string ok';
+
+ $res = $endpoint->run_test_request( GET => '/requests.xml?service_code=POT', );
+ ok $res->is_success, 'valid request';
+
+ is_string $res->content, $xml, 'xml string ok POT'
+ or diag $res->content;
+
+ $res = $endpoint->run_test_request( GET => '/requests.xml?service_code=BIN', );
+ ok $res->is_success, 'valid request';
+ is_string $res->content, <<CONTENT, 'xml string ok BIN (no requests)';
+<?xml version="1.0" encoding="utf-8"?>
+<service_requests>
+</service_requests>
+CONTENT
+};
+
+subtest "GET Service Request" => sub {
+ my @req=(<<REQ0,<<REQ1);
+<?xml version="1.0" encoding="utf-8"?>
+<service_requests>
+ <request>
+ <address>22 Acacia Avenue</address>
+ <address_id></address_id>
+ <lat>0</lat>
+ <long>0</long>
+ <media_url></media_url>
+ <requested_datetime>2014-01-01T12:00:00Z</requested_datetime>
+ <service_code>POT</service_code>
+ <service_name>Pothole Repairs</service_name>
+ <service_request_id>0</service_request_id>
+ <status>open</status>
+ <updated_datetime>2014-01-01T12:00:00Z</updated_datetime>
+ <zipcode></zipcode>
+ </request>
+</service_requests>
+REQ0
+<?xml version="1.0" encoding="utf-8"?>
+<service_requests>
+ <request>
+ <address>22 Acacia Avenue</address>
+ <address_id></address_id>
+ <lat>0</lat>
+ <long>0</long>
+ <media_url></media_url>
+ <requested_datetime>2014-02-01T12:00:00Z</requested_datetime>
+ <service_code>POT</service_code>
+ <service_name>Pothole Repairs</service_name>
+ <service_request_id>1</service_request_id>
+ <status>open</status>
+ <updated_datetime>2014-02-01T12:00:00Z</updated_datetime>
+ <zipcode></zipcode>
+ </request>
+</service_requests>
+REQ1
+
+ my $res = $endpoint->run_test_request( GET => '/requests/0.xml', );
+ ok $res->is_success, 'valid request';
+
+ is_string $res->content, $req[0], 'Request 0 ok'
+ or diag $res->content;;
+
+ $res = $endpoint->run_test_request( GET => '/requests/1.xml', );
+ ok $res->is_success, 'valid request';
+
+ is_string $res->content, $req[1], 'Request 1 ok';
+};
+
+restore_time();
+done_testing;
diff --git a/t/open311/endpoint/Endpoint1.pm b/t/open311/endpoint/Endpoint1.pm
new file mode 100644
index 000000000..ccd16c238
--- /dev/null
+++ b/t/open311/endpoint/Endpoint1.pm
@@ -0,0 +1,114 @@
+package t::open311::endpoint::Endpoint1;
+use Web::Simple;
+extends 'Open311::Endpoint';
+use Types::Standard ':all';
+use MooX::HandlesVia;
+
+use Open311::Endpoint::Service;
+use t::open311::endpoint::ServiceType1;
+use Open311::Endpoint::Service::Attribute;
+
+sub services {
+ return (
+ t::open311::endpoint::ServiceType1->new(
+ service_code => 'POT',
+ service_name => 'Pothole Repairs',
+ description => 'Pothole Repairs Service',
+ attributes => [
+ Open311::Endpoint::Service::Attribute->new(
+ code => 'depth',
+ required => 1,
+ datatype => 'number',
+ datatype_description => 'an integer',
+ description => 'depth of pothole, in centimetres',
+ ),
+ Open311::Endpoint::Service::Attribute->new(
+ code => 'shape',
+ required => 0,
+ datatype => 'singlevaluelist',
+ datatype_description => 'square | circle | triangle',
+ description => 'shape of the pothole',
+ values => {
+ square => 'Square',
+ circle => 'Circle',
+ triangle => 'Triangle',
+ },
+ ),
+ ],
+ type => 'realtime',
+ keywords => [qw/ deep hole wow/],
+ group => 'highways',
+ ),
+ t::open311::endpoint::ServiceType1->new(
+ service_code => 'BIN',
+ service_name => 'Bin Enforcement',
+ description => 'Bin Enforcement Service',
+ attributes => [],
+ type => 'realtime',
+ keywords => [qw/ bin /],
+ group => 'sanitation',
+ )
+ );
+}
+
+# FOR TESTING, we'll just maintain requests in a *global* array...
+# obviously a real Service driver will use a DB or API call!
+{
+ our @SERVICE_REQUESTS;
+ has _requests => (
+ is => 'ro',
+ isa => ArrayRef[ InstanceOf[ 'Open311::Endpoint::Service::Request' ] ],
+ default => sub { \@SERVICE_REQUESTS },
+ handles_via => 'Array',
+ handles => {
+ next_request_id => 'count',
+ _add_request => 'push',
+ get_request => 'get',
+ get_requests => 'elements',
+ filter_requests => 'grep',
+ }
+ );
+}
+
+sub post_service_request {
+ my ($self, $service, $args) = @_;
+
+ my $request = Open311::Endpoint::Service::Request->new(
+
+ # NB: possible race condition between next_request_id and _add_request
+ # (this is fine for synchronous test-cases)
+
+ service => $service,
+ service_request_id => $self->next_request_id,
+ status => 'open',
+ description => $args->{description},
+ agency_responsible => '',
+ requested_datetime => DateTime->now(),
+ updated_datetime => DateTime->now(),
+ address => $args->{address_string} // '',
+ address_id => $args->{address_id} // '',
+ media_url => $args->{media_url} // '',
+ zipcode => $args->{zipcode} // '',
+ # NB: other info is passed in that would be stored by an Open311
+ # endpoint, see Open311::Endpoint::Service::Request for full list,
+ # but we don't need to handle all of those in this test
+ );
+ $self->_add_request( $request );
+
+ return ( $request );
+}
+
+sub get_service_requests {
+ my ($self, $args) = @_;
+
+ my $service_code = $args->{service_code} or return $self->get_requests;
+ # we use ~~ as the service_code arg will be an arrayref like ['POT']
+ return $self->filter_requests( sub { shift->service->service_code ~~ $service_code });
+}
+
+sub get_service_request {
+ my ($self, $service_request_id, $args) = @_;
+ return $self->get_request( $service_request_id );
+}
+
+1;
diff --git a/t/open311/endpoint/ServiceType1.pm b/t/open311/endpoint/ServiceType1.pm
new file mode 100644
index 000000000..e73b15b7d
--- /dev/null
+++ b/t/open311/endpoint/ServiceType1.pm
@@ -0,0 +1,12 @@
+package t::open311::endpoint::ServiceType1;
+use Moo;
+extends 'Open311::Endpoint::Service';
+use DateTime;
+
+use Open311::Endpoint::Service::Request;
+
+has '+default_service_notice' => (
+ default => 'This is a test service',
+);
+
+1;
diff --git a/t/open311/endpoint/schema.t b/t/open311/endpoint/schema.t
new file mode 100644
index 000000000..b669ca4a5
--- /dev/null
+++ b/t/open311/endpoint/schema.t
@@ -0,0 +1,82 @@
+use strict; use warnings;
+
+use Test::More;
+use Test::Exception;
+
+use Data::Rx;
+use Open311::Endpoint;
+
+my $endpoint = Open311::Endpoint->new;
+my $schema = $endpoint->rx;
+
+subtest 'comma tests' => sub {
+
+ dies_ok {
+ my $comma = $schema->make_schema({
+ type => '/open311/comma',
+ });
+ } 'Construction dies on no contents';
+
+ dies_ok {
+ my $comma = $schema->make_schema({
+ type => '/open311/comma',
+ contents => '/open311/status',
+ zirble => 'fleem',
+ });
+ } 'Construction dies on extra arguments';
+
+ my $comma = $schema->make_schema({
+ type => '/open311/comma',
+ contents => '/open311/status',
+ trim => 1,
+ });
+
+ ok ! $comma->check( undef ), 'Undef is not a valid string';
+ ok ! $comma->check( [] ), 'Reference is not a valid string';
+
+ ok ! $comma->check( 'zibble' ), 'invalid string';
+ ok ! $comma->check( 'open,zibble' ), 'an invalid element';
+
+ ok $comma->check( 'open' ), 'single value';
+ ok $comma->check( 'open,closed' ), 'multiple values ok';
+ ok $comma->check( 'open, closed ' ), 'spaces trimmed ok';
+};
+
+subtest 'datetime tests' => sub {
+
+ dies_ok {
+ my $comma = $schema->make_schema({
+ type => '/open311/datetime',
+ zirble => 'fleem',
+ });
+ } 'Construction dies on extra keys';
+
+ my $dt = $schema->make_schema({
+ type => '/open311/datetime',
+ });
+
+ ok ! $dt->check( undef ), 'Undef is not a valid string';
+ ok ! $dt->check( [] ), 'Reference is not a valid string';
+
+ ok ! $dt->check( '9th Feb 2012' ), 'invalid datetime format';
+
+ ok $dt->check( '1994-11-05T08:15:30-05:00' ), 'datetime format with offset';
+ ok $dt->check( '1994-11-05T08:15:30+05:00' ), 'datetime format with positive';
+ ok $dt->check( '1994-11-05T13:15:30Z' ), 'datetime format zulu';
+};
+
+subtest 'identifier tests' => sub {
+ my $id = $schema->make_schema( '/open311/example/identifier' );
+
+ ok ! $id->check( undef ), 'Undef is not a valid string';
+ ok ! $id->check( '' ), 'Empty string is not a valid identifier';
+ ok ! $id->check( 'foo bar' ), 'String with spaces is not a valid identifier';
+
+ ok $id->check( 'foo' ), 'Ascii word string is a valid identifier';
+ ok $id->check( 'foo_bar' ), 'Ascii word string is a valid identifier';
+ ok $id->check( 'foo_123' ), 'Ascii word/num string is a valid identifier';
+};
+
+done_testing;
+
+1;
diff --git a/t/open311/endpoint/spark.t b/t/open311/endpoint/spark.t
new file mode 100644
index 000000000..589f39baf
--- /dev/null
+++ b/t/open311/endpoint/spark.t
@@ -0,0 +1,64 @@
+use strict; use warnings;
+
+use Test::More;
+
+use Open311::Endpoint;
+use Data::Dumper;
+use JSON;
+
+my $endpoint = Open311::Endpoint->new;
+my $json = JSON->new;
+
+subtest "Spark test" => sub {
+ my $spark = $endpoint->spark;
+ my $struct = {
+ foo => {
+ service_requests => [ 1,2,3 ],
+ quxes => [
+ {
+ values => [1,2],
+ },
+ {
+ values => [3,4],
+ },
+ ],
+ },
+ };
+ is_deeply $spark->process_for_json($struct),
+ {
+ service_requests => [ 1,2,3 ],
+ quxes => [
+ {
+ values => [1,2],
+ },
+ {
+ values => [3,4],
+ },
+ ],
+ };
+
+ my $xml_struct = $spark->process_for_xml($struct);
+ is_deeply $xml_struct,
+ {
+ foo => {
+ service_requests => { request => [ 1,2,3 ] },
+ quxes => {
+ quxe => [
+ {
+ values => {
+ value => [1,2],
+ },
+ },
+ {
+ values => {
+ value => [3,4],
+ },
+ },
+ ]
+ },
+ }
+ }
+ or warn Dumper($xml_struct);
+};
+
+done_testing;