apes 1.0.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.
- checksums.yaml +7 -0
- data/.gitignore +7 -0
- data/.rubocop.yml +82 -0
- data/.travis-gemfile +15 -0
- data/.travis.yml +15 -0
- data/.yardopts +1 -0
- data/CHANGELOG.md +3 -0
- data/Gemfile +22 -0
- data/README.md +177 -0
- data/Rakefile +44 -0
- data/apes.gemspec +34 -0
- data/doc/Apes.html +130 -0
- data/doc/Apes/Concerns.html +127 -0
- data/doc/Apes/Concerns/Errors.html +1089 -0
- data/doc/Apes/Concerns/Pagination.html +636 -0
- data/doc/Apes/Concerns/Request.html +766 -0
- data/doc/Apes/Concerns/Response.html +940 -0
- data/doc/Apes/Controller.html +1100 -0
- data/doc/Apes/Errors.html +125 -0
- data/doc/Apes/Errors/AuthenticationError.html +133 -0
- data/doc/Apes/Errors/BadRequestError.html +157 -0
- data/doc/Apes/Errors/BaseError.html +320 -0
- data/doc/Apes/Errors/InvalidDataError.html +157 -0
- data/doc/Apes/Errors/MissingDataError.html +157 -0
- data/doc/Apes/Model.html +378 -0
- data/doc/Apes/PaginationCursor.html +2138 -0
- data/doc/Apes/RuntimeConfiguration.html +909 -0
- data/doc/Apes/Serializers.html +125 -0
- data/doc/Apes/Serializers/JSON.html +389 -0
- data/doc/Apes/Serializers/JWT.html +452 -0
- data/doc/Apes/Serializers/List.html +347 -0
- data/doc/Apes/UrlsParser.html +1432 -0
- data/doc/Apes/Validators.html +125 -0
- data/doc/Apes/Validators/BaseValidator.html +278 -0
- data/doc/Apes/Validators/BooleanValidator.html +494 -0
- data/doc/Apes/Validators/EmailValidator.html +350 -0
- data/doc/Apes/Validators/PhoneValidator.html +375 -0
- data/doc/Apes/Validators/ReferenceValidator.html +372 -0
- data/doc/Apes/Validators/TimestampValidator.html +640 -0
- data/doc/Apes/Validators/UuidValidator.html +372 -0
- data/doc/Apes/Validators/ZipCodeValidator.html +372 -0
- data/doc/Apes/Version.html +189 -0
- data/doc/ApplicationController.html +547 -0
- data/doc/Concerns.html +128 -0
- data/doc/Concerns/ErrorHandling.html +826 -0
- data/doc/Concerns/PaginationHandling.html +463 -0
- data/doc/Concerns/RequestHandling.html +512 -0
- data/doc/Concerns/ResponseHandling.html +579 -0
- data/doc/Errors.html +126 -0
- data/doc/Errors/AuthenticationError.html +123 -0
- data/doc/Errors/BadRequestError.html +147 -0
- data/doc/Errors/BaseError.html +289 -0
- data/doc/Errors/InvalidDataError.html +147 -0
- data/doc/Errors/MissingDataError.html +147 -0
- data/doc/Model.html +315 -0
- data/doc/PaginationCursor.html +764 -0
- data/doc/Serializers.html +126 -0
- data/doc/Serializers/JSON.html +253 -0
- data/doc/Serializers/JWT.html +253 -0
- data/doc/Serializers/List.html +245 -0
- data/doc/Validators.html +126 -0
- data/doc/Validators/BaseValidator.html +209 -0
- data/doc/Validators/BooleanValidator.html +391 -0
- data/doc/Validators/EmailValidator.html +298 -0
- data/doc/Validators/PhoneValidator.html +313 -0
- data/doc/Validators/ReferenceValidator.html +284 -0
- data/doc/Validators/TimestampValidator.html +476 -0
- data/doc/Validators/UuidValidator.html +310 -0
- data/doc/Validators/ZipCodeValidator.html +310 -0
- data/doc/_index.html +435 -0
- data/doc/class_list.html +58 -0
- data/doc/css/common.css +1 -0
- data/doc/css/full_list.css +57 -0
- data/doc/css/style.css +339 -0
- data/doc/file.README.html +252 -0
- data/doc/file_list.html +60 -0
- data/doc/frames.html +26 -0
- data/doc/index.html +252 -0
- data/doc/js/app.js +219 -0
- data/doc/js/full_list.js +181 -0
- data/doc/js/jquery.js +4 -0
- data/doc/method_list.html +615 -0
- data/doc/top-level-namespace.html +112 -0
- data/lib/apes.rb +40 -0
- data/lib/apes/concerns/errors.rb +111 -0
- data/lib/apes/concerns/pagination.rb +81 -0
- data/lib/apes/concerns/request.rb +237 -0
- data/lib/apes/concerns/response.rb +74 -0
- data/lib/apes/controller.rb +77 -0
- data/lib/apes/errors.rb +38 -0
- data/lib/apes/model.rb +94 -0
- data/lib/apes/pagination_cursor.rb +152 -0
- data/lib/apes/runtime_configuration.rb +80 -0
- data/lib/apes/serializers.rb +88 -0
- data/lib/apes/urls_parser.rb +233 -0
- data/lib/apes/validators.rb +234 -0
- data/lib/apes/version.rb +24 -0
- data/spec/apes/concerns/errors_spec.rb +141 -0
- data/spec/apes/concerns/pagination_spec.rb +114 -0
- data/spec/apes/concerns/request_spec.rb +244 -0
- data/spec/apes/concerns/response_spec.rb +79 -0
- data/spec/apes/controller_spec.rb +54 -0
- data/spec/apes/errors_spec.rb +14 -0
- data/spec/apes/models_spec.rb +148 -0
- data/spec/apes/pagination_cursor_spec.rb +113 -0
- data/spec/apes/runtime_configuration_spec.rb +100 -0
- data/spec/apes/serializers_spec.rb +70 -0
- data/spec/apes/urls_parser_spec.rb +150 -0
- data/spec/apes/validators_spec.rb +237 -0
- data/spec/spec_helper.rb +30 -0
- data/views/_included.json.jbuilder +9 -0
- data/views/_pagination.json.jbuilder +9 -0
- data/views/collection.json.jbuilder +4 -0
- data/views/errors/400.json.jbuilder +9 -0
- data/views/errors/403.json.jbuilder +7 -0
- data/views/errors/404.json.jbuilder +6 -0
- data/views/errors/422.json.jbuilder +19 -0
- data/views/errors/500.json.jbuilder +12 -0
- data/views/errors/501.json.jbuilder +7 -0
- data/views/layouts/general.json.jbuilder +36 -0
- data/views/object.json.jbuilder +4 -0
- metadata +262 -0
data/lib/apes/version.rb
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#
|
|
2
|
+
# This file is part of the apes gem. Copyright (C) 2016 and above Shogun <shogun@cowtech.it>.
|
|
3
|
+
# Licensed under the MIT license, which can be found at http://www.opensource.org/licenses/mit-license.php.
|
|
4
|
+
#
|
|
5
|
+
|
|
6
|
+
# A tiny JSON API framework for Ruby on Rails.
|
|
7
|
+
module Apes
|
|
8
|
+
# The current version of apes, according to semantic versioning.
|
|
9
|
+
#
|
|
10
|
+
# @see http://semver.org
|
|
11
|
+
module Version
|
|
12
|
+
# The major version.
|
|
13
|
+
MAJOR = 1
|
|
14
|
+
|
|
15
|
+
# The minor version.
|
|
16
|
+
MINOR = 0
|
|
17
|
+
|
|
18
|
+
# The patch version.
|
|
19
|
+
PATCH = 0
|
|
20
|
+
|
|
21
|
+
# The current version of apes.
|
|
22
|
+
STRING = [MAJOR, MINOR, PATCH].compact.join(".")
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
require "spec_helper"
|
|
2
|
+
|
|
3
|
+
describe Apes::Concerns::Errors do
|
|
4
|
+
class ErrorHandlingMockContainer
|
|
5
|
+
include Apes::Concerns::Errors
|
|
6
|
+
|
|
7
|
+
def request_valid_content_type
|
|
8
|
+
"FOO"
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
class ErrorHandlingMockModel
|
|
13
|
+
include ActiveModel::Validations
|
|
14
|
+
|
|
15
|
+
attr_reader :field, :other_field
|
|
16
|
+
validates :field, presence: true
|
|
17
|
+
validates :other_field, presence: true
|
|
18
|
+
|
|
19
|
+
def self.i18n_scope
|
|
20
|
+
:activerecord
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
subject { ErrorHandlingMockContainer.new }
|
|
25
|
+
|
|
26
|
+
describe "fail_request!" do
|
|
27
|
+
it "should raise the right exception" do
|
|
28
|
+
expect { subject.fail_request!(1, 2) }.to raise_error(Apes::Errors::BaseError)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
describe "error_handle_exception" do
|
|
33
|
+
it "should call the right handler" do
|
|
34
|
+
expect(subject).to receive(:error_handle_bad_request)
|
|
35
|
+
subject.error_handle_exception(::Apes::Errors::BadRequestError.new)
|
|
36
|
+
|
|
37
|
+
expect(subject).to receive(:error_handle_invalid_data)
|
|
38
|
+
subject.error_handle_exception(::Apes::Errors::InvalidDataError.new)
|
|
39
|
+
|
|
40
|
+
expect(subject).to receive(:error_handle_others)
|
|
41
|
+
subject.error_handle_exception(RuntimeError.new)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
describe "error_handle_general" do
|
|
46
|
+
it "should render the right template" do
|
|
47
|
+
expect(subject).to receive(:render_error).with(401, "FOO")
|
|
48
|
+
subject.error_handle_general(::Apes::Errors::BaseError.new({status: 401, error: "FOO"}))
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
describe "error_handle_others" do
|
|
53
|
+
it "should render the right template and store the useful information" do
|
|
54
|
+
allow(Apes::RuntimeConfiguration).to receive(:rails_root).and_return("/abc")
|
|
55
|
+
allow(Apes::RuntimeConfiguration).to receive(:gems_root).and_return("/cde")
|
|
56
|
+
|
|
57
|
+
err = RuntimeError.new
|
|
58
|
+
allow(err).to receive(:backtrace).and_return(["FOO", Apes::RuntimeConfiguration.rails_root + "/foo", Apes::RuntimeConfiguration.gems_root + "/foo"])
|
|
59
|
+
|
|
60
|
+
expect(subject).to receive(:render).with("errors/500", status: :internal_server_error)
|
|
61
|
+
subject.error_handle_others(err)
|
|
62
|
+
expect(subject.instance_variable_get(:@exception)).to be(err)
|
|
63
|
+
expect(subject.instance_variable_get(:@backtrace)).to eq(["FOO", "$RAILS/foo", "$GEMS/foo"])
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
describe "error_handle_debug" do
|
|
68
|
+
it "should render the right template with the right data" do
|
|
69
|
+
err = Lazier::Exceptions::Debug.new({a: 1, b: 2}.to_json)
|
|
70
|
+
expect(subject).to receive(:render).with("errors/400", status: 418, locals: {debug: {"a" => 1, "b" => 2}})
|
|
71
|
+
subject.error_handle_debug(err)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
describe "error_handle_fordidden" do
|
|
76
|
+
it "should render the right template with the right message" do
|
|
77
|
+
expect(subject).to receive(:render).with("errors/403", status: :forbidden).exactly(2)
|
|
78
|
+
|
|
79
|
+
subject.error_handle_fordidden(RuntimeError.new("FOO"))
|
|
80
|
+
expect(subject.instance_variable_get(:@authentication_error)).to eq({error: "FOO"})
|
|
81
|
+
|
|
82
|
+
subject.error_handle_fordidden(RuntimeError.new(""))
|
|
83
|
+
expect(subject.instance_variable_get(:@authentication_error)).to eq({error: "You don't have access to this resource."})
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
describe "error_handle_not_found" do
|
|
88
|
+
it "should render the right template" do
|
|
89
|
+
expect(subject).to receive(:render).with("errors/404", status: :not_found)
|
|
90
|
+
subject.error_handle_not_found
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
describe "error_handle_bad_request" do
|
|
95
|
+
it "should render the right template with the right reason" do
|
|
96
|
+
expect(subject).to receive(:render).with("errors/400", status: :bad_request)
|
|
97
|
+
subject.error_handle_bad_request
|
|
98
|
+
expect(subject.instance_variable_get(:@reason)).to eq("Invalid Content-Type specified. Please use \"FOO\" when performing write operations.")
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
describe "error_handle_missing_data" do
|
|
103
|
+
it "should render the right template with the right reason" do
|
|
104
|
+
expect(subject).to receive(:render).with("errors/400", status: :bad_request)
|
|
105
|
+
subject.error_handle_missing_data
|
|
106
|
+
expect(subject.instance_variable_get(:@reason)).to eq("Missing data.")
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
describe "error_handle_invalid_data" do
|
|
111
|
+
it "should render the right template with the right reason" do
|
|
112
|
+
expect(subject).to receive(:render).with("errors/400", status: :bad_request)
|
|
113
|
+
subject.error_handle_invalid_data
|
|
114
|
+
expect(subject.instance_variable_get(:@reason)).to eq("Invalid data provided.")
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
describe "error_handle_unknown_attribute" do
|
|
119
|
+
it "should render the right template with the right errors" do
|
|
120
|
+
object = ErrorHandlingMockModel.new
|
|
121
|
+
|
|
122
|
+
expect(subject).to receive(:render).with("errors/422", status: :unprocessable_entity).exactly(2)
|
|
123
|
+
subject.error_handle_unknown_attribute(ActionController::UnpermittedParameters.new(["A", "B"]))
|
|
124
|
+
expect(subject.instance_variable_get(:@errors)).to eq(["A", "B"])
|
|
125
|
+
|
|
126
|
+
subject.error_handle_unknown_attribute(ActiveRecord::UnknownAttributeError.new(object, "C"))
|
|
127
|
+
expect(subject.instance_variable_get(:@errors)).to eq("C")
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
describe "error_handle_validation" do
|
|
132
|
+
it "should render the right template with the right errors" do
|
|
133
|
+
object = ErrorHandlingMockModel.new
|
|
134
|
+
object.validate
|
|
135
|
+
|
|
136
|
+
expect(subject).to receive(:render).with("errors/422", status: :unprocessable_entity)
|
|
137
|
+
subject.error_handle_validation(ActiveRecord::RecordInvalid.new(object))
|
|
138
|
+
expect(subject.instance_variable_get(:@errors)).to eq({field: ["can't be blank"], other_field: ["can't be blank"]})
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
require "spec_helper"
|
|
2
|
+
|
|
3
|
+
describe Apes::Concerns::Pagination do
|
|
4
|
+
class PaginationHandlingMockContainer
|
|
5
|
+
include Apes::Concerns::Pagination
|
|
6
|
+
|
|
7
|
+
attr_reader :cursor
|
|
8
|
+
|
|
9
|
+
def initialize(params = {}, field = :page, count_field = :count)
|
|
10
|
+
@cursor = Apes::PaginationCursor.new(params, field, count_field)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def request
|
|
14
|
+
OpenStruct.new(params: {a: 1, b: 2})
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def url_for(params)
|
|
18
|
+
params
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
subject { PaginationHandlingMockContainer.new }
|
|
23
|
+
|
|
24
|
+
describe "#paginate" do
|
|
25
|
+
context "when NOT using the offset" do
|
|
26
|
+
it "should apply the query" do
|
|
27
|
+
collection = []
|
|
28
|
+
cursor = JWT.encode({aud: "pagination", sub: {value: "2001-02-03T04:05:06.789+0700", size: 34, direction: "next"}}, Apes::RuntimeConfiguration.jwt_token, "HS256")
|
|
29
|
+
subject = PaginationHandlingMockContainer.new({page: cursor})
|
|
30
|
+
|
|
31
|
+
expect(collection).to receive(:columns_hash).and_return({"created_at" => OpenStruct.new(type: "datetime")})
|
|
32
|
+
expect(collection).to receive(:where).with("created_at > ?", DateTime.civil(2001, 2, 3, 4, 5, 6.789, "+7")).and_return(collection)
|
|
33
|
+
expect(collection).to receive(:limit).with(34).and_return(collection)
|
|
34
|
+
expect(collection).to receive(:order).with("created_at ASC").and_return(collection)
|
|
35
|
+
|
|
36
|
+
expect(subject.paginate(collection, sort_field: :created_at, sort_order: :asc)).to eq(collection)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
context "when using the offset" do
|
|
41
|
+
it "should apply the query" do
|
|
42
|
+
collection = []
|
|
43
|
+
cursor = JWT.encode({aud: "pagination", sub: {value: 12, size: 56, use_offset: true, direction: "next"}}, Apes::RuntimeConfiguration.jwt_token, "HS256")
|
|
44
|
+
subject = PaginationHandlingMockContainer.new({page: cursor})
|
|
45
|
+
|
|
46
|
+
expect(collection).to receive(:offset).with(12).and_return(collection)
|
|
47
|
+
expect(collection).to receive(:limit).with(56).and_return(collection)
|
|
48
|
+
expect(collection).to receive(:order).with("id ASC").and_return(collection)
|
|
49
|
+
|
|
50
|
+
expect(subject.paginate(collection, sort_field: :id, sort_order: :asc)).to eq(collection)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
context "when NOT going next" do
|
|
55
|
+
it "should reverse results" do
|
|
56
|
+
collection = []
|
|
57
|
+
cursor = JWT.encode({aud: "pagination", sub: {value: 12, size: 56, use_offset: true, direction: "prev"}}, Apes::RuntimeConfiguration.jwt_token, "HS256")
|
|
58
|
+
subject = PaginationHandlingMockContainer.new({page: cursor})
|
|
59
|
+
|
|
60
|
+
expect(collection).to receive(:offset).and_return(collection)
|
|
61
|
+
expect(collection).to receive(:limit).and_return(collection)
|
|
62
|
+
expect(collection).to receive(:order).and_return(collection)
|
|
63
|
+
expect(collection).to receive(:reverse_order).and_return(collection)
|
|
64
|
+
expect(collection).to receive(:reverse).and_return(collection)
|
|
65
|
+
expect(subject.paginate(collection, sort_field: :id, sort_order: :asc)).to eq(collection)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
describe "#pagination_field" do
|
|
71
|
+
it "should return the right field" do
|
|
72
|
+
expect(subject.pagination_field).to eq(:handle)
|
|
73
|
+
|
|
74
|
+
subject.instance_variable_set(:@pagination_field, :foo)
|
|
75
|
+
expect(subject.pagination_field).to eq(:foo)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
describe "#pagination_skip?" do
|
|
80
|
+
it "should return the right value" do
|
|
81
|
+
expect(subject.pagination_skip?).to be_nil
|
|
82
|
+
|
|
83
|
+
subject.instance_variable_set(:@skip_pagination, "FOO")
|
|
84
|
+
expect(subject.pagination_skip?).to eq("FOO")
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
describe "#pagination_supported?" do
|
|
89
|
+
it "check whether pagination is supported" do
|
|
90
|
+
expect(subject.pagination_supported?).to be_falsey
|
|
91
|
+
|
|
92
|
+
subject.instance_variable_set(:@objects, OpenStruct.new(first: true))
|
|
93
|
+
expect(subject.pagination_supported?).to be_falsey
|
|
94
|
+
|
|
95
|
+
subject.instance_variable_set(:@objects, [])
|
|
96
|
+
expect(subject.pagination_supported?).to be_truthy
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
describe "#pagination_url" do
|
|
101
|
+
it "should return null when not supported" do
|
|
102
|
+
expect(subject.pagination_url("next")).to be_nil
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
it "should return the right URL when supported" do
|
|
106
|
+
subject.instance_variable_set(:@objects, ["FOO"])
|
|
107
|
+
|
|
108
|
+
allow(subject.cursor).to receive(:might_exist?).with("prev", ["FOO"]).and_return(true)
|
|
109
|
+
allow(subject.cursor).to receive(:save).with(["FOO"], "prev", field: :handle).and_return("URL")
|
|
110
|
+
|
|
111
|
+
expect(subject.pagination_url("prev")).to eq({a: 1, b: 2, only_path: false, page: "URL"})
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
require "spec_helper"
|
|
2
|
+
|
|
3
|
+
describe Apes::Concerns::Request do
|
|
4
|
+
class RequestHandlingMockContainer
|
|
5
|
+
include Apes::Concerns::Request
|
|
6
|
+
|
|
7
|
+
attr_reader :request, :response, :params, :headers
|
|
8
|
+
|
|
9
|
+
def initialize(request = nil, params = nil)
|
|
10
|
+
@request = OpenStruct.new(request || {format: nil, url: "", body: "FOO"})
|
|
11
|
+
@response ||= OpenStruct.new({content_type: :html})
|
|
12
|
+
@params = params || HashWithIndifferentAccess.new
|
|
13
|
+
@headers = HashWithIndifferentAccess.new
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def fail_request!(status, error)
|
|
17
|
+
raise(Apes::Errors::BaseError, {status: status, error: error})
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
FirstMockModel = Struct.new(:id)
|
|
22
|
+
|
|
23
|
+
class SecondMockModel
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
class MockModel
|
|
27
|
+
include ActiveModel::Model
|
|
28
|
+
include Apes::Model
|
|
29
|
+
|
|
30
|
+
ATTRIBUTES = [:id, :other, :created_at, :first_mock_model]
|
|
31
|
+
RELATIONSHIPS = {first_mock_model: nil, second: SecondMockModel}
|
|
32
|
+
|
|
33
|
+
def self.column_types
|
|
34
|
+
{"id" => OpenStruct.new(type: :string), "other" => OpenStruct.new(type: :boolean), "created_at" => OpenStruct.new(type: :datetime)}
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
before(:each) do
|
|
39
|
+
allow(Apes::RuntimeConfiguration).to receive(:timestamp_formats).and_return({full: "%FT%T.%L%z"})
|
|
40
|
+
allow(Apes::RuntimeConfiguration).to receive(:development?).and_return(true)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
subject { RequestHandlingMockContainer.new }
|
|
44
|
+
|
|
45
|
+
describe "#request_handle_cors" do
|
|
46
|
+
it "should set the right headers" do
|
|
47
|
+
allow(subject).to receive(:request_source_host).and_return("FOO")
|
|
48
|
+
|
|
49
|
+
subject.request_handle_cors
|
|
50
|
+
expect(subject.headers).to eq({
|
|
51
|
+
"Access-Control-Allow-Headers" => "Content-Type, X-User-Email, X-User-Token",
|
|
52
|
+
"Access-Control-Allow-Methods" => "POST, GET, PUT, DELETE, OPTIONS",
|
|
53
|
+
"Access-Control-Allow-Origin" => "http://FOO:4200",
|
|
54
|
+
"Access-Control-Max-Age" => "31557600"
|
|
55
|
+
})
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
describe "#request_validate" do
|
|
60
|
+
it "should override the request format and the response content type, then prepare the data" do
|
|
61
|
+
subject.request_validate
|
|
62
|
+
|
|
63
|
+
expect(subject.request.format).to eq(:json)
|
|
64
|
+
expect(subject.response.content_type).to eq("application/vnd.api+json")
|
|
65
|
+
expect(subject.params[:data]).to be_a(HashWithIndifferentAccess)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
it "should complain when the provided content_type is wrong" do
|
|
69
|
+
allow(subject.request).to receive(:post?).and_return(true)
|
|
70
|
+
allow(subject.request).to receive(:content_type).and_return("FOO")
|
|
71
|
+
|
|
72
|
+
expect { subject.request_validate }.to raise_error(Apes::Errors::BadRequestError)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
it "should complain when the data is missing" do
|
|
76
|
+
allow(subject.request).to receive(:post?).and_return(true)
|
|
77
|
+
allow(subject.request).to receive(:content_type).and_return(Apes::Concerns::Request::CONTENT_TYPE)
|
|
78
|
+
|
|
79
|
+
expect { subject.request_validate }.to raise_error(Apes::Errors::MissingDataError)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
it "should complain when the data is not a valid JSON" do
|
|
83
|
+
subject.request.body = OpenStruct.new(read: "FOO")
|
|
84
|
+
allow(subject.request).to receive(:post?).and_return(true)
|
|
85
|
+
allow(subject.request).to receive(:content_type).and_return(Apes::Concerns::Request::CONTENT_TYPE)
|
|
86
|
+
|
|
87
|
+
expect { subject.request_validate }.to raise_error(Apes::Errors::InvalidDataError)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
it "should complain when the data is missing in the data attribute for JSON API requests" do
|
|
91
|
+
subject.request.body = OpenStruct.new(read: {a: 1}.to_json)
|
|
92
|
+
allow(subject.request).to receive(:post?).and_return(true)
|
|
93
|
+
allow(subject.request).to receive(:content_type).and_return(Apes::Concerns::Request::CONTENT_TYPE)
|
|
94
|
+
|
|
95
|
+
expect { subject.request_validate }.to raise_error(Apes::Errors::MissingDataError)
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
describe "#request_source_host" do
|
|
100
|
+
it "should return the right host" do
|
|
101
|
+
subject.request.url = "http://abc.google.it:1234"
|
|
102
|
+
expect(subject.request_source_host).to eq("abc.google.it")
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
describe "#request_valid_content_type" do
|
|
107
|
+
it "should return the right type" do
|
|
108
|
+
expect(subject.request_valid_content_type).to eq("application/vnd.api+json")
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
describe "#request_extract_model" do
|
|
113
|
+
before(:each) do
|
|
114
|
+
allow(subject.request).to receive(:post?).and_return(true)
|
|
115
|
+
allow(subject.request).to receive(:content_type).and_return(Apes::Concerns::Request::CONTENT_TYPE)
|
|
116
|
+
ActionController::Parameters.action_on_unpermitted_parameters = :raise
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
it "should complain if the type is missing in the data" do
|
|
120
|
+
subject.request.body = OpenStruct.new(read: {data: {foo: 1, body: 1}}.to_json)
|
|
121
|
+
subject.request_validate
|
|
122
|
+
|
|
123
|
+
expect(subject).to receive(:fail_request!).with(:bad_request, "No type provided when type \"mock_model\" was expected.").and_raise(RuntimeError)
|
|
124
|
+
expect { subject.request_extract_model(MockModel.new) }.to raise_error(RuntimeError)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
it "should complain if the type is wrong in the data" do
|
|
128
|
+
subject.request.body = OpenStruct.new(read: {data: {type: "foo", foo: 1, body: 1}}.to_json)
|
|
129
|
+
subject.request_validate
|
|
130
|
+
|
|
131
|
+
expect(subject).to receive(:fail_request!).with(:bad_request, "Invalid type \"foo\" provided when type \"mock_model\" was expected.").and_raise(RuntimeError)
|
|
132
|
+
expect { subject.request_extract_model(MockModel.new) }.to raise_error(RuntimeError)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
it "should complain if the data is not inside the attributes field" do
|
|
136
|
+
subject.request.body = OpenStruct.new(read: {data: {type: "mock_model", foo: 1, body: 1}}.to_json)
|
|
137
|
+
subject.request_validate
|
|
138
|
+
|
|
139
|
+
expect(subject).to receive(:fail_request!).with(:bad_request, "Missing attributes in the \"attributes\" field.").and_raise(RuntimeError)
|
|
140
|
+
expect { subject.request_extract_model(MockModel.new) }.to raise_error(RuntimeError)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
it "should complain if any unknown attribute is present for the mock_model" do
|
|
144
|
+
subject.request.body = OpenStruct.new(read: {data: {type: "mock_model", attributes: {foo: 1, other: 1}}}.to_json)
|
|
145
|
+
subject.request_validate
|
|
146
|
+
|
|
147
|
+
expect { subject.request_extract_model(MockModel.new) }.to raise_error(ActionController::UnpermittedParameters) do |error|
|
|
148
|
+
expect(error.params).to eq(["attributes.foo"])
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
it "should allowed hash attributes" do
|
|
153
|
+
subject.request.body = OpenStruct.new(read: {data: {type: "mock_model", attributes: {foo: 1, other: {a: 1}}}}.to_json)
|
|
154
|
+
subject.request_validate
|
|
155
|
+
|
|
156
|
+
expect { subject.request_extract_model(MockModel.new) }.to raise_error(ActionController::UnpermittedParameters) do |error|
|
|
157
|
+
expect(error.params).to eq(["attributes.foo"])
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
it "should return attributes for the mock_model" do
|
|
162
|
+
subject.request.body = OpenStruct.new(read: {data: {type: "mock_model", attributes: {id: "1", other: 1}}}.to_json)
|
|
163
|
+
subject.request_validate
|
|
164
|
+
|
|
165
|
+
expect(subject.request_extract_model(MockModel.new)).to eq({id: "1", other: 1}.with_indifferent_access)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
it "should return relationships for the model" do
|
|
169
|
+
first = FirstMockModel.new(1)
|
|
170
|
+
allow(FirstMockModel).to receive(:find_with_any).and_return(first)
|
|
171
|
+
subject.request.body = OpenStruct.new(read: {data: {type: "mock_model", attributes: {id: 1}, relationships: {first_mock_model: {data: {type: "first_mock_model", id: first.id}}}}}.to_json)
|
|
172
|
+
subject.request_validate
|
|
173
|
+
|
|
174
|
+
expect(subject.request_extract_model(MockModel.new)).to eq({id: 1, first_mock_model: first}.with_indifferent_access)
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
it "should move inline references to the relationship objects" do
|
|
178
|
+
first = FirstMockModel.new(1)
|
|
179
|
+
allow(FirstMockModel).to receive(:find_with_any).and_return(first)
|
|
180
|
+
subject.request.body = OpenStruct.new(read: {data: {type: "mock_model", attributes: {id: 1, first_mock_model: first.id}}}.to_json)
|
|
181
|
+
subject.request_validate
|
|
182
|
+
|
|
183
|
+
extracted = subject.request_extract_model(MockModel.new)
|
|
184
|
+
expect(extracted.keys.map(&:to_sym)).to eq([:id, :first_mock_model])
|
|
185
|
+
expect(extracted[:first_mock_model].id).to eq(first.id)
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
it "should reject unallowed relationships" do
|
|
189
|
+
subject.request.body = OpenStruct.new(read: {data: {type: "mock_model", attributes: {id: 1}, relationships: {another: {}}}}.to_json)
|
|
190
|
+
subject.request_validate
|
|
191
|
+
|
|
192
|
+
target = MockModel.new
|
|
193
|
+
expect { subject.request_extract_model(target) }.to raise_error(ActionController::UnpermittedParameters) do |error|
|
|
194
|
+
expect(error.params).to eq(["relationships.another"])
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
it "should reject malformed relationships" do
|
|
199
|
+
first = FirstMockModel.new(1)
|
|
200
|
+
|
|
201
|
+
target = MockModel.new
|
|
202
|
+
subject.request.body = OpenStruct.new(read: {data: {type: "mock_model", attributes: {id: 1}, relationships: {first_mock_model: {data: {id: first.id}}}}}.to_json)
|
|
203
|
+
subject.request_validate
|
|
204
|
+
subject.request_extract_model(target)
|
|
205
|
+
expect(target.additional_errors.to_hash).to eq({first_mock_model: ["Relationship does not contain the \"data.type\" attribute"]})
|
|
206
|
+
|
|
207
|
+
target = MockModel.new
|
|
208
|
+
subject.request.body = OpenStruct.new(read: {data: {type: "mock_model", attributes: {id: 1}, relationships: {first_mock_model: {data: {type: "first_mock_model"}}}}}.to_json)
|
|
209
|
+
subject.request_validate
|
|
210
|
+
subject.request_extract_model(target)
|
|
211
|
+
expect(target.additional_errors.to_hash).to eq({first_mock_model: ["Relationship does not contain the \"data.id\" attribute"]})
|
|
212
|
+
|
|
213
|
+
target = MockModel.new
|
|
214
|
+
subject.request.body = OpenStruct.new(read: {data: {type: "mock_model", attributes: {id: 1}, relationships: {first_mock_model: {data: {type: "foo", id: 1}}}}}.to_json)
|
|
215
|
+
subject.request_validate
|
|
216
|
+
subject.request_extract_model(target)
|
|
217
|
+
expect(target.additional_errors.to_hash).to eq({first_mock_model: ["Invalid relationship type \"foo\" provided for when type \"first_mock_model\" was expected."]})
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
it "should reject invalid relationships" do
|
|
221
|
+
allow(SecondMockModel).to receive(:find_with_any).and_return(false)
|
|
222
|
+
target = MockModel.new
|
|
223
|
+
subject.request.body = OpenStruct.new(read: {data: {type: "mock_model", attributes: {id: 1}, relationships: {second: {data: {type: "second_mock_model", id: -1}}}}}.to_json)
|
|
224
|
+
subject.request_validate
|
|
225
|
+
subject.request_extract_model(target)
|
|
226
|
+
expect(target.additional_errors.to_hash).to eq({second: ["Refers to a non existing \"second_mock_model\" resource."]})
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
describe "#request_cast_attributes" do
|
|
231
|
+
it "should correctly cast attributes for a mock_model" do
|
|
232
|
+
attributes = {id: 3, other: "YES", created_at: "2001-02-03T04:05:06.789+0700"}.with_indifferent_access
|
|
233
|
+
casted_attributes = {id: 3, other: true, created_at: DateTime.civil(2001, 2, 3, 4, 5, 6.789, "+7")}.with_indifferent_access
|
|
234
|
+
expect(subject.request_cast_attributes(MockModel.new, attributes)).to eq(casted_attributes)
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
it "should add casting errors to the mock_model validation errors" do
|
|
238
|
+
attributes = {id: 3, other: "yes", created_at: "NO"}.with_indifferent_access
|
|
239
|
+
object = MockModel.new
|
|
240
|
+
subject.request_cast_attributes(object, attributes)
|
|
241
|
+
expect(object.additional_errors.to_hash).to eq(created_at: ["Invalid timestamp \"NO\"."])
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
end
|