open311-validator 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+