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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 34136d9503bd1cfed3341367129e8c7ff9ccfb9d
4
- data.tar.gz: 1ec6acd5d189963b9cd0efe1d8649d4c282edca2
3
+ metadata.gz: 6ea1d2843bc6b4a1b1ed2e268f3a287cdb6c591d
4
+ data.tar.gz: 72f7c6c496142c5dc5caed1faf5a5c83b1bb810f
5
5
  SHA512:
6
- metadata.gz: 3adf462831a8f4c9287acbea3bb93d66b31dc8c3e862052d152a7ba091a8611989da4130d35d5f0f2623d115ebefa05d64df378545933e98e552a93b8cb6edfb
7
- data.tar.gz: 3a8ab95d17d35671725ea6edbebbd94f599126ce5f073e3350fc2733bbf3fc53935ea9febd22361d5a53b94d1769c250dd643b6c0acaac68a29f88bda379f72e
6
+ metadata.gz: 88c91bbeb2dc81e6337069fd9053c8c5c262a0a43fd7b70a865c544dedb67061c58648ab2ec14cb93982e6bb1d1fa23df259ce2f5fe97c033fc19fbc04560747
7
+ data.tar.gz: 5a109aac02025614a11feb80a853f12ad980f6c0537e8fef5b77ea3acae12555b09df0d2fc455ab86d70d49298bd147151f4d3bdda479860ab1aa0b0f00c3c8b
data/.gitignore CHANGED
@@ -1,9 +1,6 @@
1
- _builds
2
- _cache
3
- _projects
4
- _steps
5
1
  .bundle
6
2
  .DS_Store
3
+ .wercker
7
4
  .yardoc
8
5
  *.gem
9
6
  doc/
data/.rubocop.yml CHANGED
@@ -1,3 +1,6 @@
1
+ AllCops:
2
+ TargetRubyVersion: 2.3
3
+
1
4
  Metrics/LineLength:
2
5
  Max: 100
3
6
 
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.3.1
data/.wercker.yml CHANGED
@@ -1,9 +1,16 @@
1
- box: ruby
1
+ box: ruby:2.3
2
2
 
3
3
  build:
4
4
  steps:
5
+ - script:
6
+ name: fix Bundler issue
7
+ code: |
8
+ gem update --system 2.6.1
9
+ gem install bundler --version $BUNDLER_VERSION
5
10
  - bundle-install:
11
+ path: vendor/bundle
6
12
  jobs: "8"
13
+ clean: true
7
14
  - script:
8
15
  name: bootstrap
9
16
  code: script/bootstrap
data/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## [v0.6.0](https://github.com/blendle/eipiai/tree/v0.6.0) (2015-12-18)
4
+ [Full Changelog](https://github.com/blendle/eipiai/compare/v0.5.1...v0.6.0)
5
+
6
+ **Merged pull requests:**
7
+
8
+ - introduce representable and collection models [\#10](https://github.com/blendle/eipiai/pull/10) ([JeanMertz](https://github.com/JeanMertz))
9
+
3
10
  ## [v0.5.1](https://github.com/blendle/eipiai/tree/v0.5.1) (2015-12-14)
4
11
  [Full Changelog](https://github.com/blendle/eipiai/compare/v0.5.0...v0.5.1)
5
12
 
data/Gemfile CHANGED
@@ -1,2 +1,3 @@
1
+ # frozen_string_literal: true
1
2
  source 'https://rubygems.org'
2
3
  gemspec
data/Rakefile CHANGED
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'bundler/gem_tasks'
2
3
  require 'cucumber/rake/task'
3
4
  require 'rubocop/rake_task'
data/eipiai.gemspec CHANGED
@@ -1,4 +1,5 @@
1
1
  # coding: utf-8
2
+ # frozen_string_literal: true
2
3
  lib = File.expand_path('lib', __dir__)
3
4
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
5
  require 'eipiai/version'
@@ -0,0 +1,82 @@
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: List main API endpoint
8
+ Given the following resource at "/item/:item_uid":
9
+ """ruby
10
+ class ItemResource < Webmachine::Resource
11
+ include Eipiai::Resource
12
+
13
+ def top_level_relation?
14
+ true
15
+ end
16
+
17
+ def query_keys
18
+ %w(available)
19
+ end
20
+ end
21
+ """
22
+ And the following resource at "/items":
23
+ """ruby
24
+ class ItemsResource < Webmachine::Resource
25
+ include Eipiai::Resource
26
+
27
+ def top_level_relation?
28
+ true
29
+ end
30
+
31
+ def query_keys
32
+ %w(category weight purchased)
33
+ end
34
+ end
35
+ """
36
+ And the following resource at "/user/:user_uid":
37
+ """ruby
38
+ class UserResource < Webmachine::Resource
39
+ include Eipiai::Resource
40
+
41
+ def top_level_relation?
42
+ true
43
+ end
44
+ end
45
+ """
46
+ And the following resource at "/user/:user_uid/orders":
47
+ """ruby
48
+ class UserOrdersResource < Webmachine::Resource
49
+ include Eipiai::Resource
50
+ end
51
+ """
52
+
53
+ When the client does a GET request to "/api"
54
+ Then the status code should be "200" (OK)
55
+ And the response should be HAL/JSON:
56
+ """json
57
+ {
58
+ "_links": {
59
+ "self": {
60
+ "href": "https://example.org/api"
61
+ },
62
+ "api": {
63
+ "href": "https://example.org/api"
64
+ },
65
+ "health": {
66
+ "href": "https://example.org/health"
67
+ },
68
+ "item": {
69
+ "href": "https://example.org/item/{item_uid}{?available}",
70
+ "templated": true
71
+ },
72
+ "items": {
73
+ "href": "https://example.org/items{?category,weight,purchased}",
74
+ "templated": true
75
+ },
76
+ "user": {
77
+ "href": "https://example.org/user/{user_uid}",
78
+ "templated": true
79
+ }
80
+ }
81
+ }
82
+ """
@@ -0,0 +1,60 @@
1
+ Feature: Request a collection of resources
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
8
+ Given the following top-level resource at "/items":
9
+ """ruby
10
+ class ItemsResource < Webmachine::Resource
11
+ include Eipiai::Resource
12
+
13
+ def object
14
+ OpenStruct.new(count: 2, items: [{ hello: 'world' }, { hello: 'universe' }])
15
+ end
16
+ end
17
+ """
18
+ When the client provides the header "Accept: application/json"
19
+ And the client does a GET request to the "items" resource
20
+ Then the status code should be "200" (OK)
21
+ And the response should be JSON:
22
+ """json
23
+ {
24
+ "count": 2,
25
+ "items": [
26
+ {
27
+ "hello": "world"
28
+ },
29
+ {
30
+ "hello": "universe"
31
+ }
32
+ ]
33
+ }
34
+ """
35
+
36
+ Scenario: POST
37
+ Given the following top-level resource at "/items":
38
+ """ruby
39
+ class ItemsResource < Webmachine::Resource
40
+ include Eipiai::Resource
41
+
42
+ def create_path
43
+ '/item/' + params['uid']
44
+ end
45
+
46
+ def new_object
47
+ OpenStruct.new(params)
48
+ end
49
+ end
50
+ """
51
+ When the client provides the header "Content-Type: application/json"
52
+ And the client does a POST request to the "items" resource with the following content:
53
+ """json
54
+ {
55
+ "uid": "hello",
56
+ "hello": "galaxy"
57
+ }
58
+ """
59
+ Then the status code should be "201" (Created)
60
+ And the response should contain the header "Location" with value "https://example.org/item/hello"
@@ -0,0 +1,189 @@
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 "/items":
9
+ """ruby
10
+ class ItemsResource < Webmachine::Resource
11
+ include Eipiai::Resource
12
+
13
+ def object
14
+ OpenStruct.new(count: 2, items: [{ hello: 'world' }, { hello: 'universe' }])
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 "items" resource
21
+ Then the status code should be "200" (OK)
22
+ And the response should be JSON:
23
+ """json
24
+ {
25
+ "count": 2,
26
+ "items": [
27
+ {
28
+ "hello": "world"
29
+ },
30
+ {
31
+ "hello": "universe"
32
+ }
33
+ ]
34
+ }
35
+ """
36
+
37
+ Scenario: Accept header "application/hal+json" returns "200 OK" and a body of type "application/hal+json"
38
+ Given the following top-level resource at "/items":
39
+ """ruby
40
+ class ItemsResource < Webmachine::Resource
41
+ include Eipiai::Resource
42
+
43
+ def to_hal_json
44
+ to_h.merge(_links: { self: { href: request.uri.path } }).to_json
45
+ end
46
+
47
+ def object
48
+ OpenStruct.new(count: 2, items: [{ hello: 'world' }, { hello: 'universe' }])
49
+ end
50
+ end
51
+ """
52
+
53
+ When the client provides the header "Accept: application/hal+json"
54
+ And the client does a GET request to the "items" resource
55
+ Then the status code should be "200" (OK)
56
+ And the response should be HAL/JSON:
57
+ """json
58
+ {
59
+ "_links": {
60
+ "self": {
61
+ "href": "/items"
62
+ }
63
+ },
64
+ "count": 2,
65
+ "items": [
66
+ {
67
+ "hello": "world"
68
+ },
69
+ {
70
+ "hello": "universe"
71
+ }
72
+ ]
73
+ }
74
+ """
75
+
76
+ Scenario: Accept header "text/xml" returns "200 OK" and a body of type "text/html"
77
+ Given the following top-level resource at "/items":
78
+ """ruby
79
+ class ItemsResource < Webmachine::Resource
80
+ require 'active_support/core_ext/hash/conversions'
81
+
82
+ include Eipiai::Resource
83
+
84
+ def content_types_provided
85
+ [['text/xml', :to_xml]]
86
+ end
87
+
88
+ def to_xml
89
+ to_h.to_xml(root: 'items')
90
+ end
91
+
92
+ def object
93
+ OpenStruct.new(count: 2, items: [{ hello: 'world' }, { hello: 'universe' }])
94
+ end
95
+ end
96
+ """
97
+
98
+ When the client provides the header "Accept: text/xml"
99
+ And the client does a GET request to the "items" resource
100
+ Then the status code should be "200" (OK)
101
+ And the response should be XML:
102
+ """xml
103
+ <?xml version="1.0" encoding="UTF-8"?>
104
+ <items>
105
+ <count type="integer">2</count>
106
+ <items type="array">
107
+ <item>
108
+ <hello>world</hello>
109
+ </item>
110
+ <item>
111
+ <hello>universe</hello>
112
+ </item>
113
+ </items>
114
+ </items>
115
+ """
116
+
117
+ Scenario: Not providing the Accept header returns "200 OK" and uses the first in the list of acceptable response content types
118
+ Given the following top-level resource at "/items":
119
+ """ruby
120
+ class ItemsResource < Webmachine::Resource
121
+ include Eipiai::Resource
122
+
123
+ def to_hal_json
124
+ to_h.merge(_links: { self: { href: request.uri.path } }).to_json
125
+ end
126
+
127
+ def object
128
+ OpenStruct.new(count: 2, items: [{ hello: 'world' }, { hello: 'universe' }])
129
+ end
130
+ end
131
+ """
132
+
133
+ When the client does a GET request to the "items" resource
134
+ Then the status code should be "200" (OK)
135
+ And the response should be HAL/JSON:
136
+ """json
137
+ {
138
+ "_links": {
139
+ "self": {
140
+ "href": "/items"
141
+ }
142
+ },
143
+ "count": 2,
144
+ "items": [
145
+ {
146
+ "hello": "world"
147
+ },
148
+ {
149
+ "hello": "universe"
150
+ }
151
+ ]
152
+ }
153
+ """
154
+
155
+ Scenario: An unknown Accept header returns "406 Not Acceptable"
156
+ Given the following top-level resource at "/items":
157
+ """ruby
158
+ class ItemsResource < Webmachine::Resource
159
+ include Eipiai::Resource
160
+ end
161
+ """
162
+
163
+ When the client provides the header "Accept: application/unknown"
164
+ And the client provides the header "Content-Type: application/json"
165
+ And the client does a GET request to the "items" resource
166
+ Then the status code should be "406" (Not Acceptable)
167
+
168
+ Scenario: An empty collection returns "200 OK" with an empty array
169
+ Given the following top-level resource at "/items":
170
+ """ruby
171
+ class ItemsResource < Webmachine::Resource
172
+ include Eipiai::Resource
173
+
174
+ def object
175
+ OpenStruct.new(count: 0, items: [])
176
+ end
177
+ end
178
+ """
179
+
180
+ When the client provides the header "Accept: application/json"
181
+ And the client does a GET request to the "items" resource
182
+ Then the status code should be "200" (OK)
183
+ And the response should be JSON:
184
+ """json
185
+ {
186
+ "count": 0,
187
+ "items": []
188
+ }
189
+ """
@@ -0,0 +1,230 @@
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 "201 Created" and a body of type "application/json"
8
+ Given the following top-level resource at "/items":
9
+ """ruby
10
+ class ItemsResource < Webmachine::Resource
11
+ include Eipiai::Resource
12
+
13
+ def create_path
14
+ '/item/' + params['uid']
15
+ end
16
+
17
+ def new_object
18
+ OpenStruct.new(params)
19
+ end
20
+ end
21
+ """
22
+
23
+ When the client provides the header "Accept: application/json"
24
+ And the client provides the header "Content-Type: application/json"
25
+ And the client does a POST request to the "items" resource with the following content:
26
+ """json
27
+ {
28
+ "uid": "hello",
29
+ "hello": "galaxy"
30
+ }
31
+ """
32
+ Then the status code should be "201" (Created)
33
+ And the response should be JSON:
34
+ """json
35
+ {
36
+ "uid": "hello",
37
+ "hello": "galaxy"
38
+ }
39
+ """
40
+
41
+ Scenario: Accept header "application/hal+json" returns "201 Created" and a body of type "application/hal+json"
42
+ Given the following top-level resource at "/items":
43
+ """ruby
44
+ class ItemsResource < Webmachine::Resource
45
+ include Eipiai::Resource
46
+
47
+ def to_hal_json
48
+ to_h.merge(_links: { self: { href: create_uri } }).to_json
49
+ end
50
+
51
+ def create_path
52
+ '/item/' + params['uid']
53
+ end
54
+
55
+ def new_object
56
+ OpenStruct.new(params)
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 "items" resource with the following content:
64
+ """json
65
+ {
66
+ "uid": "goodbye",
67
+ "hello": "galaxy"
68
+ }
69
+ """
70
+ Then the status code should be "201" (Created)
71
+ And the response should be HAL/JSON:
72
+ """json
73
+ {
74
+ "_links": {
75
+ "self": {
76
+ "href": "https://example.org/item/goodbye"
77
+ }
78
+ },
79
+ "uid": "goodbye",
80
+ "hello": "galaxy"
81
+ }
82
+ """
83
+
84
+ Scenario: Accept header "text/xml" returns "201 Created" and a body of type "text/html"
85
+ Given the following top-level resource at "/items":
86
+ """ruby
87
+ class ItemsResource < Webmachine::Resource
88
+ require 'active_support/core_ext/hash/conversions'
89
+
90
+ include Eipiai::Resource
91
+
92
+ def content_types_provided
93
+ [['text/xml', :to_xml]]
94
+ end
95
+
96
+ def to_xml
97
+ to_h.to_xml(root: 'item')
98
+ end
99
+
100
+ def create_path
101
+ '/item/' + params['uid']
102
+ end
103
+
104
+ def new_object
105
+ OpenStruct.new(params)
106
+ end
107
+ end
108
+ """
109
+
110
+ When the client provides the header "Accept: text/xml"
111
+ And the client provides the header "Content-Type: application/json"
112
+ And the client does a POST request to the "items" resource with the following content:
113
+ """json
114
+ {
115
+ "uid": "hello",
116
+ "hello": "galaxy"
117
+ }
118
+ """
119
+ Then the status code should be "201" (Created)
120
+ And the response should be XML:
121
+ """xml
122
+ <?xml version="1.0" encoding="UTF-8"?>
123
+ <item>
124
+ <uid>hello</uid>
125
+ <hello>galaxy</hello>
126
+ </item>
127
+ """
128
+
129
+ Scenario: Not providing the Accept header returns "201 Created" and a "Location" header
130
+ Given the following top-level resource at "/items":
131
+ """ruby
132
+ class ItemsResource < Webmachine::Resource
133
+ include Eipiai::Resource
134
+
135
+ def create_path
136
+ '/item/' + params['uid']
137
+ end
138
+
139
+ def new_object
140
+ OpenStruct.new(params)
141
+ end
142
+ end
143
+ """
144
+
145
+ When the client provides the header "Content-Type: application/json"
146
+ And the client does a POST request to the "items" resource with the following content:
147
+ """json
148
+ {
149
+ "uid": "hi",
150
+ "hello": "galaxy"
151
+ }
152
+ """
153
+ Then the status code should be "201" (Created)
154
+ And the response should contain the header "Location" with value "https://example.org/item/hi"
155
+
156
+ Scenario: An unknown Accept header returns "406 Not Acceptable"
157
+ Given the following top-level resource at "/items":
158
+ """ruby
159
+ class ItemsResource < Webmachine::Resource
160
+ include Eipiai::Resource
161
+ end
162
+ """
163
+
164
+ When the client provides the header "Accept: application/unknown"
165
+ And the client provides the header "Content-Type: application/json"
166
+ And the client does a POST request to the "items" resource with the following content:
167
+ """json
168
+ {
169
+ "hello": "galaxy"
170
+ }
171
+ """
172
+ Then the status code should be "406" (Not Acceptable)
173
+
174
+ Scenario: Not providing the Content-Type header returns "415 Unsupported Media Type"
175
+ Given the following top-level resource at "/items":
176
+ """ruby
177
+ class ItemsResource < Webmachine::Resource
178
+ include Eipiai::Resource
179
+
180
+ def create_path
181
+ '/item' + params['uid']
182
+ end
183
+
184
+ def new_object
185
+ OpenStruct.new(params)
186
+ end
187
+ end
188
+ """
189
+
190
+ When the client provides the header "Accept: application/json"
191
+ And the client does a POST request to the "items" resource with the following content:
192
+ """json
193
+ {
194
+ "uid": "bye",
195
+ "hello": "galaxy"
196
+ }
197
+ """
198
+ Then the status code should be "415" (Unsupported Media Type)
199
+
200
+ Scenario: Content-Type as JSON but body is not JSON returns "400 Bad Request"
201
+ Given the following top-level resource at "/items":
202
+ """ruby
203
+ class ItemsResource < Webmachine::Resource
204
+ include Eipiai::Resource
205
+ end
206
+ """
207
+
208
+ When the client provides the header "Accept: application/json"
209
+ And the client provides the header "Content-Type: application/json"
210
+ And the client does a POST request to the "items" resource with the following content:
211
+ """json
212
+ {
213
+ "hello": INVALID
214
+ }
215
+ """
216
+ Then the status code should be "400" (Bad Request)
217
+ And the response should be JSON:
218
+ """json
219
+ {
220
+ "_errors": [
221
+ {
222
+ "id": "InvalidJson",
223
+ "message": "invalid json"
224
+ }
225
+ ]
226
+ }
227
+ """
228
+
229
+ @todo
230
+ Scenario: Providing valid JSON with invalid/unknown attributes returns "422 Unprocessable Entity"