Skip to content
This repository
Browse code

Merge pull request #4 from seomoz/message-type-and-status-code

Message type and status code
  • Loading branch information...
commit 301263d4eac4c35bca3e9aca647b6914f1f7d503 2 parents 2494741 + f5a48f6
authored
13  README.md
Source Rendered
@@ -51,7 +51,9 @@ name: user_projects
51 51
 route: /users/:user_id/projects
52 52
 method: GET
53 53
 definitions:
54  
-  - versions: ["1.0"]
  54
+  - message_type: response
  55
+    versions: ["1.0"]
  56
+    status_codes: ["2xx", "404"]
55 57
     schema:
56 58
       description: Returns a list of projects for the given user.
57 59
       type: object
@@ -94,8 +96,17 @@ Let's look at this YAML file, point-by-point:
94 96
 * The `definitions` array contains a list of versioned schema definitions, with
95 97
   corresponding examples.  Everytime you modify your schema and change the version,
96 98
   you should add a new entry here.
  99
+* The `message_type` describes whether the following schema is for requests or responses.
  100
+  It is an optional attribute that when omitted defaults to response. The only valid values
  101
+  are `request` and `response`.
97 102
 * The `versions` array lists the endpoint versions that should be associated with a
98 103
   particular schema definition.
  104
+* The `status_codes` is an optional array of status code strings describing for which
  105
+  status code or codes this schema applies to. `status_codes` is ignored if used with the
  106
+  `request` `message_type`. When used with the `response` `message_type` it is an optional
  107
+  attribute that defaults to all status codes. Valid formats for a status code are 3
  108
+  characters. Each character must be a digit (0-9) or 'x' (wildcard). The following strings
  109
+  are all valid: "200", "4xx", "x0x".
99 110
 * The `schema` contains a [JSON schema](http://tools.ietf.org/html/draft-zyp-json-schema-03)
100 111
   description of the contents of the endpoint. This schema definition is used by the
101 112
   `SchemaValidation` middleware to ensure that your implementation of the endpoint
2  Rakefile 100644 → 100755
@@ -12,7 +12,7 @@ if defined?(RUBY_ENGINE) && RUBY_ENGINE == 'ruby' # MRI only
12 12
 
13 13
   desc "Run cane to check quality metrics"
14 14
   Cane::RakeTask.new(:quality) do |cane|
15  
-    cane.abc_max = 12
  15
+    cane.abc_max = 16
16 16
     cane.add_threshold 'coverage/coverage_percent.txt', :==, 100
17 17
     cane.style_measure = 100
18 18
   end
87  example/definitions/add_address.yml
... ...
@@ -0,0 +1,87 @@
  1
+---
  2
+name: add_contact_address
  3
+route: /contacts/:contact_id/address
  4
+method: PUT
  5
+definitions:
  6
+  - message_type: request
  7
+    versions: ["1.0"]
  8
+    schema:
  9
+      description: Adds a new address for a contact.
  10
+      type: object
  11
+      properties:
  12
+        street:
  13
+          description: "Street address"
  14
+          type: string
  15
+        city:
  16
+          description: "The City"
  17
+          type: string
  18
+        state:
  19
+          description: "The State"
  20
+          type: string
  21
+        zip:
  22
+          description: "Zip code"
  23
+          type: string
  24
+    examples:
  25
+      - street: 1300 Pine St.
  26
+        city: Seattle
  27
+        state: WA
  28
+        zip: '98111'
  29
+  - message_type: request
  30
+    versions: ["2.0"]
  31
+    schema:
  32
+      description: Adds a new address for a contact.
  33
+      type: object
  34
+      properties:
  35
+        street:
  36
+          description: "Street address"
  37
+          type: string
  38
+        apartment:
  39
+          description: "Apartment number"
  40
+          type: string
  41
+          optional: true
  42
+        city:
  43
+          description: "The City"
  44
+          type: string
  45
+        state:
  46
+          description: "The State"
  47
+          type: string
  48
+        postal:
  49
+          description: "Postal code"
  50
+          type: string
  51
+    examples:
  52
+      - street: 1300 Pine St.
  53
+        apartment: 'Suite 400'
  54
+        city: Seattle
  55
+        state: WA
  56
+        postal: '98111'
  57
+  - message_type: response
  58
+    versions: ["1.0"]
  59
+    status_codes: ["2xx"]
  60
+    schema:
  61
+      description: Successfully added an address for a contact.
  62
+      type: object
  63
+      properties:
  64
+        added_address_id:
  65
+          description: "The id of the newly added address"
  66
+          type: integer
  67
+        contact_address_count:
  68
+          description: "The number of addresses for this contact after adding the new one"
  69
+          type: integer
  70
+    examples:
  71
+      - added_address_id: 2185
  72
+        contact_address_count: 5
  73
+  - message_type: response
  74
+    versions: ["1.0"]
  75
+    schema:
  76
+      description: Error messaging for failure to add the new address for a contact.
  77
+      type: object
  78
+      properties:
  79
+        error_code:
  80
+          description: "Error code value for the failure reason"
  81
+          type: integer
  82
+        error_message:
  83
+          description: "Human readable failure message"
  84
+          type: string
  85
+    examples:
  86
+      - error_code: 34
  87
+        error_message: Address already exists for this contact
11  lib/interpol/configuration.rb
@@ -8,14 +8,21 @@ module DefinitionFinder
8 8
     include HashFetcher
9 9
     NoDefinitionFound = Class.new
10 10
 
11  
-    def find_definition(method, path)
  11
+    def find_definition(method, path, message_type, status_code = nil)
12 12
       with_endpoint_matching(method, path) do |endpoint|
13 13
         version = yield endpoint
14  
-        endpoint.definitions.find { |d| d.version == version }
  14
+        find_definitions_for(endpoint, version, message_type).find do |definition|
  15
+          definition.matches_status_code?(status_code)
  16
+        end
15 17
       end
16 18
     end
17 19
 
18 20
   private
  21
+    def find_definitions_for(endpoint, version, message_type)
  22
+      endpoint.definitions.find do |d|
  23
+          d.first.version == version && d.first.message_type == message_type
  24
+      end || []
  25
+    end
19 26
 
20 27
     def with_endpoint_matching(method, path)
21 28
       method = method.downcase.to_sym
16  lib/interpol/documentation_app/views/endpoint.erb
@@ -3,13 +3,15 @@
3 3
     <h1><%= endpoint.name %></h1>
4 4
     <h2><%= endpoint.method.to_s.upcase %> <%= endpoint.route %></h2>
5 5
 
6  
-    <% endpoint.definitions.reverse.each do |definition| %>
7  
-      <div class="versioned-endpoint-definition">
8  
-        <h3>Version <%= definition.version %></h3>
9  
-        <hr>
10  
-
11  
-        <%= Interpol::Documentation.html_for_schema(definition.schema) %>
12  
-      </div>
  6
+    <% endpoint.definitions.each do |definitions| %>
  7
+      <% definitions.each do |definition| %>
  8
+        <div class="versioned-endpoint-definition">
  9
+          <h3><%= definition.message_type.capitalize %> - Version <%= definition.version %> - <%= definition.status_codes %></h3>
  10
+          <br/>
  11
+          <%= Interpol::Documentation.html_for_schema(definition.schema) %>
  12
+        </div>
  13
+        <hr/>
  14
+      <% end %>
13 15
     <% end %>
14 16
   </div>
15 17
 </div><!--/span-->
108  lib/interpol/endpoint.rb
@@ -27,23 +27,36 @@ def initialize(endpoint_hash)
27 27
       validate_name!
28 28
     end
29 29
 
30  
-    def find_definition!(version)
31  
-      @definitions.fetch(version) do
  30
+    def find_definition!(version, message_type)
  31
+      @definitions.fetch([message_type, version]) do
32 32
         message = "No definition found for #{name} endpoint for version #{version}"
  33
+        message << " and message_type #{message_type}"
33 34
         raise ArgumentError.new(message)
34 35
       end
35 36
     end
36 37
 
37  
-    def find_example_for!(version)
38  
-      find_definition!(version).examples.first
  38
+    def find_example_for!(version, message_type)
  39
+      find_definition!(version, message_type).first.examples.first
  40
+    end
  41
+
  42
+    def find_example_status_code_for!(version)
  43
+      find_definition!(version, 'response').first.example_status_code
39 44
     end
40 45
 
41 46
     def available_versions
42  
-      definitions.map(&:version)
  47
+      definitions.map { |d| d.first.version }
43 48
     end
44 49
 
45 50
     def definitions
46  
-      @definitions.values.sort_by(&:version)
  51
+      # sort all requests before all responses
  52
+      # sort higher version numbers before lower version numbers
  53
+      @definitions.values.sort do |x,y|
  54
+        if x.first.message_type == y.first.message_type
  55
+          y.first.version <=> x.first.version
  56
+        else
  57
+          x.first.message_type <=> y.first.message_type
  58
+        end
  59
+      end
47 60
     end
48 61
 
49 62
     def route_matches?(path)
@@ -66,12 +79,16 @@ def route_regex
66 79
       end
67 80
     end
68 81
 
  82
+    DEFAULT_MESSAGE_TYPE = 'response'
  83
+
69 84
     def extract_definitions_from(endpoint_hash)
70  
-      definitions = {}
  85
+      definitions = Hash.new { |h, k| h[k] = [] }
71 86
 
72 87
       fetch_from(endpoint_hash, 'definitions').each do |definition|
73 88
         fetch_from(definition, 'versions').each do |version|
74  
-          definitions[version] = EndpointDefinition.new(name, version, definition)
  89
+          message_type = definition.fetch('message_type', DEFAULT_MESSAGE_TYPE)
  90
+          key = [message_type, version]
  91
+          definitions[key] << EndpointDefinition.new(name, version, message_type, definition)
75 92
         end
76 93
       end
77 94
 
@@ -90,13 +107,15 @@ def validate_name!
90 107
   # Provides the means to validate data against that version of the schema.
91 108
   class EndpointDefinition
92 109
     include HashFetcher
93  
-    attr_reader :endpoint_name, :version, :schema, :examples
94  
-
95  
-    def initialize(endpoint_name, version, definition)
96  
-      @endpoint_name = endpoint_name
97  
-      @version       = version
98  
-      @schema        = fetch_from(definition, 'schema')
99  
-      @examples      = fetch_from(definition, 'examples').map { |e| EndpointExample.new(e, self) }
  110
+    attr_reader :endpoint_name, :message_type, :version, :schema, :examples
  111
+
  112
+    def initialize(endpoint_name, version, message_type, definition)
  113
+      @endpoint_name  = endpoint_name
  114
+      @message_type   = message_type
  115
+      @status_codes   = StatusCodeMatcher.new(definition['status_codes'])
  116
+      @version        = version
  117
+      @schema         = fetch_from(definition, 'schema')
  118
+      @examples       = fetch_from(definition, 'examples').map { |e| EndpointExample.new(e, self) }
100 119
       make_schema_strict!(@schema)
101 120
     end
102 121
 
@@ -108,7 +127,19 @@ def validate_data!(data)
108 127
     end
109 128
 
110 129
     def description
111  
-      "#{endpoint_name} (v. #{version})"
  130
+      "#{endpoint_name} (v. #{version}, mt. #{message_type}, sc. #{status_codes})"
  131
+    end
  132
+
  133
+    def status_codes
  134
+      @status_codes.code_strings.join(',')
  135
+    end
  136
+
  137
+    def matches_status_code?(status_code)
  138
+      status_code.nil? || @status_codes.matches?(status_code)
  139
+    end
  140
+
  141
+    def example_status_code
  142
+      @example_status_code ||= @status_codes.example_status_code
112 143
     end
113 144
 
114 145
   private
@@ -127,6 +158,49 @@ def make_schema_strict!(raw_schema, modify_object=true)
127 158
     end
128 159
   end
129 160
 
  161
+  # Holds the acceptable status codes for an enpoint entry
  162
+  # Acceptable status code are either exact status codes (200, 404, etc)
  163
+  # or partial status codes (2xx, 3xx, 4xx, etc). Currently, partial status
  164
+  # codes can only be a digit followed by two lower-case x's.
  165
+  class StatusCodeMatcher
  166
+    attr_reader :code_strings
  167
+
  168
+    def initialize(codes)
  169
+      codes = ["xxx"] if Array(codes).empty?
  170
+      @code_strings = codes
  171
+      validate!
  172
+    end
  173
+
  174
+    def matches?(status_code)
  175
+      code_regexes.any? { |re| re =~ status_code.to_s }
  176
+    end
  177
+
  178
+    def example_status_code
  179
+      example_status_code = "200"
  180
+      code_strings.first.chars.each_with_index do |char, index|
  181
+        example_status_code[index] = char if char != 'x'
  182
+      end
  183
+      example_status_code
  184
+    end
  185
+
  186
+    private
  187
+      def code_regexes
  188
+        @code_regexes ||= code_strings.map do |string|
  189
+          /\A#{string.gsub('x', '\d')}\z/
  190
+        end
  191
+      end
  192
+
  193
+      def validate!
  194
+        code_strings.each do |code|
  195
+          # ensure code is 3 characters and all chars are a number or 'x'
  196
+          # http://rubular.com/r/4sl68Bb4XO
  197
+          unless code =~ /\A[\dx]{3}\Z/
  198
+            raise StatusCodeMatcherArgumentError, "#{code} is not a valid format"
  199
+          end
  200
+        end
  201
+      end
  202
+  end
  203
+
130 204
   # Wraps an example for a particular endpoint entry.
131 205
   class EndpointExample
132 206
     attr_reader :data, :definition
@@ -140,5 +214,3 @@ def validate!
140 214
     end
141 215
   end
142 216
 end
143  
-
144  
-
3  lib/interpol/errors.rb
@@ -31,5 +31,8 @@ def initialize(errors = [], data = nil, endpoint_description = '')
31 31
   # Error raised when the schema validator cannot find a matching
32 32
   # endpoint definition for the request.
33 33
   class NoEndpointDefinitionFoundError < Error; end
  34
+
  35
+  # Raised when an invalid status code is found during validate_codes!
  36
+  class StatusCodeMatcherArgumentError < ArgumentError; end
34 37
 end
35 38
 
3  lib/interpol/response_schema_validator.rb
@@ -67,7 +67,8 @@ def path
67 67
       end
68 68
 
69 69
       def validator
70  
-        @validator ||= @config.endpoints.find_definition(request_method, path) do |endpoint|
  70
+        @validator ||= @config.endpoints.
  71
+            find_definition(request_method, path, 'response', status) do |endpoint|
71 72
           @config.api_version_for(env, endpoint)
72 73
         end
73 74
       end
8  lib/interpol/stub_app.rb
@@ -18,8 +18,8 @@ def interpol_config
18 18
         self.class.interpol_config
19 19
       end
20 20
 
21  
-      def example_for(endpoint, version)
22  
-        endpoint.find_example_for!(version)
  21
+      def example_for(endpoint, version, message_type)
  22
+        endpoint.find_example_for!(version, message_type)
23 23
       rescue ArgumentError
24 24
         interpol_config.request_version_unavailable(self, version, endpoint.available_versions)
25 25
       end
@@ -49,8 +49,10 @@ def build
49 49
       def endpoint_definition(endpoint)
50 50
         lambda do
51 51
           version = interpol_config.api_version_for(request.env, endpoint)
52  
-          example = example_for(endpoint, version)
  52
+          message_type = 'response'
  53
+          example = example_for(endpoint, version, message_type)
53 54
           example.validate!
  55
+          status endpoint.find_example_status_code_for!(version)
54 56
           JSON.dump(example.data)
55 57
         end
56 58
       end
8  lib/interpol/test_helper.rb
@@ -5,9 +5,11 @@ module TestHelper
5 5
     module Common
6 6
       def each_example_from(endpoints)
7 7
         endpoints.each do |endpoint|
8  
-          endpoint.definitions.each do |definition|
9  
-            definition.examples.each_with_index do |example, index|
10  
-              yield endpoint, definition, example, index
  8
+          endpoint.definitions.each do |definitions|
  9
+            definitions.each do |definition|
  10
+              definition.examples.each_with_index do |example, index|
  11
+                yield endpoint, definition, example, index
  12
+              end
11 13
             end
12 14
           end
13 15
         end
66  spec/unit/interpol/configuration_spec.rb
@@ -4,40 +4,59 @@
4 4
 module Interpol
5 5
   describe DefinitionFinder do
6 6
     describe '#find_definition' do
7  
-      def endpoint(name, method, route, *versions)
  7
+      def endpoint_def(message_type, status_codes, *versions)
  8
+        {
  9
+          'versions' => versions,
  10
+          'message_type' => message_type,
  11
+          'status_codes' => status_codes,
  12
+          'schema' => {},
  13
+          'examples' => {}
  14
+        }
  15
+      end
  16
+
  17
+      def endpoint(name, method, route, *endpoint_defs)
8 18
         Endpoint.new \
9  
-          'name' => 'endpoint_name',
  19
+          'name' => name,
10 20
           'route' => route,
11 21
           'method' => method,
12  
-          'definitions' => [{
13  
-            'versions' => versions,
14  
-            'schema' => {},
15  
-            'examples' => {}
16  
-          }]
  22
+          'definitions' => endpoint_defs
17 23
       end
18 24
 
19  
-      let(:endpoint_1)    { endpoint 'e1', 'GET', '/users/:user_id/overview', '1.3' }
20  
-      let(:endpoint_2)    { endpoint 'e2', 'POST', '/foo/bar', '2.3', '2.7' }
  25
+      let(:endpoint_def_1a) { endpoint_def('response', ['2xx'], '1.3') }
  26
+      let(:endpoint_def_1b) { endpoint_def('response', nil, '1.3') }
  27
+      let(:endpoint_def_2a) { endpoint_def('request', nil, '2.3', '2.7') }
  28
+
  29
+      let(:endpoint_1) do
  30
+        endpoint 'e1', 'GET', '/users/:user_id/overview', endpoint_def_1a, endpoint_def_1b
  31
+      end
  32
+      let(:endpoint_2)    { endpoint 'e2', 'POST', '/foo/bar', endpoint_def_2a}
21 33
       let(:all_endpoints) { [endpoint_1, endpoint_2].extend(DefinitionFinder) }
22 34
 
23 35
       def find(options)
24  
-        all_endpoints.find_definition(options[:method], options[:path]) { |e| options[:version] }
  36
+        find_with_status_code(nil, options)
  37
+      end
  38
+
  39
+      def find_with_status_code(status_code, options)
  40
+        all_endpoints.find_definition(options[:method], options[:path],
  41
+          options[:message_type], status_code) { |e| options[:version] }
25 42
       end
26 43
 
27 44
       it 'finds a matching endpoint definition' do
28  
-        found = find(:method => 'POST', :path => '/foo/bar', :version => '2.3')
  45
+        found = find(:method => 'POST', :path => '/foo/bar',
  46
+          :version => '2.3', :message_type => 'request')
29 47
         found.endpoint_name.should eq(endpoint_2.name)
30 48
         found.version.should eq('2.3')
31 49
       end
32 50
 
33 51
       it 'finds the correct versioned definition of the endpoint' do
34  
-        found = find(:method => 'POST', :path => '/foo/bar', :version => '2.7')
  52
+        found = find(:method => 'POST', :path => '/foo/bar',
  53
+          :version => '2.7', :message_type => 'request')
35 54
         found.version.should eq('2.7')
36 55
       end
37 56
 
38 57
       it 'calls the version block with the endpoint' do
39 58
         endpoint = nil
40  
-        all_endpoints.find_definition('POST', '/foo/bar') do |e|
  59
+        all_endpoints.find_definition('POST', '/foo/bar', 'request') do |e|
41 60
           endpoint = e
42 61
         end
43 62
 
@@ -45,18 +64,29 @@ def find(options)
45 64
       end
46 65
 
47 66
       it 'returns NoDefinitionFound if it cannot find a matching route' do
48  
-        result = find(:method => 'POST', :path => '/goo/bar', :version => '2.7')
  67
+        result = find(:method => 'POST', :path => '/goo/bar',
  68
+          :version => '2.7', :message_type => 'request')
49 69
         result.should be(DefinitionFinder::NoDefinitionFound)
50 70
       end
51 71
 
52 72
       it 'returns nil if the endpoint does not have a matching version' do
53  
-        result = find(:method => 'POST', :path => '/foo/bar', :version => '13.7')
  73
+        result = find(:method => 'POST', :path => '/foo/bar',
  74
+          :version => '13.7', :message_type => 'request')
54 75
         result.should be(DefinitionFinder::NoDefinitionFound)
55 76
       end
56 77
 
57 78
       it 'handles route params properly' do
58  
-        found = find(:method => 'GET', :path => '/users/17/overview', :version => '1.3')
  79
+        found = find_with_status_code('200', :method => 'GET', :path => '/users/17/overview',
  80
+          :version => '1.3', :message_type => 'response')
  81
+        found.endpoint_name.should be(endpoint_1.name)
  82
+        found.status_codes.should eq('2xx')
  83
+      end
  84
+
  85
+      it 'handles status code params properly' do
  86
+        found = find_with_status_code('403', :method => 'GET', :path => '/users/17/overview',
  87
+          :version => '1.3', :message_type => 'response')
59 88
         found.endpoint_name.should be(endpoint_1.name)
  89
+        found.status_codes.should eq('xxx')
60 90
       end
61 91
     end
62 92
   end
@@ -138,7 +168,9 @@ def find(options)
138 168
         def assert_expected_endpoint
139 169
           config.endpoints.size.should eq(1)
140 170
           endpoint = config.endpoints.first
141  
-          endpoint.definitions.first.schema.fetch("properties").should have_key("name")
  171
+          endpoint.definitions.first.each do |definitions|
  172
+            definitions.schema.fetch("properties").should have_key("name")
  173
+          end
142 174
         end
143 175
 
144 176
         it 'supports the merge keys when configured before the endpoint definition files' do
124  spec/unit/interpol/endpoint_spec.rb
@@ -54,17 +54,32 @@ def build_hash(hash = {})
54 54
       'examples' => ['e1', 'e2']
55 55
     }] end
56 56
 
  57
+    let(:request_definition_array) do [{
  58
+      'versions'      => ['1.1'],
  59
+      'message_type'  => 'request',
  60
+      'schema'        => {'a' => ' request schema'},
  61
+      'examples'      => ['e1', 'e2']
  62
+    }] end
  63
+
57 64
     describe "#definitions" do
58 65
       it 'returns each definition object, ordered by version' do
59 66
         endpoint = Endpoint.new(build_hash('definitions' => definitions_array))
60  
-        endpoint.definitions.map(&:version).should eq(%w[ 1.2 3.2 ])
  67
+        endpoint.definitions.map{|d|d.first.version}.should eq(%w[ 3.2 1.2 ])
61 68
       end
  69
+
  70
+      it 'returns each definition object, ordered by message type' do
  71
+        full_definitions_array = (definitions_array + request_definition_array)
  72
+        endpoint = Endpoint.new(build_hash('definitions' => full_definitions_array))
  73
+        endpoint.definitions.map{|d|d.first.version}.should eq(%w[ 1.1 3.2 1.2 ])
  74
+        endpoint.definitions.map{|d|d.first.message_type}.should eq(%w[ request response response ])
  75
+      end
  76
+
62 77
     end
63 78
 
64 79
     describe "#available_versions" do
65 80
       it 'returns the list of available version strings, ordered by version' do
66 81
         endpoint = Endpoint.new(build_hash('definitions' => definitions_array))
67  
-        endpoint.available_versions.should eq(%w[ 1.2 3.2 ])
  82
+        endpoint.available_versions.should eq(%w[ 3.2 1.2 ])
68 83
       end
69 84
     end
70 85
 
@@ -72,13 +87,15 @@ def build_hash(hash = {})
72 87
       let(:hash) { build_hash('definitions' => definitions_array) }
73 88
       let(:endpoint) { Endpoint.new(hash) }
74 89
 
75  
-      it 'finds the definition matching the given version' do
76  
-        endpoint.find_definition!('1.2').version.should eq('1.2')
  90
+      it 'finds the definition matching the given version and message_type' do
  91
+        definitions = endpoint.find_definition!('1.2', 'response')
  92
+        definitions.first.version.should eq('1.2')
  93
+        definitions.first.message_type.should eq('response')
77 94
       end
78 95
 
79 96
       it 'raises an error when given a version that matches no definition' do
80 97
         expect {
81  
-          endpoint.find_definition!('2.1')
  98
+          endpoint.find_definition!('2.1', 'response')
82 99
         }.to raise_error(ArgumentError)
83 100
       end
84 101
     end
@@ -88,12 +105,12 @@ def build_hash(hash = {})
88 105
       let(:endpoint) { Endpoint.new(hash) }
89 106
 
90 107
       it 'returns an example for the requested version' do
91  
-        endpoint.find_example_for!('1.2').data.should eq('e1')
  108
+        endpoint.find_example_for!('1.2', 'response').data.should eq('e1')
92 109
       end
93 110
 
94 111
       it 'raises an error when given a version it does not have' do
95 112
         expect {
96  
-          endpoint.find_example_for!('2.1')
  113
+          endpoint.find_example_for!('2.1', 'response')
97 114
         }.to raise_error(ArgumentError)
98 115
       end
99 116
     end
@@ -142,20 +159,35 @@ def build_hash(hash = {})
142 159
     let(:version)  { '1.0' }
143 160
 
144 161
     it 'initializes the endpoint_name' do
145  
-      EndpointDefinition.new("e-name", version, build_hash).endpoint_name.should eq("e-name")
  162
+      endpoint_def = EndpointDefinition.new("e-name", version, 'response', build_hash)
  163
+      endpoint_def.endpoint_name.should eq("e-name")
146 164
     end
147 165
 
148 166
     it 'initializes the version' do
149  
-      EndpointDefinition.new("name", '2.3', build_hash).version.should eq('2.3')
  167
+      endpoint_def = EndpointDefinition.new("name", '2.3', 'response', build_hash)
  168
+      endpoint_def.version.should eq('2.3')
  169
+    end
  170
+
  171
+    it 'default initialized the message type' do
  172
+      endpoint_def = EndpointDefinition.new("name", '2.3', 'response', build_hash)
  173
+      endpoint_def.message_type.should eq('response')
  174
+    end
  175
+
  176
+    it 'initializes the message type' do
  177
+      hash = build_hash('message_type' => 'request')
  178
+      endpoint_def = EndpointDefinition.new("name", '2.3', 'request', hash)
  179
+      endpoint_def.message_type.should eq('request')
150 180
     end
151 181
 
152 182
     it 'initializes the example data' do
153  
-      v = EndpointDefinition.new("name", version, build_hash('examples' => [{'a' => 5}]))
  183
+      hash = build_hash('examples' => [{'a' => 5}])
  184
+      v = EndpointDefinition.new("name", version, 'response', hash)
154 185
       v.examples.map(&:data).should eq([{ 'a' => 5 }])
155 186
     end
156 187
 
157 188
     it 'initializes the schema' do
158  
-      v = EndpointDefinition.new("name", version, build_hash('schema' => {'the' => 'schema'}))
  189
+      hash = build_hash('schema' => {'the' => 'schema'})
  190
+      v = EndpointDefinition.new("name", version, 'response', hash)
159 191
       v.schema['the'].should eq('schema')
160 192
     end
161 193
 
@@ -163,7 +195,7 @@ def build_hash(hash = {})
163 195
       it "raises an error if not initialized with '#{attr}'" do
164 196
         hash = build_hash.reject { |k, v| k == attr }
165 197
         expect {
166  
-          EndpointDefinition.new("name", version, hash)
  198
+          EndpointDefinition.new("name", version, 'response', hash)
167 199
         }.to raise_error(/key not found.*#{attr}/)
168 200
       end
169 201
     end
@@ -174,7 +206,9 @@ def build_hash(hash = {})
174 206
         'properties' => {'foo' => { 'type' => 'integer' } }
175 207
       } end
176 208
 
177  
-      subject { EndpointDefinition.new("e-name", version, build_hash('schema' => schema)) }
  209
+      subject {
  210
+        EndpointDefinition.new("e-name", version, 'response', build_hash('schema' => schema))
  211
+      }
178 212
 
179 213
       it 'raises a validation error when given data of the wrong type' do
180 214
         expect {
@@ -194,7 +228,8 @@ def build_hash(hash = {})
194 228
       end
195 229
 
196 230
       it 'rejects unrecognized data types' do
197  
-        pending "waiting for my json-schema PR to be merged: https://github.com/hoxworth/json-schema/pull/37" do
  231
+        pending "waiting for my json-schema PR to be merged: " +
  232
+           "https://github.com/hoxworth/json-schema/pull/37" do
198 233
           schema['properties']['foo']['type'] = 'sting'
199 234
           expect {
200 235
             subject.validate_data!('foo' => 'bar')
@@ -288,6 +323,67 @@ def build_hash(hash = {})
288 323
     end
289 324
   end
290 325
 
  326
+  describe StatusCodeMatcher do
  327
+    describe "#new" do
  328
+      it 'initializes the codes for nil' do
  329
+        StatusCodeMatcher.new(nil).code_strings.should == ['xxx']
  330
+      end
  331
+
  332
+      it 'initializs the codes for a single code' do
  333
+        StatusCodeMatcher.new(['200']).code_strings.should == ["200"]
  334
+      end
  335
+
  336
+      it 'initializs the codes for a multiple codes' do
  337
+        StatusCodeMatcher.new(['200', '4xx', 'x0x']).code_strings.should == ['200', '4xx', 'x0x']
  338
+      end
  339
+
  340
+      it 'should raise an error for invalid status code formats' do
  341
+        expect {
  342
+          StatusCodeMatcher.new(['200', '4y4'])
  343
+        }.to raise_error(StatusCodeMatcherArgumentError)
  344
+
  345
+        expect {
  346
+          StatusCodeMatcher.new(['2000', '404'])
  347
+        }.to raise_error(StatusCodeMatcherArgumentError)
  348
+      end
  349
+    end
  350
+
  351
+    describe "#matches?" do
  352
+      let(:nil_codes_subject) { StatusCodeMatcher.new(nil) }
  353
+      it 'returns true when codes is nil' do
  354
+        nil_codes_subject.matches?('200').should be_true
  355
+      end
  356
+
  357
+      subject { StatusCodeMatcher.new(['200', '4xx', 'x5x']) }
  358
+      it 'returns true for an exact match' do
  359
+        subject.matches?('200').should be_true
  360
+      end
  361
+
  362
+      it 'returns true for a partial matches' do
  363
+        subject.matches?('401').should be_true
  364
+        subject.matches?('454').should be_true
  365
+      end
  366
+
  367
+      it 'returns false for no matches' do
  368
+        subject.matches?('202').should be_false
  369
+      end
  370
+    end
  371
+
  372
+    describe '#example_status_code' do
  373
+      it 'returns a valid example status code when a specific status code was given' do
  374
+        StatusCodeMatcher.new(['404']).example_status_code.should == '404'
  375
+      end
  376
+
  377
+      it 'returns a valid example status code when no status codes were given' do
  378
+        StatusCodeMatcher.new(nil).example_status_code.should == '200'
  379
+      end
  380
+
  381
+      it 'returns a valid example status code based on the first status code' do
  382
+        StatusCodeMatcher.new(['4xx', 'x0x']).example_status_code.should == '400'
  383
+      end
  384
+    end
  385
+  end
  386
+
291 387
   describe EndpointExample do
292 388
     describe "#validate!" do
293 389
       let(:definition) { fire_double("Interpol::EndpointDefinition") }
2  spec/unit/interpol/response_schema_validator_spec.rb
@@ -80,7 +80,7 @@ def stub_lookup(v = validator)
80 80
       validator.should_receive(:validate_data!).with("a" => "b")
81 81
 
82 82
       default_definition_finder.should_receive(:find_definition).
83  
-        with("GET", "/search/200/overview").
  83
+        with("GET", "/search/200/overview", "response", 200).
84 84
         and_return(validator)
85 85
 
86 86
       get '/search/200/overview'
2  spec/unit/interpol/stub_app_spec.rb
@@ -101,7 +101,7 @@ def parsed_body
101 101
     end
102 102
 
103 103
     let(:endpoint_example) do
104  
-      endpoint.find_example_for!('1.0')
  104
+      endpoint.find_example_for!('1.0', 'response')
105 105
     end
106 106
 
107 107
     it 'performs validations by default' do

0 notes on commit 301263d

Please sign in to comment.
Something went wrong with that request. Please try again.