eipiai 0.6.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -4
  3. data/.rubocop.yml +3 -0
  4. data/.ruby-version +1 -0
  5. data/.wercker.yml +8 -1
  6. data/CHANGELOG.md +7 -0
  7. data/Gemfile +1 -0
  8. data/Rakefile +1 -0
  9. data/eipiai.gemspec +1 -0
  10. data/features/resources/api.feature +82 -0
  11. data/features/resources/collection.feature +60 -0
  12. data/features/resources/collection/get.feature +189 -0
  13. data/features/resources/collection/post.feature +230 -0
  14. data/features/resources/health.feature +27 -0
  15. data/features/resources/singular.feature +86 -0
  16. data/features/resources/singular/delete.feature +69 -0
  17. data/features/resources/singular/get.feature +146 -0
  18. data/features/resources/singular/post.feature +235 -0
  19. data/features/resources/singular/put.feature +164 -0
  20. data/features/step_definitions/steps.rb +10 -0
  21. data/features/step_definitions/webmachine_steps.rb +58 -0
  22. data/features/support/app.rb +14 -26
  23. data/features/support/env.rb +1 -0
  24. data/features/validation.feature +1 -0
  25. data/features/webmachine.feature +1 -56
  26. data/lib/eipiai.rb +1 -0
  27. data/lib/eipiai/configuration.rb +1 -0
  28. data/lib/eipiai/models.rb +1 -0
  29. data/lib/eipiai/models/collection.rb +1 -0
  30. data/lib/eipiai/models/representable.rb +6 -5
  31. data/lib/eipiai/roar.rb +1 -0
  32. data/lib/eipiai/roar/ext/hal.rb +3 -0
  33. data/lib/eipiai/roar/representers/api.rb +17 -6
  34. data/lib/eipiai/roar/representers/base.rb +1 -0
  35. data/lib/eipiai/validation.rb +1 -0
  36. data/lib/eipiai/validation/concerns/formatted_errors.rb +1 -0
  37. data/lib/eipiai/validation/validators/base.rb +2 -1
  38. data/lib/eipiai/validation/validators/sequel.rb +1 -0
  39. data/lib/eipiai/version.rb +2 -1
  40. data/lib/eipiai/webmachine.rb +2 -0
  41. data/lib/eipiai/webmachine/ext/decision.rb +1 -0
  42. data/lib/eipiai/webmachine/ext/request.rb +2 -1
  43. data/lib/eipiai/webmachine/resources/api.rb +7 -3
  44. data/lib/eipiai/webmachine/resources/base.rb +96 -19
  45. data/lib/eipiai/webmachine/resources/collection.rb +4 -18
  46. data/lib/eipiai/webmachine/resources/concerns/objectifiable.rb +1 -0
  47. data/lib/eipiai/webmachine/resources/concerns/representable.rb +69 -0
  48. data/lib/eipiai/webmachine/resources/health.rb +16 -10
  49. data/lib/eipiai/webmachine/resources/singular.rb +45 -10
  50. metadata +16 -4
  51. data/features/support/db.rb +0 -8
@@ -0,0 +1,27 @@
1
+ Feature: Request entrypoint of API
2
+
3
+ When a collection of resources exist
4
+ I want to use Webmachine to provide API access to those resource
5
+ So that the resources can easily be retrieved in one request
6
+
7
+ Scenario: Get service health
8
+ When the client does a GET request to "/health"
9
+ Then the status code should be "200" (OK)
10
+ And the response should be JSON:
11
+ """json
12
+ {
13
+ "healthy": true
14
+ }
15
+ """
16
+
17
+ Scenario: Get unhealthy service health
18
+ Given the configuration "healthy" is set to "false"
19
+ When the client does a GET request to "/health"
20
+ Then the status code should be "503" (Service Unavailable)
21
+ And the response contains the "Retry-After" header with value "60"
22
+ And the response should be JSON:
23
+ """json
24
+ {
25
+ "healthy": false
26
+ }
27
+ """
@@ -0,0 +1,86 @@
1
+ Feature: Working with singular resources (GET, POST, PUT, DELETE)
2
+
3
+ When a single resource exists
4
+ I want to use Webmachine to provide API access to that resource
5
+ So that the resource can easily be worked with
6
+
7
+ Scenario: GET
8
+ Given the following top-level resource at "/item/:item_uid":
9
+ """ruby
10
+ class ItemResource < Webmachine::Resource
11
+ include Eipiai::Resource
12
+
13
+ def object
14
+ { hello: 'world' }
15
+ end
16
+ end
17
+ """
18
+
19
+ When the client provides the header "Accept: application/json"
20
+ And the client does a GET request to the "item" resource with the template variable "item_uid" set to "hello"
21
+ Then the status code should be "200" (OK)
22
+ And the response should be JSON:
23
+ """json
24
+ {
25
+ "hello": "world"
26
+ }
27
+ """
28
+
29
+ Scenario: POST
30
+ Given the following top-level resource at "/item/:item_uid":
31
+ """ruby
32
+ class ItemResource < Webmachine::Resource
33
+ include Eipiai::Resource
34
+
35
+ def object
36
+ OpenStruct.new(hello: 'world')
37
+ end
38
+ end
39
+ """
40
+
41
+ When the client provides the header "Content-Type: application/json"
42
+ And the client does a POST request to the "item" resource with the template variable "item_uid" set to "hello" and the following content:
43
+ """json
44
+ { "hello": "galaxy" }
45
+ """
46
+ Then the status code should be "204" (No Content)
47
+ And the response should contain the header "Location" with value "https://example.org/item/hello"
48
+
49
+ Scenario: PUT
50
+ Given the following top-level resource at "/item/:item_uid":
51
+ """ruby
52
+ class ItemResource < Webmachine::Resource
53
+ include Eipiai::Resource
54
+
55
+ def object
56
+ OpenStruct.new(hello: 'world')
57
+ end
58
+
59
+ def new_object
60
+ OpenStruct.new(params)
61
+ end
62
+ end
63
+ """
64
+
65
+ When the client provides the header "Content-Type: application/json"
66
+ And the client does a PUT request to the "item" resource with the template variable "item_uid" set to "hello" and the following content:
67
+ """json
68
+ { "hello": "galaxy" }
69
+ """
70
+ Then the status code should be "204" (No Content)
71
+ And the response should contain the header "Location" with value "https://example.org/item/hello"
72
+
73
+ Scenario: DELETE
74
+ Given the following top-level resource at "/item/:item_uid":
75
+ """ruby
76
+ class ItemResource < Webmachine::Resource
77
+ include Eipiai::Resource
78
+
79
+ def object
80
+ OpenStruct.new(hello: 'world')
81
+ end
82
+ end
83
+ """
84
+
85
+ When the client does a DELETE request to the "item" resource with the template variable "item_uid" set to "hello"
86
+ Then the status code should be "204" (No Content)
@@ -0,0 +1,69 @@
1
+ Feature: Working with singular resources (GET, POST, PUT, DELETE)
2
+
3
+ When a single resource exists
4
+ I want to use Webmachine to provide API access to that resource
5
+ So that the resource can easily be worked with
6
+
7
+ Scenario: Deleting an existing resource returns "204 No Content"
8
+ Given the following top-level resource at "/item/:item_uid":
9
+ """ruby
10
+ class ItemResource < Webmachine::Resource
11
+ include Eipiai::Resource
12
+
13
+ def object
14
+ OpenStruct.new(hello: 'world')
15
+ end
16
+ end
17
+ """
18
+
19
+ When the client does a DELETE request to the "item" resource with the template variable "item_uid" set to "hello"
20
+ Then the status code should be "204" (No Content)
21
+
22
+ Scenario: Failing to delete a resource returns "500 Internal Server Error"
23
+ Given the following top-level resource at "/item/:item_uid":
24
+ """ruby
25
+ class ItemResource < Webmachine::Resource
26
+ include Eipiai::Resource
27
+
28
+ def delete_resource
29
+ false
30
+ end
31
+
32
+ def object
33
+ OpenStruct.new(hello: 'world')
34
+ end
35
+ end
36
+ """
37
+
38
+ When the client does a DELETE request to the "item" resource with the template variable "item_uid" set to "hello"
39
+ Then the status code should be "500" (Internal Server Error)
40
+
41
+ Scenario: Asynchronously deleting resource returns "202 Accepted"
42
+ Given the following top-level resource at "/item/:item_uid":
43
+ """ruby
44
+ class ItemResource < Webmachine::Resource
45
+ include Eipiai::Resource
46
+
47
+ def delete_completed?
48
+ false
49
+ end
50
+
51
+ def object
52
+ OpenStruct.new(hello: 'world')
53
+ end
54
+ end
55
+ """
56
+
57
+ When the client does a DELETE request to the "item" resource with the template variable "item_uid" set to "hello"
58
+ Then the status code should be "202" (Accepted)
59
+
60
+ Scenario: Deleting a non-existing resource returns "404 Not Found"
61
+ Given the following top-level resource at "/item/:item_uid":
62
+ """ruby
63
+ class ItemResource < Webmachine::Resource
64
+ include Eipiai::Resource
65
+ end
66
+ """
67
+
68
+ When the client does a DELETE request to the "item" resource with the template variable "item_uid" set to "hello"
69
+ Then the status code should be "404" (Not Found)
@@ -0,0 +1,146 @@
1
+ Feature: Working with singular resources (GET, POST, PUT, DELETE)
2
+
3
+ When a single resource exists
4
+ I want to use Webmachine to provide API access to that resource
5
+ So that the resource can easily be worked with
6
+
7
+ Scenario: Accept header "application/json" returns "200 OK" and a body of type "application/json"
8
+ Given the following top-level resource at "/item/:item_uid":
9
+ """ruby
10
+ class ItemResource < Webmachine::Resource
11
+ include Eipiai::Resource
12
+
13
+ def object
14
+ OpenStruct.new(hello: 'world')
15
+ end
16
+ end
17
+ """
18
+
19
+ When the client provides the header "Accept: application/json"
20
+ And the client does a GET request to the "item" resource with the template variable "item_uid" set to "hello"
21
+ Then the status code should be "200" (OK)
22
+ And the response should be JSON:
23
+ """json
24
+ {
25
+ "hello": "world"
26
+ }
27
+ """
28
+
29
+ Scenario: Accept header "application/hal+json" returns "200 OK" and a body of type "application/hal+json"
30
+ Given the following top-level resource at "/item/:item_uid":
31
+ """ruby
32
+ class ItemResource < Webmachine::Resource
33
+ include Eipiai::Resource
34
+
35
+ def to_hal_json
36
+ to_h.merge(_links: { self: { href: create_uri } }).to_json
37
+ end
38
+
39
+ def object
40
+ OpenStruct.new(hello: 'world')
41
+ end
42
+ end
43
+ """
44
+
45
+ When the client provides the header "Accept: application/hal+json"
46
+ And the client does a GET request to the "item" resource with the template variable "item_uid" set to "hello"
47
+ Then the status code should be "200" (OK)
48
+ And the response should be HAL/JSON:
49
+ """json
50
+ {
51
+ "_links": {
52
+ "self": {
53
+ "href": "https://example.org/item/hello"
54
+ }
55
+ },
56
+ "hello": "world"
57
+ }
58
+ """
59
+
60
+ Scenario: Accept header "text/xml" returns "200 OK" and a body of type "text/html"
61
+ Given the following top-level resource at "/item/:item_uid":
62
+ """ruby
63
+ class ItemResource < Webmachine::Resource
64
+ require 'active_support/core_ext/hash/conversions'
65
+
66
+ include Eipiai::Resource
67
+
68
+ def content_types_provided
69
+ [['text/xml', :to_xml]]
70
+ end
71
+
72
+ def to_xml
73
+ to_h.to_xml(root: 'item')
74
+ end
75
+
76
+ def object
77
+ OpenStruct.new(hello: 'world')
78
+ end
79
+ end
80
+ """
81
+
82
+ When the client provides the header "Accept: text/xml"
83
+ And the client does a GET request to the "item" resource with the template variable "item_uid" set to "hello"
84
+ Then the status code should be "200" (OK)
85
+ And the response should be XML:
86
+ """xml
87
+ <?xml version="1.0" encoding="UTF-8"?>
88
+ <item>
89
+ <hello>world</hello>
90
+ </item>
91
+ """
92
+
93
+ Scenario: Not providing the Accept header returns "200 OK" and uses the first in the list of acceptable response content types
94
+ Given the following top-level resource at "/item/:item_uid":
95
+ """ruby
96
+ class ItemResource < Webmachine::Resource
97
+ include Eipiai::Resource
98
+
99
+ def to_hal_json
100
+ to_h.merge(_links: { self: { href: create_uri } }).to_json
101
+ end
102
+
103
+ def object
104
+ OpenStruct.new(hello: 'world')
105
+ end
106
+ end
107
+ """
108
+
109
+ When the client does a GET request to the "item" resource with the template variable "item_uid" set to "hello"
110
+ Then the status code should be "200" (OK)
111
+ And the response should be HAL/JSON:
112
+ """json
113
+ {
114
+ "_links": {
115
+ "self": {
116
+ "href": "https://example.org/item/hello"
117
+ }
118
+ },
119
+ "hello": "world"
120
+ }
121
+ """
122
+
123
+ Scenario: An unknown Accept header returns "406 Not Acceptable"
124
+ Given the following top-level resource at "/item/:item_uid":
125
+ """ruby
126
+ class ItemResource < Webmachine::Resource
127
+ include Eipiai::Resource
128
+ end
129
+ """
130
+
131
+ When the client provides the header "Accept: application/unknown"
132
+ And the client provides the header "Content-Type: application/json"
133
+ And the client does a GET request to the "item" resource with the template variable "item_uid" set to "hello"
134
+ Then the status code should be "406" (Not Acceptable)
135
+
136
+ Scenario: A resource that can't be found returns "404 Not Found"
137
+ Given the following top-level resource at "/item/:item_uid":
138
+ """ruby
139
+ class ItemResource < Webmachine::Resource
140
+ include Eipiai::Resource
141
+ end
142
+ """
143
+
144
+ When the client provides the header "Accept: application/json"
145
+ And the client does a GET request to the "item" resource with the template variable "item_uid" set to "hello"
146
+ Then the status code should be "404" (Not Found)
@@ -0,0 +1,235 @@
1
+ Feature: Working with singular resources (GET, POST, PUT, DELETE)
2
+
3
+ When a single resource exists
4
+ I want to use Webmachine to provide API access to that resource
5
+ So that the resource can easily be worked with
6
+
7
+ Scenario: Accept header "application/json" returns "200 OK" and a body of type "application/json"
8
+ Given the following top-level resource at "/item/:item_uid":
9
+ """ruby
10
+ class ItemResource < Webmachine::Resource
11
+ include Eipiai::Resource
12
+
13
+ def from_json
14
+ object.marshal_load(params.symbolize_keys)
15
+ super
16
+ end
17
+
18
+ def object
19
+ @object ||= OpenStruct.new(hello: 'world')
20
+ end
21
+ end
22
+ """
23
+
24
+ When the client provides the header "Accept: application/json"
25
+ And the client provides the header "Content-Type: application/json"
26
+ And the client does a POST request to the "item" resource with the template variable "item_uid" set to "hello" and the following content:
27
+ """json
28
+ {
29
+ "hello": "galaxy"
30
+ }
31
+ """
32
+ Then the status code should be "200" (OK)
33
+ And the response should be JSON:
34
+ """json
35
+ {
36
+ "hello": "galaxy"
37
+ }
38
+ """
39
+
40
+ Scenario: Accept header "application/hal+json" returns "200 OK" and a body of type "application/hal+json"
41
+ Given the following top-level resource at "/item/:item_uid":
42
+ """ruby
43
+ class ItemResource < Webmachine::Resource
44
+ include Eipiai::Resource
45
+
46
+ def to_hal_json
47
+ to_h.merge(_links: { self: { href: create_uri } }).to_json
48
+ end
49
+
50
+ def from_json
51
+ object.marshal_load(params.symbolize_keys)
52
+ super
53
+ end
54
+
55
+ def object
56
+ @object ||= OpenStruct.new(hello: 'world')
57
+ end
58
+ end
59
+ """
60
+
61
+ When the client provides the header "Accept: application/hal+json"
62
+ And the client provides the header "Content-Type: application/json"
63
+ And the client does a POST request to the "item" resource with the template variable "item_uid" set to "hello" and the following content:
64
+ """json
65
+ {
66
+ "hello": "galaxy"
67
+ }
68
+ """
69
+ Then the status code should be "200" (OK)
70
+ And the response should be HAL/JSON:
71
+ """json
72
+ {
73
+ "_links": {
74
+ "self": {
75
+ "href": "https://example.org/item/hello"
76
+ }
77
+ },
78
+ "hello": "galaxy"
79
+ }
80
+ """
81
+
82
+ Scenario: Accept header "text/xml" returns "200 OK" and a body of type "text/html"
83
+ Given the following top-level resource at "/item/:item_uid":
84
+ """ruby
85
+ class ItemResource < Webmachine::Resource
86
+ require 'active_support/core_ext/hash/conversions'
87
+
88
+ include Eipiai::Resource
89
+
90
+ def content_types_provided
91
+ [['text/xml', :to_xml]]
92
+ end
93
+
94
+ def to_xml
95
+ to_h.to_xml(root: 'item')
96
+ end
97
+
98
+ def from_json
99
+ object.marshal_load(params.symbolize_keys)
100
+ super
101
+ end
102
+
103
+ def object
104
+ @object ||= OpenStruct.new(hello: 'world')
105
+ end
106
+ end
107
+ """
108
+
109
+ When the client provides the header "Accept: text/xml"
110
+ And the client provides the header "Content-Type: application/json"
111
+ And the client does a POST request to the "item" resource with the template variable "item_uid" set to "hello" and the following content:
112
+ """json
113
+ {
114
+ "hello": "galaxy"
115
+ }
116
+ """
117
+ Then the status code should be "200" (OK)
118
+ And the response should be XML:
119
+ """xml
120
+ <?xml version="1.0" encoding="UTF-8"?>
121
+ <item>
122
+ <hello>galaxy</hello>
123
+ </item>
124
+ """
125
+
126
+ Scenario: Not providing the Accept header returns "204 No Content"
127
+ Given the following top-level resource at "/item/:item_uid":
128
+ """ruby
129
+ class ItemResource < Webmachine::Resource
130
+ include Eipiai::Resource
131
+
132
+ def object
133
+ OpenStruct.new(hello: 'world')
134
+ end
135
+ end
136
+ """
137
+
138
+ When the client provides the header "Content-Type: application/json"
139
+ And the client does a POST request to the "item" resource with the template variable "item_uid" set to "hello" and the following content:
140
+ """json
141
+ {
142
+ "hello": "galaxy"
143
+ }
144
+ """
145
+ Then the status code should be "204" (No Content)
146
+ And the response should contain the header "Location" with value "https://example.org/item/hello"
147
+
148
+ Scenario: An unknown Accept header returns "406 Not Acceptable"
149
+ Given the following top-level resource at "/item/:item_uid":
150
+ """ruby
151
+ class ItemResource < Webmachine::Resource
152
+ include Eipiai::Resource
153
+ end
154
+ """
155
+
156
+ When the client provides the header "Accept: application/unknown"
157
+ And the client provides the header "Content-Type: application/json"
158
+ And the client does a POST request to the "item" resource with the template variable "item_uid" set to "hello" and the following content:
159
+ """json
160
+ {
161
+ "hello": "galaxy"
162
+ }
163
+ """
164
+ Then the status code should be "406" (Not Acceptable)
165
+
166
+ Scenario: Not providing the Content-Type header returns "415 Unsupported Media Type"
167
+ Given the following top-level resource at "/item/:item_uid":
168
+ """ruby
169
+ class ItemResource < Webmachine::Resource
170
+ include Eipiai::Resource
171
+
172
+ def object
173
+ OpenStruct.new(hello: 'world')
174
+ end
175
+ end
176
+ """
177
+
178
+ When the client provides the header "Accept: application/json"
179
+ And the client does a POST request to the "item" resource with the template variable "item_uid" set to "hello" and the following content:
180
+ """json
181
+ {
182
+ "hello": "galaxy"
183
+ }
184
+ """
185
+ Then the status code should be "415" (Unsupported Media Type)
186
+
187
+ Scenario: Content-Type as JSON but body is not JSON returns "400 Bad Request"
188
+ Given the following top-level resource at "/item/:item_uid":
189
+ """ruby
190
+ class ItemResource < Webmachine::Resource
191
+ include Eipiai::Resource
192
+ end
193
+ """
194
+
195
+ When the client provides the header "Accept: application/json"
196
+ And the client provides the header "Content-Type: application/json"
197
+ And the client does a POST request to the "item" resource with the template variable "item_uid" set to "hello" and the following content:
198
+ """json
199
+ {
200
+ "hello": INVALID
201
+ }
202
+ """
203
+ Then the status code should be "400" (Bad Request)
204
+ And the response should be JSON:
205
+ """json
206
+ {
207
+ "_errors": [
208
+ {
209
+ "id": "InvalidJson",
210
+ "message": "invalid json"
211
+ }
212
+ ]
213
+ }
214
+ """
215
+
216
+ Scenario: A resource that can't be found returns "404 Not Found"
217
+ Given the following top-level resource at "/item/:item_uid":
218
+ """ruby
219
+ class ItemResource < Webmachine::Resource
220
+ include Eipiai::Resource
221
+ end
222
+ """
223
+
224
+ When the client provides the header "Accept: application/json"
225
+ And the client provides the header "Content-Type: application/json"
226
+ And the client does a POST request to the "item" resource with the template variable "item_uid" set to "hello" and the following content:
227
+ """json
228
+ {
229
+ "hello": "universe"
230
+ }
231
+ """
232
+ Then the status code should be "404" (Not Found)
233
+
234
+ @todo
235
+ Scenario: Providing valid JSON with invalid/unknown attributes returns "422 Unprocessable Entity"