lurch 0.1.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 (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