cucumber-rest-bdd 0.3.4.pre.alpha.pre.86

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.
@@ -0,0 +1,21 @@
1
+ Feature: Performing different rest methods
2
+
3
+ Background:
4
+ Given I am a client
5
+
6
+ Scenario: Count the number of elements
7
+ When I request a list of posts with:
8
+ | User Id | 8 |
9
+ Then the request is successful
10
+ And the JSON response should have "$." of type array with 10 entries
11
+
12
+ Scenario: Check for null type
13
+ When I request to create a post with:
14
+ | attribute | type | value |
15
+ | title | string | foo |
16
+ | body | text | bar |
17
+ | null | null | |
18
+ | nil | nil | |
19
+ Then the JSON response should have "null" of type null
20
+ Then the JSON response should have "nil" of type nil
21
+ Then the JSON response should have "nil" of type nill
@@ -0,0 +1,21 @@
1
+ Feature: Using the response from a previous request
2
+
3
+ Background:
4
+ Given I am a client
5
+
6
+ Scenario: Get an id from creation and use in get
7
+ When I request to create a post with:
8
+ | attribute | type | value |
9
+ | Title | string | foo |
10
+ | Body | string | bar |
11
+ | User Id | integer | 12 |
12
+ Then the request is successful
13
+ When I save "id"
14
+ And I request the post "{id}"
15
+ Then the request is successful
16
+ And the response has the following attributes:
17
+ | attribute | type | value |
18
+ | Title | string | foo |
19
+ | Body | string | bar |
20
+ | User Id | numeric | 12 |
21
+ | Id | numeric | {id} |
@@ -0,0 +1,16 @@
1
+ Feature: We can inspect the headers of the response
2
+
3
+ Scenario: Parse a single result
4
+ When I request the post "1"
5
+ Then the request was successful
6
+ And the response has the header "Content Type" with value "application/json; charset=utf-8"
7
+
8
+ Scenario Outline: Can check for multiple headers
9
+ When I request the post "1"
10
+ Then the request was successful
11
+ And the response has the header "<header>" with the value "<value>"
12
+
13
+ Examples:
14
+ | header | value |
15
+ | Content Type | application/json; charset=utf-8 |
16
+ | Cache Control | no-cache |
@@ -0,0 +1,101 @@
1
+ Feature: Performing different rest methods
2
+
3
+ Background:
4
+ Given I am a client
5
+
6
+ Scenario: Retrieve a single item
7
+ When I request the post "1"
8
+ Then the request was successful
9
+ And the response has the following attributes:
10
+ | attribute | type | value |
11
+ | User Id | numeric | 1 |
12
+ | Id | numeric | 1 |
13
+ | Title | string | sunt aut facere repellat provident occaecati excepturi optio reprehenderit |
14
+ | Body | string | quia et suscipit\\nsuscipit recusandae consequuntur expedita et cum\\nreprehenderit molestiae ut ut quas totam\\nnostrum rerum est autem sunt rem eveniet architecto |
15
+
16
+ Scenario: Retrieve multiple items
17
+ When I request a list of posts
18
+ Then the request was successful
19
+ And the response is a list of at least 2 posts
20
+ And one post has the following attributes:
21
+ | attribute | type | value |
22
+ | User Id | numeric | 1 |
23
+ | Id | numeric | 1 |
24
+ | Title | string | sunt aut facere repellat provident occaecati excepturi optio reprehenderit |
25
+ | Body | string | quia et suscipit\\nsuscipit recusandae consequuntur expedita et cum\\nreprehenderit molestiae ut ut quas totam\\nnostrum rerum est autem sunt rem eveniet architecto |
26
+ And one post has the following attributes:
27
+ | attribute | type | value |
28
+ | User Id | numeric | 1 |
29
+ | Id | numeric | 2 |
30
+ | Title | string | qui est esse |
31
+ | Body | string | est rerum tempore vitae\\nsequi sint nihil reprehenderit dolor beatae ea dolores neque\\nfugiat blanditiis voluptate porro vel nihil molestiae ut reiciendis\\nqui aperiam non debitis possimus qui neque nisi nulla |
32
+ And ten posts have the following attributes:
33
+ | attribute | type | value |
34
+ | User Id | numeric | 5 |
35
+ And at most 20 posts have the following attributes:
36
+ | attribute | type | value |
37
+ | User Id | numeric | 5 |
38
+
39
+ Scenario: Retrieve multiple items with filter
40
+ When I request a list of posts with:
41
+ | User Id | 2 |
42
+ Then the request is successful
43
+ And the response is a list of at least 2 posts
44
+ And the response is a list of fewer than 100 posts
45
+ And one post has the following attributes:
46
+ | attribute | type | value |
47
+ | User Id | numeric | 2 |
48
+ | Id | numeric | 11 |
49
+ | Title | string | et ea vero quia laudantium autem |
50
+ | Body | string | delectus reiciendis molestiae occaecati non minima eveniet qui voluptatibus\\naccusamus in eum beatae sit\\nvel qui neque voluptates ut commodi qui incidunt\nut animi commodi |
51
+ And one post has the following attributes:
52
+ | attribute | type | value |
53
+ | User Id | numeric | 2 |
54
+ | Id | numeric | 12 |
55
+ | Title | string | in quibusdam tempore odit est dolorem |
56
+ | Body | string | itaque id aut magnam\\npraesentium quia et ea odit et ea voluptas et\\nsapiente quia nihil amet occaecati quia id voluptatem\\nincidunt ea est distinctio odio |
57
+ And more than two posts has the following attributes:
58
+ | attribute | type | value |
59
+ | User Id | numeric | 2 |
60
+
61
+ Scenario: Create an item
62
+ When I request to create a post with:
63
+ | attribute | type | value |
64
+ | Title | string | foo |
65
+ | Body | string | bar |
66
+ | User Id | numeric | 1 |
67
+ Then the request is successful and a post was created
68
+ And the response has the following attributes:
69
+ | attribute | type | value |
70
+ | User Id | numeric | 1 |
71
+ | Title | string | foo |
72
+ | Body | string | bar |
73
+
74
+ Scenario: Remove an item
75
+ When I request to remove the post "20"
76
+ Then the request is successful
77
+
78
+ Scenario: Modify an item
79
+ When I request to modify the post "21" with:
80
+ | attribute | type | value |
81
+ | Title | string | foo |
82
+ Then the request is successful
83
+ And the response has the following attributes:
84
+ | attribute | type | value |
85
+ | User Id | numeric | 3 |
86
+ | Title | string | foo |
87
+ | Body | string | repellat aliquid praesentium dolorem quo\\nsed totam minus non itaque\\nnihil labore molestiae sunt dolor eveniet hic recusandae veniam\\ntempora et tenetur expedita sunt |
88
+
89
+ Scenario: Update an item with Id
90
+ When I request to create a post "22" with:
91
+ | attribute | type | value |
92
+ | Title | string | foo |
93
+ | Body | string | bar |
94
+ | User Id | numeric | 1 |
95
+ Then the request is successful
96
+ And the response has the following attributes:
97
+ | attribute | type | value |
98
+ | User Id | numeric | 1 |
99
+ | Id | numeric | 22 |
100
+ | Title | string | foo |
101
+ | Body | string | bar |
@@ -0,0 +1,103 @@
1
+ Feature: Dealing with sub objects
2
+
3
+ Background:
4
+ Given I am a client
5
+
6
+ Scenario: Create and read an item with child objects
7
+ When I request to create a post with:
8
+ | attribute | type | value |
9
+ | Title | string | foo |
10
+ | Body | string | bar |
11
+ | User : Id | numeric | 1 |
12
+ | User : name | string | name |
13
+ Then the request is successful and a post was created
14
+ And the response has the following attributes:
15
+ | attribute | type | value |
16
+ | Title | string | foo |
17
+ | Body | string | bar |
18
+ | User : Id | numeric | 1 |
19
+ | User : name | string | name |
20
+
21
+ Scenario: Request a single item with child objects
22
+ When I request the comment "1" with:
23
+ | `_expand` | post |
24
+ Then the response has the following attributes:
25
+ | attribute | type | value |
26
+ | name | string | id labore ex et quam laborum |
27
+ | email | string | Eliseo@gardner.biz |
28
+ | body | string | laudantium enim quasi est quidem magnam voluptate ipsam eos\\ntempora quo necessitatibus\\ndolor quam autem quasi\\nreiciendis et nam sapiente accusantium |
29
+ | post : title | string | sunt aut facere repellat provident occaecati excepturi optio reprehenderit |
30
+ | post : body | string | quia et suscipit\\nsuscipit recusandae consequuntur expedita et cum\\nreprehenderit molestiae ut ut quas totam\\nnostrum rerum est autem sunt rem eveniet architecto |
31
+
32
+ Scenario: Request an item within a list of items
33
+ When I request a list of comments with:
34
+ | `_expand` | post |
35
+ | Post ID | 1 |
36
+ Then the response is a list of more than 1 comment
37
+ And one comment has the following attributes:
38
+ | attribute | type | value |
39
+ | name | string | id labore ex et quam laborum |
40
+ | email | string | Eliseo@gardner.biz |
41
+ | body | string | laudantium enim quasi est quidem magnam voluptate ipsam eos\\ntempora quo necessitatibus\\ndolor quam autem quasi\\nreiciendis et nam sapiente accusantium |
42
+ | post : title | string | sunt aut facere repellat provident occaecati excepturi optio reprehenderit |
43
+ | post : body | string | quia et suscipit\\nsuscipit recusandae consequuntur expedita et cum\\nreprehenderit molestiae ut ut quas totam\\nnostrum rerum est autem sunt rem eveniet architecto |
44
+
45
+ Scenario: Match an item with a list of items
46
+ When I request the post "1" with:
47
+ | `_embed` | comments |
48
+ Then the response has the following attributes:
49
+ | attribute | type | value |
50
+ | title | string | sunt aut facere repellat provident occaecati excepturi optio reprehenderit |
51
+ | body | string | quia et suscipit\\nsuscipit recusandae consequuntur expedita et cum\\nreprehenderit molestiae ut ut quas totam\\nnostrum rerum est autem sunt rem eveniet architecto |
52
+ And the response has one comment with attributes:
53
+ | attribute | type | value |
54
+ | name | string | id labore ex et quam laborum |
55
+ | email | string | Eliseo@gardner.biz |
56
+ | body | string | laudantium enim quasi est quidem magnam voluptate ipsam eos\\ntempora quo necessitatibus\\ndolor quam autem quasi\\nreiciendis et nam sapiente accusantium |
57
+ And the response has five comments with attributes:
58
+ | attribute | type | value |
59
+ | Post Id | integer | 1 |
60
+ And the response has at least five comments
61
+
62
+ Scenario: Multiple levels
63
+ When I set JSON request body to:
64
+ """
65
+ {"title":"test","body":"multiple",
66
+ "comments":[
67
+ {"common":1,"id":1,"title":"fish","body":"cake","image":{"href":"some_url"}},
68
+ {"common":1,"id":2,"title":"foo","body":"bar","image":{"href":"some_url"}}
69
+ ]}
70
+ """
71
+ And I send a POST request to "http://test-server/posts"
72
+ Then the response has the attributes:
73
+ | attribute | type | value |
74
+ | title | string | test |
75
+ | body | string | multiple |
76
+ And the response has a list of comments
77
+ And the response has a list of 2 comments
78
+ And the response has two comments with attributes:
79
+ | attribute | type | value |
80
+ | common | integer | 1 |
81
+ And the response has two comments with an image with attributes:
82
+ | attribute | type | value |
83
+ | href | string | some_url |
84
+ And the response has one comment with attributes:
85
+ | attribute | type | value |
86
+ | Id | integer | 1 |
87
+ | Title | string | fish |
88
+ | Body | string | cake |
89
+ And the response has one comment with attributes:
90
+ | attribute | type | value |
91
+ | Id | integer | 2 |
92
+ | Title | string | foo |
93
+ | Body | string | bar |
94
+
95
+ Scenario: Multiple levels in array
96
+ When I request a list of posts with:
97
+ | `_embed` | comments |
98
+ Then the request is successful
99
+ And one post has one comment with attributes:
100
+ | attribute | type | value |
101
+ | Id | integer | 1 |
102
+ And at least ten posts contain a list of five comments
103
+ And more than 95 posts contain more than four comments
@@ -0,0 +1,18 @@
1
+ Feature: I can handle different response codes
2
+
3
+ Background:
4
+ Given I am a client
5
+
6
+ Scenario: Successful response
7
+ When I request the post "1"
8
+ Then the request is successful
9
+
10
+ Scenario: Key not found
11
+ When I request the post "not-found"
12
+ Then the request fails
13
+ And it fails because the post was not found
14
+
15
+ Scenario: Resource not found
16
+ When I request the Not Found "5"
17
+ Then the request fails
18
+ And it fails because the not found was not found
@@ -0,0 +1,2 @@
1
+ require 'cucumber-rest-bdd'
2
+ require 'cucumber-api'
@@ -0,0 +1,48 @@
1
+ Feature: Dealing with sub objects
2
+
3
+ Background:
4
+ Given I am a client
5
+
6
+ Scenario: Handle null values
7
+ When I request to create a post with:
8
+ | attribute | type | value |
9
+ | title | string | foo |
10
+ | body | string | bar |
11
+ | extra | null | |
12
+ Then the request was successful
13
+ And the response has the following attributes:
14
+ | attribute | type | value |
15
+ | title | string | foo |
16
+ | body | string | bar |
17
+ | extra | nil | |
18
+
19
+ Scenario: Handle types
20
+ When I request to create a post with:
21
+ | attribute | type | value |
22
+ | title | string | foo |
23
+ | body | text | bar |
24
+ | int | int | 1 |
25
+ | long | long | 1347289473247823749 |
26
+ | float | float | 1.2 |
27
+ | double | double | 1.4 |
28
+ | number | number | 12 |
29
+ | numeric | numeric | 15 |
30
+ | bool | bool | true |
31
+ | boolean | boolean | false |
32
+ | null | null | |
33
+ | nil | nil | |
34
+ Then the request was successful
35
+ And the response has the following attributes:
36
+ | attribute | type | value |
37
+ | title | string | foo |
38
+ | body | text | bar |
39
+ | int | int | 1 |
40
+ | long | long | 1347289473247824000 |
41
+ | float | float | 1.2 |
42
+ | double | double | 1.4 |
43
+ | number | number | 12 |
44
+ | numeric | numeric | 15 |
45
+ | bool | bool | true |
46
+ | boolean | boolean | false |
47
+ | null | null | |
48
+ | nil | nil | |
@@ -0,0 +1,9 @@
1
+ require 'easy_diff'
2
+
3
+ class Hash
4
+ def deep_include?(other)
5
+ diff = other.easy_diff(self)
6
+ diff[0].delete_if { |k, v| v.empty? if v.is_a?(::Hash) }
7
+ diff[0].empty?
8
+ end
9
+ end
@@ -0,0 +1,25 @@
1
+ require 'cucumber-api/response'
2
+ require 'cucumber-api/steps'
3
+
4
+ Then(/^the response (?:should have|has a|has the) header "([^"]*)" with (?:a |the )?value "([^"]*)"$/) do |header, value|
5
+ p_value = resolve(value)
6
+ p_header = header.parameterize
7
+ raise %/Required header: #{header} not found\n#{@response.raw_headers.inspect}/ if !@response.raw_headers.key?(p_header)
8
+ exists = @response.raw_headers[p_header].include? p_value
9
+ raise %/Expect #{p_value} in #{header} (#{p_header})\n#{@response.raw_headers.inspect}/ if !exists
10
+ end
11
+
12
+ Then(/^the JSON response should have "([^"]*)" of type array with (\d+) entr(?:y|ies)$/) do |json_path, number|
13
+ list = @response.get_as_type json_path, 'array'
14
+ raise %/Expected #{number} items in array for path '#{json_path}', found: #{list.count}\n#{@response.to_json_s}/ if list.count != number.to_i
15
+ end
16
+
17
+ Then(/^the JSON response should have "([^"]*)" of type (.+) that matches "(.+)"$/) do |json_path, type, regex|
18
+ value = @response.get_as_type json_path, type
19
+ raise %/Expected #{json_path} value '#{value}' to match regex: #{regex}\n#{@response.to_json_s}/ if (Regexp.new(regex) =~ value).nil?
20
+ end
21
+
22
+ Then(/^the JSON response should have "([^"]*)" of type (?:nill|null|nil)$/) do |json_path|
23
+ value = @response.get_as_type_or_null json_path, 'string'
24
+ raise %/Expected #{json_path} to be nil, was: #{value.class}\n#{@response.to_json_s}/ if !value.nil?
25
+ end
@@ -0,0 +1,127 @@
1
+ require 'cucumber-api/response'
2
+ require 'cucumber-api/steps'
3
+ require 'active_support/inflector'
4
+ require 'cucumber-rest-bdd/url'
5
+ require 'cucumber-rest-bdd/types'
6
+ require 'cucumber-rest-bdd/hash'
7
+ require 'easy_diff'
8
+
9
+ Given(/^I am a client$/) do
10
+ steps %Q{
11
+ Given I send "application/json" and accept JSON
12
+ }
13
+ end
14
+
15
+ # GET
16
+
17
+ When(/^I request (?:an?|the) (.+?)(?: with (?:key|id))? "([^"]*)"$/) do |resource, token|
18
+ resource_name = get_resource(resource)
19
+ url = get_url("#{resource_name}/#{token}")
20
+ steps %Q{When I send a GET request to "#{url}"}
21
+ end
22
+
23
+ When(/^I request (?:an?|the) (.+?)(?: with (?:key|id))? "([^"]*)" with:$/) do |resource, token, params|
24
+ resource_name = get_resource(resource)
25
+ url = get_url("#{resource_name}/#{token}")
26
+ unless params.raw.empty?
27
+ query = params.raw.map{|key, value| %/#{get_parameter(key)}=#{resolve(value)}/}.join("&")
28
+ url = "#{url}?#{query}"
29
+ end
30
+ steps %Q{When I send a GET request to "#{url}"}
31
+ end
32
+
33
+ When(/^I request a list of ([^:]+)$/) do |resource|
34
+ resource_name = get_resource(resource)
35
+ url = get_url("#{resource_name}")
36
+ steps %Q{When I send a GET request to "#{url}"}
37
+ end
38
+
39
+ When(/^I request a list of (.+) with:$/) do |resource, params|
40
+ resource_name = get_resource(resource)
41
+ url = get_url("#{resource_name}")
42
+ unless params.raw.empty?
43
+ query = params.raw.map{|key, value| %/#{get_parameter(key)}=#{resolve(value)}/}.join("&")
44
+ url = "#{url}?#{query}"
45
+ end
46
+ steps %Q{When I send a GET request to "#{url}"}
47
+ end
48
+
49
+ # DELETE
50
+
51
+ When(/^I request to (?:delete|remove) the (.+) "([^"]*)"$/) do |resource, token|
52
+ resource_name = get_resource(resource)
53
+ url = get_url("#{resource_name}/#{token}")
54
+ steps %Q{When I send a DELETE request to "#{url}"}
55
+ end
56
+
57
+ # POST
58
+
59
+ When(/^I request to create an? ([^:]+?)$/) do |resource|
60
+ resource_name = get_resource(resource)
61
+ url = get_url("#{resource_name}")
62
+ steps %Q{When I send a POST request to "#{url}"}
63
+ end
64
+
65
+ When(/^I request to create an? ([^"]+?) with:$/) do |resource, params|
66
+ resource_name = get_resource(resource)
67
+ request_hash = get_attributes(params.hashes)
68
+ json = MultiJson.dump(request_hash)
69
+ url = get_url("#{resource_name}")
70
+ steps %Q{
71
+ When I set JSON request body to:
72
+ """
73
+ #{json}
74
+ """
75
+ And I send a POST request to "#{url}"
76
+ }
77
+ end
78
+
79
+ # PUT
80
+
81
+ When(/^I request to (?:create|replace) (?:an?|the) ([^"]+?)(?: with (?:key|id))? "([^"]+)"$/) do |resource, id|
82
+ resource_name = get_resource(resource)
83
+ url = get_url("#{resource_name}/#{id}")
84
+ steps %Q{
85
+ When I send a PUT request to "#{url}"
86
+ }
87
+ end
88
+
89
+ When(/^I request to (?:create|replace) (?:an?|the) ([^"]+?)(?: with (?:key|id))? "([^"]+)" with:$/) do |resource, id, params|
90
+ resource_name = get_resource(resource)
91
+ request_hash = get_attributes(params.hashes)
92
+ json = MultiJson.dump(request_hash)
93
+ url = get_url("#{resource_name}/#{id}")
94
+ steps %Q{
95
+ When I set JSON request body to:
96
+ """
97
+ #{json}
98
+ """
99
+ And I send a PUT request to "#{url}"
100
+ }
101
+ end
102
+
103
+ # PATCH
104
+
105
+ When(/^I request to modify the (.+?)(?: with (?:key|id))? "([^"]+)" with:$/) do |resource, id, params|
106
+ resource_name = get_resource(resource)
107
+ request_hash = get_attributes(params.hashes)
108
+ json = MultiJson.dump(request_hash)
109
+ url = get_url("#{resource_name}/#{id}")
110
+ steps %Q{
111
+ When I set JSON request body to:
112
+ """
113
+ #{json}
114
+ """
115
+ And I send a PATCH request to "#{url}"
116
+ }
117
+ end
118
+
119
+ # value capture
120
+
121
+ When(/^I save (?:attribute )?"([^"]+)"$/) do |attribute|
122
+ steps %Q{When I grab "#{get_json_path(attribute)}"}
123
+ end
124
+
125
+ When(/^I save (?:attribute )?"([^"]+)" to "([^"]+)"$/) do |attribute, ref|
126
+ steps %Q{When I grab "#{get_json_path(attribute)}" as "#{ref}"}
127
+ end
@@ -0,0 +1,156 @@
1
+ require 'cucumber-rest-bdd/steps/resource'
2
+ require 'cucumber-rest-bdd/types'
3
+
4
+ # response interrogation
5
+
6
+ Then(/^the response is a list (?:of|containing) (#{FEWER_MORE_THAN})?\s*(#{CAPTURE_INT}|\d+) .*?$/) do |count_mod, count|
7
+ list = @response.get_as_type get_root_json_path(), 'array'
8
+ raise %/Expected at least #{count} items in array for path '#{get_root_json_path()}', found: #{list.count}\n#{@repsponse.to_json_s}/ if !num_compare(count_mod, list.count, count.to_i)
9
+ end
10
+
11
+ Then(/^the response ((?:#{HAVE_SYNONYM} (?:a|an|(?:(?:#{FEWER_MORE_THAN})?\s*#{CAPTURE_INT}|\d+)) (?:\w+) )*)#{HAVE_SYNONYM} (?:the )?(?:following )?(?:data )?attributes:$/) do |nesting, attributes|
12
+ expected = get_attributes(attributes.hashes)
13
+ groups = nesting
14
+ grouping = get_grouping(groups)
15
+ grouping.push({
16
+ root: true,
17
+ type: 'single'
18
+ })
19
+ data = @response.get get_root_json_path()
20
+ raise %/Could not find a match for: #{nesting}\n#{@response.to_json_s}/ if !nest_match(data, grouping, expected)
21
+ end
22
+
23
+ Then(/^the response ((?:#{HAVE_SYNONYM} (?:a|an|(?:(?:#{FEWER_MORE_THAN})?\s*#{CAPTURE_INT}|\d+)) (?:\w+)\s?)+)$/) do |nesting|
24
+ groups = nesting
25
+ grouping = get_grouping(groups)
26
+ grouping.push({
27
+ root: true,
28
+ type: 'single'
29
+ })
30
+ data = @response.get get_root_json_path()
31
+ raise %/Could not find a match for: #{nesting}\n#{@response.to_json_s}/ if !nest_match(data, grouping, {})
32
+ end
33
+
34
+ Then(/^(#{FEWER_MORE_THAN})?\s*(#{CAPTURE_INT}|\d+) (?:.*?) ((?:#{HAVE_SYNONYM} (?:a|an|(?:(?:#{FEWER_MORE_THAN})?\s*#{CAPTURE_INT}|\d+)) (?:\w+) )*)#{HAVE_SYNONYM} (?:the )?(?:following )?(?:data )?attributes:$/) do |count_mod, count, nesting, attributes|
35
+ expected = get_attributes(attributes.hashes)
36
+ groups = nesting
37
+ grouping = get_grouping(groups)
38
+ grouping.push({
39
+ root: true,
40
+ type: 'multiple',
41
+ count: count.to_i,
42
+ count_mod: count_mod
43
+ })
44
+ data = @response.get get_root_json_path()
45
+ raise %/Expected #{compare_to_string(count_mod)}#{count} items in array with attributes for: #{nesting}\n#{@response.to_json_s}/ if !nest_match(data, grouping, expected)
46
+ end
47
+
48
+ Then(/^(#{FEWER_MORE_THAN})?\s*(#{CAPTURE_INT}|\d+) (?:.*?) ((?:#{HAVE_SYNONYM} (?:a|an|(?:(?:#{FEWER_MORE_THAN})?\s*#{CAPTURE_INT}|\d+)) (?:\w+)\s?)+)$/) do |count_mod, count, nesting|
49
+ groups = nesting
50
+ grouping = get_grouping(groups)
51
+ grouping.push({
52
+ root: true,
53
+ type: 'multiple',
54
+ count: count.to_i,
55
+ count_mod: count_mod
56
+ })
57
+ data = @response.get get_root_json_path()
58
+ raise %/Expected #{compare_to_string(count_mod)}#{count} items in array with: #{nesting}\n#{@response.to_json_s}/ if !nest_match(data, grouping, {})
59
+ end
60
+
61
+ Then(/^the response ((?:#{HAVE_SYNONYM} (?:a|an|(?:(?:#{FEWER_MORE_THAN})?\s*#{CAPTURE_INT}|\d+)) (?:\w+) )*)#{HAVE_SYNONYM} a list of (#{FEWER_MORE_THAN})?\s*(#{CAPTURE_INT} |\d+ )?(\w+)$/) do |nesting, num_mod, num, item|
62
+ groups = nesting
63
+ list = {
64
+ type: 'list',
65
+ key: get_resource(item)
66
+ }
67
+ if (num) then
68
+ list[:count] = num.to_i
69
+ list[:count_mod] = num_mod
70
+ end
71
+ grouping = [list]
72
+ grouping.concat(get_grouping(groups))
73
+ grouping.push({
74
+ root: true,
75
+ type: 'single'
76
+ })
77
+ data = @response.get get_root_json_path()
78
+ raise %/Could not find a match for #{nesting}#{compare_to_string(num_mod)}#{num} #{item}\n#{@response.to_json_s}/ if !nest_match(data, grouping, {})
79
+ end
80
+
81
+ Then(/^(#{FEWER_MORE_THAN})?\s*(#{CAPTURE_INT}|\d+) (?:.*?) ((?:#{HAVE_SYNONYM} (?:a|an|(?:(?:#{FEWER_MORE_THAN})?\s*#{CAPTURE_INT}|\d+)) (?:\w+) )*)#{HAVE_SYNONYM} a list of (#{FEWER_MORE_THAN})?\s*(?:(#{CAPTURE_INT}|\d+) )?(\w+)$/) do |count_mod, count, nesting, num_mod, num, item|
82
+ groups = nesting
83
+ list = {
84
+ type: 'list',
85
+ key: get_resource(item)
86
+ }
87
+ if (num) then
88
+ list[:count] = num.to_i
89
+ list[:count_mod] = num_mod
90
+ end
91
+ grouping = [list]
92
+ grouping.concat(get_grouping(groups))
93
+ grouping.push({
94
+ root: true,
95
+ type: 'multiple',
96
+ count: count.to_i,
97
+ count_mod: count_mod
98
+ })
99
+ data = @response.get get_root_json_path()
100
+ raise %/Expected #{compare_to_string(count_mod)}#{count} items with #{nesting}#{compare_to_string(num_mod)}#{num}#{item}\n#{@response.to_json_s}/ if !nest_match(data, grouping, {})
101
+ end
102
+
103
+ # gets an array in the nesting format that nest_match understands to interrogate nested object and array data
104
+ def get_grouping(nesting)
105
+ grouping = []
106
+ while matches = /^#{HAVE_SYNONYM} (?:a|an|(?:(#{FEWER_MORE_THAN})?\s*(#{CAPTURE_INT}|\d+))) (\w+)\s?+/.match(nesting)
107
+ nesting = nesting[matches[0].length, nesting.length]
108
+ if matches[2].nil? then
109
+ level = {
110
+ type: 'single',
111
+ key: get_parameter(matches[3]),
112
+ root: false
113
+ }
114
+ else
115
+ level = {
116
+ type: 'multiple',
117
+ key: get_parameter(matches[3]),
118
+ count: to_num(matches[2]),
119
+ root: false,
120
+ count_mod: to_compare(matches[1])
121
+ }
122
+ end
123
+ grouping.push(level)
124
+ end
125
+ grouping.reverse
126
+ end
127
+
128
+ # top level has 2 children with an item containing at most three fish with attributes:
129
+ #
130
+ # nesting = [{key=fish,count=3,count_mod='<=',type=multiple},{key=item,type=single},{key=children,type=multiple,count=2,count_mod='='},{root=true,type=single}]
131
+ #
132
+ # returns true if the expected data is contained within the data based on the nesting information
133
+ def nest_match(data, nesting, expected)
134
+ return data.deep_include?(expected) if nesting.size == 0
135
+
136
+ local_nesting = nesting.dup
137
+ level = local_nesting.pop
138
+ case level[:type]
139
+ when 'single' then
140
+ child_data = level[:root] ? data.dup : data[get_parameter(level[:key])]
141
+ return nest_match(child_data, local_nesting, expected)
142
+ when 'multiple' then
143
+ child_data = level[:root] ? data.dup : data[get_resource(level[:key])]
144
+ matched = child_data.select { |item| nest_match(item, local_nesting, expected) }
145
+ return num_compare(level[:count_mod], matched.count, level[:count])
146
+ when 'list' then
147
+ child_data = level[:root] ? data.dup : data[get_resource(level[:key])]
148
+ return false if !child_data.is_a?(Array)
149
+ if level.has_key?(:count) then
150
+ return num_compare(level[:count_mod], child_data.count, level[:count])
151
+ end
152
+ return true
153
+ else
154
+ raise %/Unknown nested data type: #{level[:type]}/
155
+ end
156
+ end