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,164 @@
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: Replace an existing record 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
+
17
+ def new_object
18
+ OpenStruct.new(params)
19
+ end
20
+ end
21
+ """
22
+
23
+ When the client provides the header "Content-Type: application/json"
24
+ And the client does a PUT request to the "item" resource with the template variable "item_uid" set to "hello" and the following content:
25
+ """json
26
+ {
27
+ "hello": "galaxy"
28
+ }
29
+ """
30
+ Then the status code should be "204" (No Content)
31
+ And the response should contain the header "Location" with value "https://example.org/item/hello"
32
+
33
+ Scenario: Creating a new record returns "201 Created"
34
+ Given the following top-level resource at "/item/:item_uid":
35
+ """ruby
36
+ class ItemResource < Webmachine::Resource
37
+ include Eipiai::Resource
38
+
39
+ def new_object
40
+ OpenStruct.new(params)
41
+ end
42
+ end
43
+ """
44
+
45
+ When the client provides the header "Content-Type: application/json"
46
+ And the client does a PUT request to the "item" resource with the template variable "item_uid" set to "hello" and the following content:
47
+ """json
48
+ {
49
+ "hello": "galaxy"
50
+ }
51
+ """
52
+ Then the status code should be "201" (Created)
53
+ And the response should contain the header "Location" with value "https://example.org/item/hello"
54
+
55
+ Scenario: Replace an existing record while providing Accept header "application/json" returns "200 OK"
56
+ Given the following top-level resource at "/item/:item_uid":
57
+ """ruby
58
+ class ItemResource < Webmachine::Resource
59
+ include Eipiai::Resource
60
+
61
+ def to_hal_json
62
+ to_h.merge(_links: { self: { href: create_uri } }).to_json
63
+ end
64
+
65
+ def object
66
+ @object ||= OpenStruct.new(hello: 'world', foo: 'bar')
67
+ end
68
+
69
+ def new_object
70
+ @object = OpenStruct.new(params)
71
+ end
72
+ end
73
+ """
74
+
75
+ When the client provides the header "Accept: application/hal+json"
76
+ And the client provides the header "Content-Type: application/json"
77
+ And the client does a PUT request to the "item" resource with the template variable "item_uid" set to "hello" and the following content:
78
+ """json
79
+ {
80
+ "hello": "galaxy",
81
+ "foo": "bar"
82
+ }
83
+ """
84
+ Then the status code should be "200" (OK)
85
+ And the response should be HAL/JSON:
86
+ """json
87
+ {
88
+ "_links": {
89
+ "self": {
90
+ "href": "https://example.org/item/hello"
91
+ }
92
+ },
93
+ "hello": "galaxy",
94
+ "foo": "bar"
95
+ }
96
+ """
97
+
98
+ Scenario: Create new object while providing Accept "application/json" returns "200 OK"
99
+ Given the following top-level resource at "/item/:item_uid":
100
+ """ruby
101
+ class ItemResource < Webmachine::Resource
102
+ include Eipiai::Resource
103
+
104
+ def object
105
+ @object
106
+ end
107
+
108
+ def new_object
109
+ @object = OpenStruct.new(params)
110
+ end
111
+ end
112
+ """
113
+
114
+ When the client provides the header "Accept: application/json"
115
+ And the client provides the header "Content-Type: application/json"
116
+ And the client does a PUT request to the "item" resource with the template variable "item_uid" set to "hello" and the following content:
117
+ """json
118
+ {
119
+ "hello": "universe",
120
+ "foo": "bar"
121
+ }
122
+ """
123
+ Then the status code should be "200" (OK)
124
+ And the response should be JSON:
125
+ """json
126
+ {
127
+ "hello": "universe",
128
+ "foo": "bar"
129
+ }
130
+ """
131
+
132
+ Scenario: Replacing existing object while providing Accept "application/json" returns "200 OK"
133
+ Given the following top-level resource at "/item/:item_uid":
134
+ """ruby
135
+ class ItemResource < Webmachine::Resource
136
+ include Eipiai::Resource
137
+
138
+ def object
139
+ @object ||= OpenStruct.new(hello: 'world')
140
+ end
141
+
142
+ def new_object
143
+ @object = OpenStruct.new(params)
144
+ end
145
+ end
146
+ """
147
+
148
+ When the client provides the header "Accept: application/json"
149
+ And the client provides the header "Content-Type: application/json"
150
+ And the client does a PUT request to the "item" resource with the template variable "item_uid" set to "hello" and the following content:
151
+ """json
152
+ {
153
+ "hello": "universe",
154
+ "foo": "bar"
155
+ }
156
+ """
157
+ Then the status code should be "200" (OK)
158
+ And the response should be JSON:
159
+ """json
160
+ {
161
+ "hello": "universe",
162
+ "foo": "bar"
163
+ }
164
+ """
@@ -1,3 +1,13 @@
1
+ # frozen_string_literal: true
1
2
  Given(/^the configuration "([^"]*)" is set to "([^"]*)"$/) do |config, value|
2
3
  Eipiai.configuration.send("#{config}=", value)
3
4
  end
5
+
6
+ Then(/^the response should be XML:$/) do |xml|
7
+ dump last_response.body
8
+
9
+ xml = Liquid::Template.parse(xml).render
10
+
11
+ assert_equal last_response.headers['Content-Type'], 'text/xml'
12
+ expect(last_response.body.strip).to eq(xml)
13
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+ Given(/^the following( top-level)? resource at "([^"]*)":$/) do |top, path, code|
3
+ if top
4
+ code = code.lines.map do |line|
5
+ next line unless line.include?('Webmachine::Resource')
6
+
7
+ line + " def top_level_relation?\n true\n end\n\n"
8
+ end.join
9
+ end
10
+
11
+ add_resource(path, code)
12
+ end
13
+
14
+ def add_resource(path, code)
15
+ resource = code[/class (\S+Resource)/, 1]
16
+
17
+ if Eipiai::Test.const_defined?(resource)
18
+ Eipiai::Test.class_exec { remove_const(resource) }
19
+ end
20
+
21
+ Eipiai::Test.class_eval(code)
22
+ add_route(path, resource)
23
+ end
24
+
25
+ def add_route(path, resource)
26
+ path = path.split('/').drop(1).map! { |r| r[0] == ':' ? r[1..-1].to_sym : r }
27
+
28
+ Eipiai::Test::App.tap do |app|
29
+ app.add_route path, Eipiai::Test.const_get(resource)
30
+ Eipiai::ApiRepresenter.populate_from_app(app)
31
+ end
32
+ end
33
+
34
+ Before do
35
+ # Add the `/api` and `/health` routes
36
+ #
37
+ Eipiai::Test::App.add_route ['api'], Eipiai::ApiResource
38
+ Eipiai::Test::App.add_route ['health'], Eipiai::HealthResource
39
+ end
40
+
41
+ After do
42
+ # Remove any existing resource objects
43
+ #
44
+ Eipiai::Test.class_exec do
45
+ constants.each do |constant|
46
+ remove_const(constant) if constant.to_s.end_with?('Resource')
47
+ end
48
+ end
49
+
50
+ # Remove any existing routes
51
+ #
52
+ Eipiai::Test::App.dispatcher.instance_variable_set(:@routes, [])
53
+
54
+ # Remove links from ApiRepresenter
55
+ #
56
+ Eipiai.class_exec { remove_const(:ApiRepresenter) }
57
+ load 'eipiai/roar/representers/api.rb'
58
+ end
@@ -1,38 +1,26 @@
1
- lib = File.expand_path('../lib', __dir__)
2
- $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
-
1
+ # frozen_string_literal: true
4
2
  require 'eipiai'
3
+ require 'webmachine'
5
4
  require 'webmachine/adapters/rack'
6
- require_relative '../../support/fixtures/app'
7
-
8
- include Eipiai::TestApp
9
-
10
- WebmachineApp = Webmachine::Application.new do |app|
11
- app.routes do
12
- add ['api'], Eipiai::ApiResource
13
- add ['health'], Eipiai::HealthResource
14
5
 
15
- add ['items'], ItemsResource
16
- add ['item', :item_uid], ItemResource
17
- add ['users'], UsersResource
18
- add ['user', :user_uid], UserResource
6
+ module Eipiai
7
+ module Test
8
+ App = Webmachine::Application.new do |app|
9
+ app.configure do |config|
10
+ config.adapter = :Rack
11
+ end
12
+ end
19
13
  end
20
-
21
- app.configure do |config|
22
- config.adapter = :Rack
23
- end
24
-
25
- Eipiai::ApiRepresenter.populate_from_app(app)
26
14
  end
27
15
 
28
16
  def app
29
- WebmachineApp.adapter
17
+ Eipiai::Test::App.adapter
30
18
  end
31
19
 
32
20
  Before do
33
- Eipiai.configuration = Eipiai::Configuration.new
34
- Eipiai.configure do |config|
35
- config.base_uri = 'https://example.org'
36
- config.curie_uris = { b: 'https://api.example.org/rel/{rel}' }
21
+ Eipiai.configuration.tap do |c|
22
+ c.healthy = true
23
+ c.base_uri = 'https://example.org'
24
+ c.curie_uris = { b: 'https://api.example.org/rel/{rel}' }
37
25
  end
38
26
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'cucumber/blendle_steps'
2
3
 
3
4
  Before do
@@ -1,3 +1,4 @@
1
+ @wip
1
2
  Feature: Object validation
2
3
 
3
4
  When accepting external requests to create objects
@@ -1,65 +1,10 @@
1
+ @wip
1
2
  Feature: Webmachine CRUD operations
2
3
 
3
4
  When a new JSON API service needs to be developed
4
5
  I want to use Eipiai's default CRUD setup
5
6
  So that the basic API operations are quick and easy to implement
6
7
 
7
- Scenario: List main API endpoint
8
- When the client does a GET request to "/api"
9
- Then the status code should be "200" (OK)
10
- And the response should be HAL/JSON:
11
- """json
12
- {
13
- "_links": {
14
- "self": {
15
- "href": "https://example.org/api"
16
- },
17
- "api": {
18
- "href": "https://example.org/api"
19
- },
20
- "health": {
21
- "href": "https://example.org/health"
22
- },
23
- "items": {
24
- "href": "https://example.org/items"
25
- },
26
- "item": {
27
- "templated": true,
28
- "href": "https://example.org/item/{item_uid}"
29
- },
30
- "users": {
31
- "href": "https://example.org/users"
32
- },
33
- "user": {
34
- "templated": true,
35
- "href": "https://example.org/user/{user_uid}"
36
- }
37
- }
38
- }
39
- """
40
-
41
- Scenario: Get service health
42
- When the client does a GET request to "/health"
43
- Then the status code should be "200" (OK)
44
- And the response should be JSON:
45
- """json
46
- {
47
- "healthy": true
48
- }
49
- """
50
-
51
- Scenario: Get unhealthy service health
52
- Given the configuration "healthy" is set to "false"
53
- When the client does a GET request to "/health"
54
- Then the status code should be "503" (Service Unavailable)
55
- And the response contains the "Retry-After" header with value "60"
56
- And the response should be JSON:
57
- """json
58
- {
59
- "healthy": false
60
- }
61
- """
62
-
63
8
  Scenario: Create resource
64
9
  Given the client provides the header "Content-Type: application/json"
65
10
  When the client does a POST request to the "items" resource with the following content:
data/lib/eipiai.rb CHANGED
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'eipiai/configuration'
2
3
 
3
4
  require 'eipiai/models'
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  # Eipiai
2
3
  #
3
4
  # The main module for the library.
data/lib/eipiai/models.rb CHANGED
@@ -1,2 +1,3 @@
1
+ # frozen_string_literal: true
1
2
  require 'eipiai/models/collection'
2
3
  require 'eipiai/models/representable'
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Eipiai
2
3
  # Collection
3
4
  #
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Eipiai
2
3
  # Representable
3
4
  #
@@ -21,19 +22,19 @@ module Eipiai
21
22
  "#{self.class.name}Representer".constantize.new(self)
22
23
  end
23
24
 
24
- # to_hash
25
+ # to_h
25
26
  #
26
- # call `#to_hash` on the representer belonging to the object.
27
+ # call `#to_h` on the representer belonging to the object.
27
28
  #
28
29
  # @example
29
30
  # item = Item.new.extend(Eipiai::Representable)
30
- # item.to_hash # => { '_links' => { 'self' => { 'href' => '/item' } } }
31
+ # item.to_h # => { '_links' => { 'self' => { 'href' => '/item' } } }
31
32
  #
32
33
  # @param [Hash] options to be used inside the representer
33
34
  # @return [Hash] hash representation of the object
34
35
  #
35
- def to_hash(options = {})
36
- represented.to_hash(options)
36
+ def to_h(options = {})
37
+ represented.to_h(options)
37
38
  end
38
39
 
39
40
  # from_hash
data/lib/eipiai/roar.rb CHANGED
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'roar'
2
3
 
3
4
  require 'eipiai/roar/ext/hal'
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'roar/json'
2
3
  require 'set'
3
4
 
@@ -50,6 +51,8 @@ module Roar
50
51
  hash['_links']['curies'] = curies.to_a if curies.any?
51
52
  end
52
53
  end
54
+
55
+ alias to_h to_hash
53
56
  end
54
57
 
55
58
  prepend ToHashExt