open311-validator 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,14 @@
1
+ Copyright 2011 SeeClickFix
2
+
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ Unless required by applicable law or agreed to in writing, software
10
+ distributed under the License is distributed on an "AS IS" BASIS,
11
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ See the License for the specific language governing permissions and
13
+ limitations under the License.
14
+
data/README.markdown ADDED
@@ -0,0 +1,7 @@
1
+ Tools to Validate311
2
+ ====================================
3
+
4
+ Running
5
+ ----------
6
+
7
+ Install HTTParty gem. Run using command 'ruby bin/validate'
@@ -0,0 +1,41 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+
4
+ lib = File.expand_path(File.dirname(__FILE__) + '/../lib')
5
+ $LOAD_PATH.unshift(lib) if File.directory?(lib) && !$LOAD_PATH.include?(lib)
6
+
7
+ require 'optparse'
8
+ require 'validate_311'
9
+
10
+ options = { :discovery_url => 'http://seeclickfix.com/open311/discovery',
11
+ :production => false,
12
+ :write => false
13
+ }
14
+
15
+
16
+ op = OptionParser.new do |opts|
17
+ opts.banner = "Usage: validate [options]"
18
+ opts.on('-u', '--url [discovery_url]','') do |url|
19
+ options[:discovery_url] = url if url
20
+ end
21
+ opts.on('-p', '--production [true/false]', 'Default: false') do |resp|
22
+ options[:production] = resp unless resp.nil?
23
+ end
24
+ opts.on('-k', '--api_key [key]', 'Default: none') do |resp|
25
+ options[:api_key] = resp unless resp.nil?
26
+ end
27
+ opts.on('-w', '--write [true/false]', 'Default: false') do |resp|
28
+ options[:write] = resp unless resp.nil?
29
+ end
30
+ opts.on('-j', '--jurisdiction_id [jurisdiction_id]', 'Default: none') do |resp|
31
+ options[:jurisdiction_id] = resp unless resp.nil?
32
+ end
33
+ opts.on('-a', '--address [address]', 'Default: none') do |resp|
34
+ options[:address] = resp unless resp.nil?
35
+ end
36
+
37
+ end
38
+ op.parse!
39
+
40
+ @session = Session.new(options)
41
+ @session.valid? ? @session.run_tests : (puts op)
data/lib/client.rb ADDED
@@ -0,0 +1,70 @@
1
+
2
+
3
+ class Client
4
+ attr_accessor :raw, :base, :format, :unwrap
5
+
6
+ def initialize(base,format)
7
+ @base = base
8
+ @format = format
9
+ end
10
+
11
+ def get_and_unwrap(url,unwrap)
12
+ @unwrap = unwrap
13
+ get(url)
14
+ end
15
+
16
+ def get(url)
17
+ @raw = HTTParty.get("#{@base}#{url}")
18
+ self
19
+ end
20
+
21
+ def post(url,body)
22
+ # TODO: remove. This is simply for my testing.
23
+ url = 'http://localhost:3000/open311/requests.xml'
24
+ @raw = HTTParty.post(url, :body => body)
25
+ self
26
+ end
27
+
28
+ def response
29
+ if @format == 'xml' and !@unwrap.nil?
30
+ Array(Session.unwrap(raw_response,@unwrap))
31
+ else
32
+ raw_response
33
+ end
34
+ end
35
+
36
+ def raw_response
37
+ Client.hash2ostruct(@raw.parsed_response)
38
+ end
39
+
40
+ def headers
41
+ Client.hash2ostruct(@raw.headers)
42
+ end
43
+
44
+ def body
45
+ @raw.body
46
+ end
47
+
48
+ def request
49
+ @raw.request
50
+ end
51
+
52
+ # Recursively create ostruct obj from
53
+ # httparty's Hash/Array mess.
54
+ def self.hash2ostruct(object)
55
+ return case object
56
+ when Hash
57
+ object = object.clone
58
+ object.each do |key, value|
59
+ object[key] = Client.hash2ostruct(value)
60
+ end
61
+ OpenStruct.new(object)
62
+ when Array
63
+ object = object.clone
64
+ object.map! { |i| Client.hash2ostruct(i) }
65
+ else
66
+ object
67
+ end
68
+ end
69
+
70
+ end
data/lib/resource.rb ADDED
@@ -0,0 +1,98 @@
1
+
2
+
3
+ class Resource
4
+ attr_accessor :options, :raw
5
+
6
+ def initialize(session,options)
7
+ @options = options
8
+ @session = session
9
+ @last_resource = session.resource
10
+ end
11
+
12
+ def get_next
13
+ self.send @options[:with]
14
+ end
15
+
16
+ def discovery_url
17
+ @raw = OpenStruct.new
18
+ @raw.json = Client.new(@options[:discovery_url],'json').get(".json")
19
+ @raw.xml = Client.new(@options[:discovery_url],'xml').get(".xml")
20
+ end
21
+
22
+ def services_resource
23
+ @raw = []
24
+ @session.endpoint_array.each do |endpoint|
25
+ response = Client.new(endpoint[0],endpoint[1])
26
+ response.get_and_unwrap("/services.#{endpoint[1]}?jurisdiction_id=#{@options[:jurisdiction_id]}",'services.service')
27
+ @raw << response
28
+ end
29
+ end
30
+
31
+ def definition_resource
32
+ @raw = []
33
+ @session.raw_services.each do |raw_service|
34
+ raw_service.response.each do |service|
35
+ next if !service.metadata or service.metadata == 'false'
36
+ response = Client.new(raw_service.base,raw_service.format)
37
+ response.get_and_unwrap("/services/#{service.service_code}.#{raw_service.format}?jurisdiction_id=#{@options[:jurisdiction_id]}",'')
38
+ @raw << response
39
+ end
40
+ end
41
+ end
42
+
43
+ #
44
+ # Purpose: Create service requests
45
+ # URL: https://[API endpoint]/requests.[format]
46
+ # Sample URL: https://api.city.gov/dev/v2/requests.xml
47
+ # Format sent: Content-Type: application/x-www-form-urlencoded
48
+ # Formats returned: XML (JSON available if denoted by Service Discovery)
49
+ # HTTP Method: POST
50
+ # Requires API Key: Yes
51
+ #
52
+ #
53
+ # Required Arguments
54
+ # jurisdiction_id
55
+ # service_code (obtained from GET Service List method)
56
+ # location: either lat & long or address_string or address_id must be submitted
57
+ # attribute - only required if the service_code requires a service definition with required fields
58
+ # Explanation: An array of key/value responses based on Service Definitions. This takes the form of attribute[code]=value where multiple code/value pairs can be specified as well as multiple values for the same code in the case of a multivaluelist datatype (attribute[code1][]=value1&attribute[code1][]=value2&attribute[code1][]=value3) - see example
59
+ #
60
+ def create_resource_once
61
+ @raw.xml = []
62
+ @raw.json = []
63
+ # For each endpoint...
64
+ @session.discovery.xml.response.discovery.endpoints.endpoint.each do |endpoint|
65
+ next if type(endpoint) == 'production' and !@options.production
66
+ @raw.xml << Client.new.post("#{endpoint.url}/requests.xml",data_from_options)
67
+ end
68
+
69
+ @session.discovery.json.response.endpoints.each do |endpoint|
70
+ next if type(endpoint) == 'production' and !@options.production
71
+ @raw.json << Client.new.post("#{endpoint.url}/requests.json",data_from_options)
72
+ end if @options.json
73
+
74
+ @raw.options = @options
75
+ @raw
76
+ end
77
+
78
+ def data_from_options
79
+ ## Converting @options back to a hash and merge them with user-supplied
80
+ # post data.
81
+ { :api_key => @options.api_key,
82
+ :jurisdiction_id => @options.jurisdiction_id,
83
+ :service_code => first_service_code,
84
+ :address_string => @options.address
85
+ }.merge!(@options.post)
86
+ end
87
+
88
+ def first_service_code
89
+ @session.resources[1].xml.first.response.services.service.service_code
90
+ end
91
+
92
+
93
+
94
+ private
95
+
96
+
97
+
98
+ end
data/lib/ruby_spec.rb ADDED
@@ -0,0 +1,75 @@
1
+
2
+ # class PositiveSpec
3
+ # def initialize(obj,required)
4
+ # @obj = obj
5
+ # @required = required
6
+ # end
7
+ #
8
+ # def ==(other)
9
+ # if @obj != other
10
+ # raise Exception.new("#{requirement}: \"Expected: #{other} Got: #{@obj}\"")
11
+ # end
12
+ # end
13
+ #
14
+ # def >(other)
15
+ # unless @obj > other
16
+ # raise Exception.new("#{requirement}: \"Expected: #{other} Got: #{@obj}\"")
17
+ # end
18
+ # end
19
+ #
20
+ # def requirement
21
+ # @required ? "REQUIRED" : "WARNING"
22
+ # end
23
+ # end
24
+
25
+ # class NegativeSpec
26
+ # def initialize(obj,required)
27
+ # @obj = obj
28
+ # @required = required
29
+ # end
30
+ #
31
+ # def ==(other)
32
+ # if @obj == other
33
+ # raise Exception.new("#{requirement}: \"Expected: #{other} Got: #{@obj}\"")
34
+ # end
35
+ # end
36
+ #
37
+ # def requirement
38
+ # @required ? "REQUIRED" : "WARNING"
39
+ # end
40
+ # end
41
+
42
+ # class Object
43
+ # def should
44
+ # PositiveSpec.new(self,false)
45
+ # end
46
+ #
47
+ # def should_not
48
+ # NegativeSpec.new(self,false)
49
+ # end
50
+ #
51
+ # def must
52
+ # RequiredPositiveSpec.new(self,true)
53
+ # end
54
+ #
55
+ # def must_not
56
+ # RequiredNegativeSpec.new(self,true)
57
+ # end
58
+ # end
59
+
60
+
61
+ def rule(msg)
62
+ begin
63
+ yield
64
+ print " #{msg.to_s.lstrip.ljust(90)[0..70]}","PASSED"
65
+ rescue Exception => e
66
+ print " #{msg.to_s.lstrip.ljust(90)[0..70]}","FAILED #{e.to_s}"
67
+ end
68
+ puts ""
69
+ end
70
+
71
+ def test(msg,options)
72
+ puts " Testing - #{msg.to_s}"
73
+ @session.add_resource(options)
74
+ yield
75
+ end
data/lib/session.rb ADDED
@@ -0,0 +1,106 @@
1
+
2
+ class Session
3
+ attr_accessor :options
4
+
5
+ def initialize(options)
6
+ @resources = []
7
+ @options = {
8
+ :production => false,
9
+ :write => false
10
+ }.merge options
11
+ end
12
+
13
+ def run_tests
14
+ load 'tests/discovery.rb'
15
+ load 'tests/services.rb'
16
+ load 'tests/service_definition.rb'
17
+ load 'tests/create.rb' if @options[:write]
18
+ end
19
+
20
+ def valid?
21
+ @options[:discovery_url] &&
22
+ !@options[:production].nil? &&
23
+ !@options[:write].nil? &&
24
+ !@options[:production].nil? &&
25
+ (!@options[:write] || ( @options[:write] && !@options[:address].nil? ))
26
+ end
27
+
28
+ #
29
+ # Resource mgmt.
30
+ def add_resource(options)
31
+ resource = Resource.new(self,@options.merge(options))
32
+ resource.get_next
33
+ @resources << resource
34
+ end
35
+
36
+ def resources
37
+ @resources
38
+ end
39
+
40
+ def discovery
41
+ @resources.first
42
+ end
43
+
44
+ def resource
45
+ @resources.last
46
+ end
47
+
48
+ # Endpoint mgmt.
49
+ def endpoint_array
50
+ t = []
51
+ all_endpoints.each do |endpoint|
52
+ Session.endpoint_formats_as_array(endpoint).each do |format|
53
+ next if format =~ /^.*html$/i
54
+ t << [endpoint.url,format]
55
+ end
56
+ end
57
+ t
58
+ end
59
+
60
+ def self.endpoint_formats_as_array(endpoint)
61
+ Session.endpoint_formats(endpoint).map{|format| format.to_s.split('/')[1]}
62
+ end
63
+
64
+ def self.endpoint_formats(endpoint)
65
+ endpoint.formats.is_a?(Array) ? endpoint.formats : Array(Session.unwrap(endpoint,'formats.format'))
66
+ end
67
+
68
+ def all_endpoints
69
+ json_endpoints + xml_endpoints
70
+ end
71
+
72
+ def json_endpoints
73
+ Session.unwrap(discovery.raw.json, 'response.endpoints').select{|endpoint| production_safe?(endpoint) }
74
+ end
75
+
76
+ def xml_endpoints
77
+ Array(Session.unwrap(discovery.raw.xml,'response.discovery.endpoints.endpoint')).select{|endpoint| production_safe?(endpoint) }
78
+ end
79
+
80
+ #
81
+ # Services Lookup
82
+ #
83
+ def services
84
+ @resources[1].raw.map{|r| r.response }.flatten
85
+ end
86
+
87
+ def raw_services
88
+ @resources[1].raw
89
+ end
90
+
91
+ # Class Methods
92
+ def self.unwrap(obj,methods)
93
+ methods.split('.').each do |method|
94
+ obj = obj.send method.to_sym
95
+ end
96
+ obj
97
+ end
98
+
99
+ private
100
+
101
+ # The marshal_dump is to avoid calling .type on the obj.
102
+ def production_safe?(endpoint)
103
+ endpoint.marshal_dump[:type] != 'production' or @options[:production]
104
+ end
105
+
106
+ end
@@ -0,0 +1,37 @@
1
+
2
+ module TestHelper
3
+
4
+ def validate_date_time(time)
5
+ time.is_a?(Time) ? time : Time.iso8601(time) rescue false
6
+ end
7
+
8
+ def resource
9
+ @session.resource
10
+ end
11
+
12
+ def xml_endpoints
13
+ @session.discovery.raw.xml.response.discovery.endpoints.endpoint
14
+ end
15
+
16
+ def json_endpoints
17
+ @session.discovery.raw.json.response.endpoints
18
+ end
19
+
20
+ def all_endpoints
21
+ xml_endpoints + json_endpoints
22
+ end
23
+
24
+ def v2_endpoints
25
+ all_endpoints.select{|endpoint| endpoint.specification =~ /^.*_v2$/i }
26
+ end
27
+
28
+ def xml_discovery
29
+ @session.discovery.raw.xml.response.discovery
30
+ end
31
+
32
+ def json_discovery
33
+ @session.discovery.raw.json.response
34
+ end
35
+
36
+ extend self
37
+ end
@@ -0,0 +1,11 @@
1
+
2
+
3
+ require 'client'
4
+ require 'session'
5
+ require 'resource'
6
+ require 'ruby_spec'
7
+
8
+ require 'rubygems'
9
+ require 'ostruct'
10
+ require 'httparty'
11
+ require 'rspec'
data/tests/create.rb ADDED
@@ -0,0 +1,29 @@
1
+
2
+
3
+ # @session.resources = History of resources for entire session of tests.
4
+ # @resource = currently tested resource.
5
+ # @resource.options = options used for that resource
6
+ # @resource.json.response = objectified response
7
+ # @resource.json.headers = response headers
8
+ # @resource.json.body = response body
9
+ # @resource.json.request = httparty request object
10
+ #
11
+ #
12
+
13
+ data = { :email => 'foo@bar.com', :description => 'I know' }
14
+
15
+ test "Creating Client Once", { :with => :create_resource_once, :post => data } do
16
+
17
+ rule 'each should return something' do
18
+ @resource.xml.each do |request|
19
+ request.response.should_not == ''
20
+ end
21
+
22
+ if @resource.options.json
23
+ @resource.json.each do |request|
24
+ request.response.should_not == ''
25
+ end
26
+ end
27
+ end
28
+
29
+ end
@@ -0,0 +1,105 @@
1
+ require 'test_helper';include TestHelper
2
+
3
+ test "Service Discovery", :with => :discovery_url do
4
+
5
+ rule 'url should end with "discovery"' do
6
+ resource.options[:discovery_url][-9..-1].should == 'discovery'
7
+ end
8
+
9
+ rule 'both formats should return results' do
10
+ resource.raw.xml.to_s.strip.should_not == '' && resource.raw.json.to_s.strip.should_not == ''
11
+ end
12
+
13
+ rule 'both formats should have contacts' do
14
+ xml_discovery.contact.to_s.strip.should_not == ''
15
+ json_discovery.contact.to_s.strip.should_not == ''
16
+ end
17
+
18
+ rule 'both formats should have key_service information' do
19
+ xml_discovery.key_service.to_s.strip.should_not == ''
20
+ json_discovery.key_service.to_s.strip.should_not == ''
21
+ end
22
+
23
+ rule 'return valid DateTime objects' do
24
+ validate_date_time(xml_discovery.changeset).class.should == Time
25
+ validate_date_time(json_discovery.changeset).class.should == Time
26
+ end
27
+
28
+ rule "contacts should return same for both formats" do
29
+ xml_discovery.contacts.should == json_discovery.contacts
30
+ end
31
+
32
+ rule "xml endpoint should be an array" do
33
+ xml_endpoints.class.should == Array
34
+ end
35
+
36
+ rule "there should be at least one endpoint" do
37
+ xml_endpoints.size.should > 0
38
+ end
39
+
40
+ rule "each endpoint should have a specification" do
41
+ all_endpoints.each do |endpoint|
42
+ endpoint.specification.should_not == ''
43
+ end
44
+ end
45
+
46
+ rule "each endpoint should have a url" do
47
+ all_endpoints.each do |endpoint|
48
+ endpoint.url.should_not == ''
49
+ end
50
+ end
51
+
52
+ rule "each endpoint should have a changeset" do
53
+ all_endpoints.each do |endpoint|
54
+ endpoint.changeset.should_not == ''
55
+ end
56
+ end
57
+
58
+ rule "each endpoint should have a valid changeset" do
59
+ all_endpoints.each do |endpoint|
60
+ validate_date_time(endpoint.changeset).class.should == Time
61
+ end
62
+ end
63
+
64
+ rule "each endpoint should have a changeset in the past" do
65
+ all_endpoints.each do |endpoint|
66
+ validate_date_time(endpoint.changeset).should < Time.now
67
+ end
68
+ end
69
+
70
+ rule "each endpoint should have a type" do
71
+ all_endpoints.each do |endpoint|
72
+ endpoint.marshal_dump[:type].should_not == ''
73
+ end
74
+ end
75
+
76
+ rule "each endpoint should have an array of formats" do
77
+ xml_endpoints.each do |endpoint|
78
+ endpoint.formats.format.class.should == Array
79
+ end
80
+ json_endpoints.each do |endpoint|
81
+ endpoint.formats.class.should == Array
82
+ end
83
+ end
84
+
85
+ rule "each endpoint should have at least one supported format" do
86
+ all_endpoints.each do |endpoint|
87
+ Session.endpoint_formats(endpoint).size.should > 0
88
+ end
89
+ end
90
+
91
+ rule "all v2 endpoints should support xml" do
92
+ v2_endpoints.each do |endpoint|
93
+ Session.endpoint_formats(endpoint).select{|format| format == 'text/xml' }.size.should > 0
94
+ end
95
+ end
96
+
97
+ end
98
+
99
+
100
+
101
+
102
+
103
+
104
+
105
+
@@ -0,0 +1,11 @@
1
+ require 'test_helper';include TestHelper
2
+
3
+ test "Service Definitions", :with => :definition_resource do
4
+
5
+ rule 'test resource' do
6
+ # raise @session.resource.raw.inspect
7
+ # raise @session.prev_resource.inspect
8
+ # raise @resource.xml.inspect
9
+ end
10
+
11
+ end
data/tests/services.rb ADDED
@@ -0,0 +1,18 @@
1
+ require 'test_helper';include TestHelper
2
+
3
+
4
+ test "Services", :with => :services_resource do
5
+
6
+ rule 'there should be at least one service right?' do
7
+ @session.raw_services.each do |services|
8
+ services.response.size.should > 0
9
+ end
10
+ end
11
+
12
+ rule 'all services should have a service code' do
13
+ @session.services.each do |service|
14
+ service.service_code.should_not == nil
15
+ end
16
+ end
17
+
18
+ end
metadata ADDED
@@ -0,0 +1,106 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: open311-validator
3
+ version: !ruby/object:Gem::Version
4
+ hash: 29
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 1
10
+ version: 0.0.1
11
+ platform: ruby
12
+ authors:
13
+ - SeeClickFix (Jeff Blasius)
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-11-14 00:00:00 Z
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: httparty
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ hash: 3
29
+ segments:
30
+ - 0
31
+ version: "0"
32
+ type: :runtime
33
+ version_requirements: *id001
34
+ - !ruby/object:Gem::Dependency
35
+ name: rspec
36
+ prerelease: false
37
+ requirement: &id002 !ruby/object:Gem::Requirement
38
+ none: false
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ hash: 3
43
+ segments:
44
+ - 0
45
+ version: "0"
46
+ type: :runtime
47
+ version_requirements: *id002
48
+ description: A command line Open311 validator. (http://open311.org)
49
+ email: dev@seeclickfix.com
50
+ executables:
51
+ - open311-validate
52
+ extensions: []
53
+
54
+ extra_rdoc_files: []
55
+
56
+ files:
57
+ - LICENSE
58
+ - README.markdown
59
+ - bin/open311-validate
60
+ - lib/client.rb
61
+ - lib/resource.rb
62
+ - lib/ruby_spec.rb
63
+ - lib/session.rb
64
+ - lib/test_helper.rb
65
+ - lib/validate_311.rb
66
+ - tests/create.rb
67
+ - tests/discovery.rb
68
+ - tests/service_definition.rb
69
+ - tests/services.rb
70
+ homepage: http://github.com/seeclickfix/open311-validator
71
+ licenses: []
72
+
73
+ post_install_message:
74
+ rdoc_options: []
75
+
76
+ require_paths:
77
+ - lib
78
+ required_ruby_version: !ruby/object:Gem::Requirement
79
+ none: false
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ hash: 3
84
+ segments:
85
+ - 0
86
+ version: "0"
87
+ required_rubygems_version: !ruby/object:Gem::Requirement
88
+ none: false
89
+ requirements:
90
+ - - ">="
91
+ - !ruby/object:Gem::Version
92
+ hash: 17
93
+ segments:
94
+ - 1
95
+ - 3
96
+ - 5
97
+ version: 1.3.5
98
+ requirements: []
99
+
100
+ rubyforge_project:
101
+ rubygems_version: 1.8.11
102
+ signing_key:
103
+ specification_version: 3
104
+ summary: Open311 Validator
105
+ test_files: []
106
+