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

Sign up to get free protection for your applications and to get access to all the features.
@@ -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