eipiai 0.6.0 → 0.7.0

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.
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"