lurch 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. checksums.yaml +7 -0
  2. data/.coveralls.yml +1 -0
  3. data/.gitignore +4 -0
  4. data/.rspec +2 -0
  5. data/.rubocop.yml +29 -0
  6. data/.travis.yml +7 -0
  7. data/CHANGELOG.md +12 -0
  8. data/Gemfile +2 -0
  9. data/LICENSE.md +9 -0
  10. data/README.md +229 -0
  11. data/Rakefile +12 -0
  12. data/TODO.md +22 -0
  13. data/lib/lurch/changeset.rb +32 -0
  14. data/lib/lurch/client.rb +92 -0
  15. data/lib/lurch/collection.rb +111 -0
  16. data/lib/lurch/configuration.rb +14 -0
  17. data/lib/lurch/error.rb +13 -0
  18. data/lib/lurch/errors/bad_request.rb +6 -0
  19. data/lib/lurch/errors/conflict.rb +6 -0
  20. data/lib/lurch/errors/forbidden.rb +6 -0
  21. data/lib/lurch/errors/json_api_error.rb +29 -0
  22. data/lib/lurch/errors/not_found.rb +6 -0
  23. data/lib/lurch/errors/not_loaded.rb +13 -0
  24. data/lib/lurch/errors/relationship_not_loaded.rb +9 -0
  25. data/lib/lurch/errors/resource_not_loaded.rb +9 -0
  26. data/lib/lurch/errors/server_error.rb +6 -0
  27. data/lib/lurch/errors/unauthorized.rb +6 -0
  28. data/lib/lurch/errors/unprocessable_entity.rb +6 -0
  29. data/lib/lurch/inflector.rb +60 -0
  30. data/lib/lurch/logger.rb +7 -0
  31. data/lib/lurch/middleware/json_api_request.rb +22 -0
  32. data/lib/lurch/middleware/json_api_response.rb +24 -0
  33. data/lib/lurch/paginator.rb +71 -0
  34. data/lib/lurch/payload_builder.rb +43 -0
  35. data/lib/lurch/query.rb +143 -0
  36. data/lib/lurch/query_builder.rb +26 -0
  37. data/lib/lurch/railtie.rb +9 -0
  38. data/lib/lurch/relationship/has_many.rb +17 -0
  39. data/lib/lurch/relationship/has_one.rb +21 -0
  40. data/lib/lurch/relationship/linked.rb +40 -0
  41. data/lib/lurch/relationship.rb +26 -0
  42. data/lib/lurch/resource.rb +82 -0
  43. data/lib/lurch/store.rb +149 -0
  44. data/lib/lurch/store_configuration.rb +27 -0
  45. data/lib/lurch/stored_resource.rb +63 -0
  46. data/lib/lurch/uri_builder.rb +32 -0
  47. data/lib/lurch/version.rb +3 -0
  48. data/lib/lurch.rb +65 -0
  49. data/lurch.gemspec +26 -0
  50. data/lurch.gif +0 -0
  51. data/test/helpers/lurch_test.rb +40 -0
  52. data/test/helpers/response_factory.rb +193 -0
  53. data/test/lurch/test_configuration.rb +20 -0
  54. data/test/lurch/test_create_resources.rb +55 -0
  55. data/test/lurch/test_delete_resources.rb +27 -0
  56. data/test/lurch/test_errors.rb +29 -0
  57. data/test/lurch/test_fetch_relationships.rb +50 -0
  58. data/test/lurch/test_fetch_resources.rb +77 -0
  59. data/test/lurch/test_inflector.rb +13 -0
  60. data/test/lurch/test_paginated_collections.rb +125 -0
  61. data/test/lurch/test_queries.rb +104 -0
  62. data/test/lurch/test_relationship.rb +17 -0
  63. data/test/lurch/test_resource.rb +34 -0
  64. data/test/lurch/test_update_relationships.rb +53 -0
  65. data/test/lurch/test_update_resources.rb +56 -0
  66. data/test/test_helper.rb +15 -0
  67. metadata +235 -0
@@ -0,0 +1,149 @@
1
+ module Lurch
2
+ class Store
3
+ def initialize(url, options = {})
4
+ @config = StoreConfiguration.new(options)
5
+ @client = Client.new(url, @config)
6
+ @store = Hash.new { |hash, key| hash[key] = {} }
7
+ end
8
+
9
+ def from(type)
10
+ query.type(type)
11
+ end
12
+ alias to from
13
+
14
+ def peek(type, id)
15
+ stored_resource = resource_from_store(type, id.to_s)
16
+ return nil if stored_resource.nil?
17
+ Resource.new(self, stored_resource.type, stored_resource.id)
18
+ end
19
+
20
+ def save(changeset, query = {})
21
+ return insert(changeset) if changeset.id.nil?
22
+ url = uri_builder.resource_uri(changeset.type, changeset.id, query)
23
+
24
+ document = @client.patch(url, payload_builder.build(changeset))
25
+ process_document(document)
26
+ rescue Errors::JSONApiError => err
27
+ changeset.errors = err.errors
28
+ raise err
29
+ end
30
+
31
+ def insert(changeset, query = {})
32
+ return save(changeset) unless changeset.id.nil?
33
+ url = uri_builder.resources_uri(changeset.type, query)
34
+
35
+ document = @client.post(url, payload_builder.build(changeset))
36
+ process_document(document)
37
+ rescue Errors::JSONApiError => err
38
+ changeset.errors = err.errors
39
+ raise err
40
+ end
41
+
42
+ def delete(resource, query = {})
43
+ url = uri_builder.resource_uri(resource.type, resource.id, query)
44
+ @client.delete(url)
45
+
46
+ remove(resource)
47
+ true
48
+ end
49
+
50
+ # add resource(s) to a has many relationship
51
+ def add_related(resource, relationship_key, related_resources)
52
+ modify_relationship(:post, resource, relationship_key, related_resources)
53
+ end
54
+
55
+ # remove resource(s) from a has many relationship
56
+ def remove_related(resource, relationship_key, related_resources)
57
+ modify_relationship(:delete, resource, relationship_key, related_resources)
58
+ end
59
+
60
+ # replace resource(s) for a has many or has one relationship
61
+ def update_related(resource, relationship_key, related_resources)
62
+ modify_relationship(:patch, resource, relationship_key, related_resources)
63
+ end
64
+
65
+ # @private
66
+ def load_from_url(url)
67
+ document = @client.get(url)
68
+ process_document(document)
69
+ end
70
+
71
+ # @private
72
+ def resource_from_store(type, id)
73
+ normalized_type = Inflector.decode_type(type)
74
+ @store[normalized_type][id]
75
+ end
76
+
77
+ # @private
78
+ def query
79
+ Query.new(self, inflector)
80
+ end
81
+
82
+ private
83
+
84
+ def inflector
85
+ @inflector ||= Inflector.new(@config.inflection_mode, @config.types_mode)
86
+ end
87
+
88
+ def uri_builder
89
+ @uri_builder ||= URIBuilder.new(inflector)
90
+ end
91
+
92
+ def payload_builder
93
+ @payload_builder ||= PayloadBuilder.new(inflector)
94
+ end
95
+
96
+ def process_document(document)
97
+ stored_resources = store_resources(document)
98
+ resources = stored_resources.map do |stored_resource|
99
+ Resource.new(self, stored_resource.type, stored_resource.id)
100
+ end
101
+
102
+ if document["data"].is_a?(Array)
103
+ paginator = pagination_links?(document) ? Paginator.new(self, document, inflector, @config) : nil
104
+ Collection.new(resources, paginator)
105
+ else
106
+ resources.first
107
+ end
108
+ end
109
+
110
+ def push(stored_resource)
111
+ @store[stored_resource.type][stored_resource.id] = stored_resource
112
+ end
113
+
114
+ def remove(resource)
115
+ @store[resource.type].delete(resource.id)
116
+ end
117
+
118
+ def store_resources(document)
119
+ primary_data = Lurch.to_a(document["data"])
120
+
121
+ primary_stored_resources = primary_data.map do |resource_object|
122
+ push(StoredResource.new(self, resource_object))
123
+ end
124
+
125
+ Lurch.to_a(document["included"]).each do |resource_object|
126
+ push(StoredResource.new(self, resource_object))
127
+ end
128
+
129
+ primary_stored_resources
130
+ end
131
+
132
+ def modify_relationship(method, resource, relationship_key, related_resources)
133
+ url = uri_builder.relationship_uri(resource.type, resource.id, relationship_key)
134
+ payload = payload_builder.build(related_resources, true)
135
+ @client.send(method, url, payload)
136
+ true
137
+ end
138
+
139
+ def pagination_links?(document)
140
+ links = document["links"]
141
+ links && (
142
+ links["first"] ||
143
+ links["last"] ||
144
+ links["next"] ||
145
+ links["prev"]
146
+ )
147
+ end
148
+ end
149
+ end
@@ -0,0 +1,27 @@
1
+ module Lurch
2
+ class StoreConfiguration
3
+ def initialize(options)
4
+ @options = options
5
+ end
6
+
7
+ def authorization
8
+ @options[:authorization]
9
+ end
10
+
11
+ def inflection_mode
12
+ @options[:inflection_mode] || :dasherize
13
+ end
14
+
15
+ def types_mode
16
+ @options[:types_mode] || :pluralize
17
+ end
18
+
19
+ def pagination_record_count_key
20
+ @options[:pagination_record_count_key] || :record_count
21
+ end
22
+
23
+ def pagination_page_count_key
24
+ @options[:pagination_page_count_key] || :page_count
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,63 @@
1
+ module Lurch
2
+ class StoredResource
3
+ def initialize(store, resource_object)
4
+ @store = store
5
+ @resource_object = resource_object
6
+ end
7
+
8
+ def id
9
+ @id ||= @resource_object["id"]
10
+ end
11
+
12
+ def type
13
+ @type ||= Inflector.decode_type(@resource_object["type"])
14
+ end
15
+
16
+ def attributes
17
+ fixed_attributes
18
+ end
19
+
20
+ def relationships
21
+ fixed_relationships
22
+ end
23
+
24
+ def attribute?(name)
25
+ fixed_attributes.key?(name.to_sym)
26
+ end
27
+
28
+ def attribute(name)
29
+ fixed_attributes[name.to_sym]
30
+ end
31
+
32
+ def relationship?(name)
33
+ fixed_relationships.key?(name.to_sym)
34
+ end
35
+
36
+ def relationship(name)
37
+ fixed_relationships[name.to_sym]
38
+ end
39
+
40
+ private
41
+
42
+ def fixed_attributes
43
+ @fixed_attributes ||= resource_attributes.each_with_object({}) do |(key, value), hash|
44
+ hash[Inflector.decode_key(key)] = value
45
+ end
46
+ end
47
+
48
+ def fixed_relationships
49
+ @fixed_relationships ||= resource_relationships.each_with_object({}) do |(key, value), hash|
50
+ relationship_key = Inflector.decode_key(key)
51
+ hash[relationship_key] = Relationship.from_document(@store, relationship_key, value)
52
+ end
53
+ end
54
+
55
+ def resource_attributes
56
+ @resource_object["attributes"] || {}
57
+ end
58
+
59
+ def resource_relationships
60
+ @resource_object["relationships"] || []
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,32 @@
1
+ module Lurch
2
+ class URIBuilder
3
+ def initialize(inflector)
4
+ @inflector = inflector
5
+ end
6
+
7
+ def resources_uri(type, query = "")
8
+ resource = inflector.encode_type(type)
9
+ uri = ::URI.parse(resource)
10
+ uri.query = query unless query.empty?
11
+ uri.to_s
12
+ end
13
+
14
+ def resource_uri(type, id, query = "")
15
+ resource = inflector.encode_type(type)
16
+ uri = ::URI.parse("#{resource}/#{id}")
17
+ uri.query = query unless query.empty?
18
+ uri.to_s
19
+ end
20
+
21
+ def relationship_uri(type, id, relationship_key)
22
+ resource = inflector.encode_type(type)
23
+ relationship = inflector.encode_key(relationship_key)
24
+ uri = ::URI.parse("#{resource}/#{id}/relationships/#{relationship}")
25
+ uri.to_s
26
+ end
27
+
28
+ private
29
+
30
+ attr_reader :inflector
31
+ end
32
+ end
@@ -0,0 +1,3 @@
1
+ module Lurch
2
+ VERSION = "0.1.0".freeze
3
+ end
data/lib/lurch.rb ADDED
@@ -0,0 +1,65 @@
1
+ require "uri"
2
+
3
+ require "inflecto"
4
+ require "faraday"
5
+ require "typhoeus"
6
+ require "typhoeus/adapters/faraday"
7
+
8
+ require "lurch/configuration"
9
+ require "lurch/logger"
10
+ require "lurch/inflector"
11
+
12
+ require "lurch/middleware/json_api_request"
13
+ require "lurch/middleware/json_api_response"
14
+
15
+ require "lurch/error"
16
+ require "lurch/errors/json_api_error"
17
+ require "lurch/errors/bad_request"
18
+ require "lurch/errors/unauthorized"
19
+ require "lurch/errors/forbidden"
20
+ require "lurch/errors/not_found"
21
+ require "lurch/errors/conflict"
22
+ require "lurch/errors/unprocessable_entity"
23
+ require "lurch/errors/server_error"
24
+ require "lurch/errors/not_loaded"
25
+ require "lurch/errors/relationship_not_loaded"
26
+ require "lurch/errors/resource_not_loaded"
27
+
28
+ require "lurch/stored_resource"
29
+ require "lurch/paginator"
30
+ require "lurch/collection"
31
+ require "lurch/relationship"
32
+ require "lurch/relationship/linked"
33
+ require "lurch/relationship/has_one"
34
+ require "lurch/relationship/has_many"
35
+ require "lurch/resource"
36
+
37
+ require "lurch/uri_builder"
38
+ require "lurch/query_builder"
39
+ require "lurch/payload_builder"
40
+ require "lurch/query"
41
+ require "lurch/changeset"
42
+ require "lurch/client"
43
+ require "lurch/store_configuration"
44
+ require "lurch/store"
45
+
46
+ require "lurch/railtie" if defined?(Rails)
47
+
48
+ module Lurch
49
+ def self.to_a(value)
50
+ return [] if value.nil?
51
+ value.is_a?(Array) ? value : [value]
52
+ end
53
+
54
+ def self.configuration
55
+ @configuration ||= Configuration.new
56
+ end
57
+
58
+ def self.reset_configuration
59
+ @configuration = Configuration.new
60
+ end
61
+
62
+ def self.configure
63
+ yield(configuration)
64
+ end
65
+ end
data/lurch.gemspec ADDED
@@ -0,0 +1,26 @@
1
+ require File.expand_path("../lib/lurch/version", __FILE__)
2
+
3
+ Gem::Specification.new do |gem|
4
+ gem.name = "lurch"
5
+ gem.version = Lurch::VERSION
6
+ gem.summary = "A simple Ruby JSON API client"
7
+ gem.description = "A client library for interacting with JSON API servers, based on http://jsonapi.org/ version 1.0."
8
+ gem.homepage = "https://github.com/peek-travel/lurch"
9
+ gem.authors = ["Chris Dosé"]
10
+ gem.email = "chris@peek.com"
11
+ gem.license = "MIT"
12
+
13
+ gem.files = `git ls-files`.split($RS)
14
+ gem.require_paths = ["lib"]
15
+
16
+ gem.add_dependency("faraday", "< 1.0")
17
+ gem.add_dependency("inflecto", "~> 0.0")
18
+ gem.add_dependency("typhoeus", "< 2.0")
19
+
20
+ gem.add_development_dependency("codecov", "~> 0.1")
21
+ gem.add_development_dependency("minitest", "~> 5.9")
22
+ gem.add_development_dependency("pry", "~> 0.10")
23
+ gem.add_development_dependency("rake", "~> 12.2")
24
+ gem.add_development_dependency("rubocop", "~> 0.43")
25
+ gem.add_development_dependency("webmock", "~> 3.1")
26
+ end
data/lurch.gif ADDED
Binary file
@@ -0,0 +1,40 @@
1
+ module LurchTest
2
+ def setup
3
+ # TODO: figure out how to run test suite multiple times with different setup, so we don't randomize like this
4
+ inflection_mode = [:dasherize, :underscore].sample
5
+ types_mode = [:pluralize, :singularize].sample
6
+
7
+ @url = "http://example.com/api"
8
+ @store = Lurch::Store.new(@url, inflection_mode: inflection_mode, types_mode: types_mode)
9
+ @inflector = Lurch::Inflector.new(inflection_mode, types_mode)
10
+ @response_factory = ResponseFactory.new(@inflector, @url)
11
+ end
12
+
13
+ def person_type
14
+ @inflector.encode_type(:person)
15
+ end
16
+
17
+ def phone_number_type
18
+ @inflector.encode_type(:phone_number)
19
+ end
20
+
21
+ def stub_get(path, response)
22
+ stub_req(:get, path, response)
23
+ end
24
+
25
+ def stub_patch(path, response)
26
+ stub_req(:patch, path, response)
27
+ end
28
+
29
+ def stub_post(path, response)
30
+ stub_req(:post, path, response)
31
+ end
32
+
33
+ def stub_delete(path, response)
34
+ stub_req(:delete, path, response)
35
+ end
36
+
37
+ def stub_req(method, path, response)
38
+ stub_request(method, "#{@url}/#{path}").to_return(response)
39
+ end
40
+ end
@@ -0,0 +1,193 @@
1
+ class ResponseFactory
2
+ def initialize(inflector, url)
3
+ @inflector = inflector
4
+ @url = url
5
+ end
6
+
7
+ def no_content_response
8
+ header(:no_content)
9
+ end
10
+
11
+ def unauthorized_response
12
+ header(:unauthorized) + errors_document([401, "Unauthorized"])
13
+ end
14
+
15
+ def forbidden_response
16
+ header(:forbidden) + errors_document([403, "Forbidden"])
17
+ end
18
+
19
+ def not_found_response
20
+ header(:not_found) + errors_document([404, "Not Found"])
21
+ end
22
+
23
+ def unprocessable_entity_response(message)
24
+ header(:unprocessable_entity) + errors_document([422, message])
25
+ end
26
+
27
+ def server_error_response
28
+ header(:server_error) + errors_document([500, "Internal Server Error"])
29
+ end
30
+
31
+ def person_response(id, name, phone_numbers_args = nil, code: :ok, include_phone_numbers: true)
32
+ included = phone_numbers_args && include_phone_numbers ? phone_numbers_args.map { |args| phone_number_data(*args) } : []
33
+ phone_number_ids = phone_numbers_args ? phone_numbers_args.map(&:first) : nil
34
+ header(code) + document(person(id, name, phone_number_ids), included)
35
+ end
36
+
37
+ def people_response(*params, code: :ok)
38
+ header(code) + document(people(*params))
39
+ end
40
+
41
+ def paginated_people_response(per_page, page, pages)
42
+ first = per_page * (page - 1) + 1
43
+ last = first - 1 + per_page
44
+ params = (first..last).map { |i| [i.to_s, "Person#{i}"] }
45
+
46
+ meta = {record_count: pages * per_page, page_count: pages}
47
+
48
+ links = {
49
+ first: "#{@url}/#{@inflector.encode_type(:person)}?page[number]=1&page[size]=#{per_page}",
50
+ last: "#{@url}/#{@inflector.encode_type(:person)}?page[number]=#{pages}&page[size]=#{per_page}"
51
+ }
52
+ links[:next] = "#{@url}/#{@inflector.encode_type(:person)}?page[number]=#{page + 1}&page[size]=#{per_page}" if page < pages
53
+ links[:prev] = "#{@url}/#{@inflector.encode_type(:person)}?page[number]=#{page - 1}&page[size]=#{per_page}" if page > 1
54
+
55
+ header(:ok) + document(people(*params), [], meta, links)
56
+ end
57
+
58
+ def phone_number_response(id, name, number, contact_args = nil, code: :ok)
59
+ included = contact_args ? [person_data(*contact_args)] : []
60
+ contact_id = contact_args ? contact_args.first : nil
61
+ header(code) + document(phone_number(id, name, number, contact_id), included)
62
+ end
63
+
64
+ def phone_numbers_response(*params, code: :ok)
65
+ header(code) + document(phone_numbers(*params))
66
+ end
67
+
68
+ private
69
+
70
+ def person(*args)
71
+ data(person_data(*args))
72
+ end
73
+
74
+ def people(*params)
75
+ data(params.map { |args| person_data(*args) })
76
+ end
77
+
78
+ def person_data(id, name, phone_number_ids = nil)
79
+ {
80
+ id: id,
81
+ type: @inflector.encode_type(:person),
82
+ attributes: {
83
+ name: name,
84
+ email_address: "#{name.downcase}@example.com"
85
+ },
86
+ relationships: {
87
+ phone_numbers: if phone_number_ids
88
+ {
89
+ data: ids(:phone_number, phone_number_ids)
90
+ }
91
+ else
92
+ {
93
+ links: {
94
+ related: "#{@url}/#{@inflector.encode_type(:person)}/#{id}/#{@inflector.encode_key(:phone_numbers)}"
95
+ }
96
+ }
97
+ end
98
+ }
99
+ }
100
+ end
101
+
102
+ def phone_number(*args)
103
+ data(phone_number_data(*args))
104
+ end
105
+
106
+ def phone_numbers(*params)
107
+ data(params.map { |args| phone_number_data(*args) })
108
+ end
109
+
110
+ def phone_number_data(id, name, phone_number, contact_id = nil)
111
+ {
112
+ id: id,
113
+ type: @inflector.encode_type(:phone_number),
114
+ attributes: {
115
+ name: name,
116
+ phone_number: phone_number
117
+ },
118
+ relationships: {
119
+ contact: if contact_id
120
+ {
121
+ data: id(:person, contact_id)
122
+ }
123
+ else
124
+ {
125
+ links: {
126
+ related: "#{@url}/#{@inflector.encode_type(:phone_number)}/#{id}/contact"
127
+ }
128
+ }
129
+ end
130
+ }
131
+ }
132
+ end
133
+
134
+ def id(type, id)
135
+ {type: @inflector.encode_type(type), id: id.to_s}
136
+ end
137
+
138
+ def ids(type, ids)
139
+ ids.map { |i| id(type, i) }
140
+ end
141
+
142
+ def document(doc, included = [], meta = nil, links = nil)
143
+ out = {jsonapi: {version: "1.0"}}
144
+ out[:meta] = encode_value(meta) if meta
145
+ out[:links] = links if links
146
+ out = out.merge(doc)
147
+ out[:included] = included unless included.empty?
148
+
149
+ JSON.dump(out)
150
+ end
151
+
152
+ def errors_document(*args)
153
+ JSON.dump(
154
+ errors: args.map do |(status, detail)|
155
+ {
156
+ status: status,
157
+ detail: detail
158
+ }
159
+ end
160
+ )
161
+ end
162
+
163
+ def data(data)
164
+ {data: encode_value(data)}
165
+ end
166
+
167
+ CODES = {
168
+ ok: "200 OK".freeze,
169
+ created: "201 Created".freeze,
170
+ no_content: "204 No Content".freeze,
171
+ unauthorized: "401 Unauthorized".freeze,
172
+ forbidden: "403 Forbidden".freeze,
173
+ not_found: "404 Not Found".freeze,
174
+ unprocessable_entity: "422 Unprocessable Entity".freeze,
175
+ server_error: "500 Internal Server Error".freeze
176
+ }.freeze
177
+
178
+ def header(code)
179
+ "HTTP/1.1 #{CODES[code]}\nContent-Type: application/vnd.api+json\n\n"
180
+ end
181
+
182
+ def encode_value(value)
183
+ if value.is_a?(Hash)
184
+ value.each_with_object({}) do |(k, v), obj|
185
+ obj[@inflector.encode_key(k)] = encode_value(v)
186
+ end
187
+ elsif value.is_a?(Array)
188
+ value.map { |v| encode_value(v) }
189
+ else
190
+ value
191
+ end
192
+ end
193
+ end
@@ -0,0 +1,20 @@
1
+ require_relative "../test_helper"
2
+
3
+ class TestConfiguration < Minitest::Test
4
+ include LurchTest
5
+
6
+ def test_configuration
7
+ assert_kind_of Logger, Lurch.configuration.logger
8
+ refute Lurch.configuration.log_payloads
9
+
10
+ Lurch.configure do |config|
11
+ config.log_payloads = true
12
+ end
13
+
14
+ assert Lurch.configuration.log_payloads
15
+
16
+ Lurch.reset_configuration
17
+
18
+ refute Lurch.configuration.log_payloads
19
+ end
20
+ end
@@ -0,0 +1,55 @@
1
+ require_relative "../test_helper"
2
+
3
+ class TestCreateResources < Minitest::Test
4
+ include LurchTest
5
+
6
+ def test_post_new_resource_to_server_using_store
7
+ stub_post(person_type, @response_factory.person_response("3", "Carol", code: :created))
8
+
9
+ changeset = Lurch::Changeset.new(:person, name: "Carol")
10
+ person = @store.insert(changeset)
11
+
12
+ assert_requested :post, "#{@url}/#{person_type}", body: "{\"data\":{\"type\":\"#{person_type}\",\"attributes\":{\"name\":\"Carol\"}}}"
13
+ assert_equal "3", person.id
14
+ assert_equal "Carol", person.name
15
+ end
16
+
17
+ def test_post_new_resource_to_server_using_query
18
+ stub_post(person_type, @response_factory.person_response("3", "Carol", code: :created))
19
+
20
+ changeset = Lurch::Changeset.new(:person, name: "Carol")
21
+ person = @store.to(:people).insert(changeset)
22
+
23
+ assert_requested :post, "#{@url}/#{person_type}", body: "{\"data\":{\"type\":\"#{person_type}\",\"attributes\":{\"name\":\"Carol\"}}}"
24
+ assert_equal "3", person.id
25
+ assert_equal "Carol", person.name
26
+ end
27
+
28
+ def test_post_new_resource_to_server_with_relationship_to_existing_resources
29
+ stub_get("#{phone_number_type}/1", @response_factory.phone_number_response("1", "Cell", "111-222-3344"))
30
+ stub_post(person_type, @response_factory.person_response("3", "Carol", code: :created))
31
+
32
+ phone_number = @store.from(:phone_numbers).find("1")
33
+
34
+ changeset = Lurch::Changeset.new(:person, name: "Carol")
35
+ changeset.set_related(:phone_numbers, [phone_number])
36
+ person = @store.insert(changeset)
37
+
38
+ assert_requested :post, "#{@url}/#{person_type}", body: "{\"data\":{\"type\":\"#{person_type}\",\"attributes\":{\"name\":\"Carol\"},\"relationships\":{\"#{@inflector.encode_key(:phone_numbers)}\":{\"data\":[{\"id\":\"1\",\"type\":\"#{phone_number_type}\"}]}}}}"
39
+ assert_equal "3", person.id
40
+ assert_equal "Carol", person.name
41
+ end
42
+
43
+ def test_validation_errors_when_posting_new_resource
44
+ stub_post(person_type, @response_factory.unprocessable_entity_response("name - can't be blank"))
45
+
46
+ changeset = Lurch::Changeset.new(:person, name: "")
47
+
48
+ err = assert_raises(Lurch::Errors::UnprocessableEntity) { @store.insert(changeset) }
49
+
50
+ assert_requested :post, "#{@url}/#{person_type}", body: "{\"data\":{\"type\":\"#{person_type}\",\"attributes\":{\"name\":\"\"}}}"
51
+ assert_equal "422: name - can't be blank", err.message
52
+ assert_equal 422, changeset.errors.first.status
53
+ assert_equal "name - can't be blank", changeset.errors.first.detail
54
+ end
55
+ end