apress-api 1.22.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.drone.yml +30 -0
- data/.gitignore +15 -0
- data/.rspec +4 -0
- data/Appraisals +31 -0
- data/CHANGELOG.md +227 -0
- data/Gemfile +8 -0
- data/LICENSE.txt +22 -0
- data/README.md +31 -0
- data/Rakefile +2 -0
- data/app/controllers/apress/api/deprecated_versions_controller.rb +15 -0
- data/app/controllers/apress/api/v1/callbacks_controller.rb +30 -0
- data/app/controllers/apress/api/v1/tokens_controller.rb +24 -0
- data/app/docs/schema/api/v1/types/apress/api/link.rb +29 -0
- data/app/docs/schema/api/v1/types/apress/api/links.rb +24 -0
- data/app/docs/swagger/v1/controllers/apress/api/tokens_controller.rb +64 -0
- data/app/docs/swagger/v1/default_responses/bad_request.rb +16 -0
- data/app/docs/swagger/v1/default_responses/not_found.rb +16 -0
- data/app/docs/swagger/v1/default_responses/unauthenticated.rb +16 -0
- data/app/docs/swagger/v1/default_responses/unauthorized.rb +16 -0
- data/app/docs/swagger/v1/default_responses/unprocessable.rb +16 -0
- data/app/docs/swagger/v1/default_responses/updates_locked.rb +13 -0
- data/app/docs/swagger/v1/models/apress/api/client.rb +53 -0
- data/app/docs/swagger/v1/models/apress/api/simple_error.rb +27 -0
- data/app/docs/swagger/v1/models/apress/api/unproccesable_error.rb +16 -0
- data/app/docs/swagger/v1/models/apress/api/unprocessable_error.rb +37 -0
- data/app/docs/swagger/v1/root.rb +28 -0
- data/app/interactors/apress/api/callbacks/base_callback.rb +42 -0
- data/app/interactors/apress/api/delayed_fire_callback.rb +25 -0
- data/app/jobs/apress/api/event_handler_enqueueing_job.rb +19 -0
- data/app/jobs/apress/api/fire_callback_job.rb +25 -0
- data/app/models/apress/api/client.rb +60 -0
- data/app/policies/apress/api/callback_policy.rb +15 -0
- data/app/services/apress/api/auth_service.rb +37 -0
- data/app/views/apress/api/shared/_exception.json.jbuilder +4 -0
- data/app/views/apress/api/shared/error.json.jbuilder +5 -0
- data/app/views/apress/api/shared/parameter_missing_errors.json.jbuilder +9 -0
- data/app/views/apress/api/shared/unprocessable_errors.json.jbuilder +9 -0
- data/app/views/apress/api/v1/clients/_client.json.jbuilder +2 -0
- data/app/views/apress/api/v1/tokens/create.json.jbuilder +4 -0
- data/apress-api.gemspec +46 -0
- data/config/routes.rb +17 -0
- data/db/migrate/20150716000000_create_api_clients.rb +38 -0
- data/dip.yml +48 -0
- data/docker-compose.development.yml +18 -0
- data/docker-compose.drone.yml +7 -0
- data/docker-compose.yml +20 -0
- data/lib/apress-api.rb +1 -0
- data/lib/apress/api.rb +25 -0
- data/lib/apress/api/api_controller/authentification.rb +33 -0
- data/lib/apress/api/api_controller/base.rb +63 -0
- data/lib/apress/api/api_controller/compatibility.rb +26 -0
- data/lib/apress/api/api_controller/pagination.rb +60 -0
- data/lib/apress/api/api_controller/pagination_helper.rb +55 -0
- data/lib/apress/api/api_controller/rescue.rb +89 -0
- data/lib/apress/api/api_controller/responds.rb +25 -0
- data/lib/apress/api/callbacks/config.rb +56 -0
- data/lib/apress/api/callbacks/fire_callback_error.rb +12 -0
- data/lib/apress/api/callbacks/integration.rb +28 -0
- data/lib/apress/api/callbacks/repeat_callback_error.rb +12 -0
- data/lib/apress/api/engine.rb +33 -0
- data/lib/apress/api/extensions/jbuilder/jbuilder_template.rb +41 -0
- data/lib/apress/api/rspec.rb +48 -0
- data/lib/apress/api/rspec/utils.rb +17 -0
- data/lib/apress/api/testing/json_matcher.rb +9 -0
- data/lib/apress/api/version.rb +5 -0
- data/lib/tasks/docs.rake +12 -0
- data/spec/controllers/api_controller/authentification_spec.rb +79 -0
- data/spec/controllers/api_controller/pagination_spec.rb +199 -0
- data/spec/controllers/api_controller/rescue_spec.rb +167 -0
- data/spec/controllers/deprecated_versions_controller_spec.rb +10 -0
- data/spec/controllers/v1/callbacks_controller_spec.rb +50 -0
- data/spec/controllers/v1/tokens_controller_spec.rb +53 -0
- data/spec/factories/client_factory.rb +4 -0
- data/spec/helpers/paginating_cache_spec.rb +72 -0
- data/spec/interactors/apress/api/delayed_fire_callback_spec.rb +43 -0
- data/spec/internal/app/integrations/error_client/fire_callback.rb +14 -0
- data/spec/internal/app/integrations/service_client/fire_callback.rb +7 -0
- data/spec/internal/app/jobs/handler_job.rb +5 -0
- data/spec/internal/app/jobs/second_handler_job.rb +5 -0
- data/spec/internal/app/models/dummy_model.rb +15 -0
- data/spec/internal/config/database.yml +5 -0
- data/spec/internal/config/environments/test.rb +5 -0
- data/spec/internal/config/initializers/api.rb +10 -0
- data/spec/internal/config/routes.rb +3 -0
- data/spec/internal/db/schema.rb +5 -0
- data/spec/internal/log/.gitignore +1 -0
- data/spec/jobs/apress/api/event_handler_equeueing_job_spec.rb +31 -0
- data/spec/jobs/apress/api/fire_callback_job_spec.rb +34 -0
- data/spec/lib/apress/api/callbacks/integration_spec.rb +24 -0
- data/spec/models/client_spec.rb +25 -0
- data/spec/services/auth_service_spec.rb +64 -0
- data/spec/spec_helper.rb +34 -0
- metadata +518 -0
@@ -0,0 +1,167 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Apress::Api::ApiController::Base, type: :controller do
|
4
|
+
render_views
|
5
|
+
|
6
|
+
let(:client) { create "api/client" }
|
7
|
+
let(:json) { JSON.parse(response.body) }
|
8
|
+
|
9
|
+
before do
|
10
|
+
allow(controller).to receive(:authenticate)
|
11
|
+
allow(controller).to receive(:current_api_client).and_return(client)
|
12
|
+
end
|
13
|
+
|
14
|
+
describe "#render_error" do
|
15
|
+
|
16
|
+
context "when exception given" do
|
17
|
+
controller do
|
18
|
+
def index
|
19
|
+
exception = ArgumentError.new(:message)
|
20
|
+
exception.set_backtrace(%w(path/to/file))
|
21
|
+
render_error(400, exception)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
it do
|
26
|
+
get :index
|
27
|
+
|
28
|
+
expect(response.status).to eq 400
|
29
|
+
expect(json["status"]).to eq 400
|
30
|
+
expect(json["error"]["message"]).to eq "message"
|
31
|
+
expect(json["error"]["backtrace"][0]).to eq "path/to/file"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
context "when server error" do
|
36
|
+
controller do
|
37
|
+
def index
|
38
|
+
exception = ArgumentError.new(:message)
|
39
|
+
exception.set_backtrace(%w(path/to/file))
|
40
|
+
server_error(exception)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
it do
|
45
|
+
get :index
|
46
|
+
|
47
|
+
expect(response.status).to eq 500
|
48
|
+
expect(json["status"]).to eq 500
|
49
|
+
expect(json["error"]["message"]).to eq "message"
|
50
|
+
expect(json["error"]["backtrace"][0]).to eq "path/to/file"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
context "when no exception" do
|
55
|
+
controller do
|
56
|
+
def index
|
57
|
+
not_found
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
it do
|
62
|
+
get :index
|
63
|
+
|
64
|
+
expect(response.status).to eq 404
|
65
|
+
expect(json["status"]).to eq 404
|
66
|
+
expect(json["error"]).to be nil
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
describe '#unproccesable' do
|
72
|
+
let(:record) { build 'api/client' }
|
73
|
+
|
74
|
+
before do
|
75
|
+
record.errors.add(:access_id, 'empty')
|
76
|
+
allow(controller).to receive(:record).and_return(record)
|
77
|
+
end
|
78
|
+
|
79
|
+
context 'when rescued from record invalid' do
|
80
|
+
controller do
|
81
|
+
def index
|
82
|
+
raise ActiveRecord::RecordInvalid.new(record)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
it 'renders errors' do
|
87
|
+
get :index
|
88
|
+
|
89
|
+
expect(response.status).to eq 422
|
90
|
+
expect(json['status']).to eq 422
|
91
|
+
expect(json['errors']).to eq [{"access_id" => "empty"}]
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
context 'when rescued from standard error' do
|
96
|
+
controller do
|
97
|
+
class CustomError < StandardError
|
98
|
+
end
|
99
|
+
|
100
|
+
rescue_from CustomError, with: :unprocessable
|
101
|
+
|
102
|
+
def index
|
103
|
+
raise CustomError.new("custom error")
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
it "renders error" do
|
108
|
+
get :index
|
109
|
+
|
110
|
+
expect(response.status).to eq 422
|
111
|
+
expect(json['status']).to eq 422
|
112
|
+
expect(json['errors']).to eq [{"message" => "custom error"}]
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
context 'when called from action' do
|
117
|
+
controller do
|
118
|
+
def index
|
119
|
+
unprocessable(record.errors)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
it 'renders errors' do
|
124
|
+
get :index
|
125
|
+
|
126
|
+
expect(response.status).to eq 422
|
127
|
+
expect(json['status']).to eq 422
|
128
|
+
expect(json['errors']).to eq [{"access_id" => "empty"}]
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
context 'when called from action with multiple errors' do
|
133
|
+
controller do
|
134
|
+
def index
|
135
|
+
unprocessable([record.errors, 'other' => 'error'])
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
it 'renders errors' do
|
140
|
+
get :index
|
141
|
+
|
142
|
+
expect(response.status).to eq 422
|
143
|
+
expect(json['status']).to eq 422
|
144
|
+
expect(json['errors']).to eq [{"access_id" => "empty"}, {'other' => 'error'}]
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
context '#parameter_missing' do
|
150
|
+
context 'when rescued' do
|
151
|
+
controller do
|
152
|
+
def index
|
153
|
+
params = ActionController::Parameters.new
|
154
|
+
params.require(:a)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
it 'renders errors' do
|
159
|
+
get :index
|
160
|
+
|
161
|
+
expect(response.status).to eq 400
|
162
|
+
expect(json['status']).to eq 400
|
163
|
+
expect(json['errors']).to eq [{"a" => "missing"}]
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
def form_params(p)
|
4
|
+
if Rails::VERSION::MAJOR > 4
|
5
|
+
{params: p}
|
6
|
+
else
|
7
|
+
p
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
describe Apress::Api::V1::CallbacksController, type: :controller do
|
12
|
+
let!(:client) { create "api/client" }
|
13
|
+
before do
|
14
|
+
allow(controller).to receive(:authenticate)
|
15
|
+
allow(controller).to receive(:current_api_client).and_return(client)
|
16
|
+
end
|
17
|
+
|
18
|
+
describe "#create" do
|
19
|
+
context "when client present and allowed" do
|
20
|
+
before do
|
21
|
+
client.update_attributes(access_id: 'service_access_id')
|
22
|
+
end
|
23
|
+
|
24
|
+
context 'when params are valid' do
|
25
|
+
it 'calls enqueueing job for each handler' do
|
26
|
+
expect(Resque).to receive(:enqueue).with(Apress::Api::EventHandlerEnqueueingJob, 'handler_job', {})
|
27
|
+
expect(Resque).to receive(:enqueue).with(Apress::Api::EventHandlerEnqueueingJob, 'second_handler_job', {})
|
28
|
+
post :create, form_params(service: 'external_service', event: 'other_event')
|
29
|
+
expect(response.status).to eq 201
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context 'when job is missing' do
|
34
|
+
it 'raises KeyError' do
|
35
|
+
expect do
|
36
|
+
post :create, form_params(service: 'service', event: 'some_event')
|
37
|
+
end.to raise_error(KeyError)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
context "when client isn't allowed" do
|
43
|
+
it 'returns 403' do
|
44
|
+
post :create, form_params(service: 'service', event: 'some_event')
|
45
|
+
|
46
|
+
expect(response.status).to eq 403
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
def form_params(p)
|
4
|
+
if Rails::VERSION::MAJOR > 4
|
5
|
+
{params: p}
|
6
|
+
else
|
7
|
+
p
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
describe Apress::Api::V1::TokensController, type: :controller do
|
12
|
+
render_views
|
13
|
+
|
14
|
+
let!(:client) { create "api/client" }
|
15
|
+
|
16
|
+
describe "#create" do
|
17
|
+
context "when client doesn't exist" do
|
18
|
+
it do
|
19
|
+
post :create, form_params(client_id: "no-name")
|
20
|
+
expect(response.status).to eq 404
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
context "when refresh token not valid" do
|
25
|
+
it do
|
26
|
+
post :create, form_params(client_id: client.access_id, refresh_token: "bad-token")
|
27
|
+
expect(response.status).to eq 400
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
context "when refresh token expired" do
|
32
|
+
it do
|
33
|
+
Timecop.travel(1.year.from_now)
|
34
|
+
post :create, form_params(client_id: client.access_id, refresh_token: client.refresh_token)
|
35
|
+
expect(response.status).to eq 403
|
36
|
+
Timecop.return
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
context "when refresh token is valid" do
|
41
|
+
it "returns regenerated tokens" do
|
42
|
+
post :create, form_params(client_id: client.access_id, refresh_token: client.refresh_token)
|
43
|
+
expect(response.status).to eq 200
|
44
|
+
|
45
|
+
client.reload
|
46
|
+
json = JSON.parse(response.body)
|
47
|
+
|
48
|
+
expect(json["client"]["secret_token"]).to eq client.secret_token
|
49
|
+
expect(json["client"]["refresh_token"]).to eq client.refresh_token
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
SIMPLE_TEST = <<-JBUILDER
|
3
|
+
json.paginating_cache! @collection, nil, skip_digest: true do
|
4
|
+
json.title 'test'
|
5
|
+
end
|
6
|
+
JBUILDER
|
7
|
+
|
8
|
+
TEST_WITH_CACHING = <<-JBUILDER
|
9
|
+
json.paginating_cache! @collection, ['v1', @collection], expire_in: 30.minutes, skip_digest: true do
|
10
|
+
json.title 'test'
|
11
|
+
end
|
12
|
+
JBUILDER
|
13
|
+
|
14
|
+
def jbuild(source_key, collection)
|
15
|
+
partials = {
|
16
|
+
'test.json.jbuilder' => SIMPLE_TEST,
|
17
|
+
'test_with_cache_key.json.jbuilder' => TEST_WITH_CACHING
|
18
|
+
}
|
19
|
+
|
20
|
+
resolver = ActionView::FixtureResolver.new(partials)
|
21
|
+
lookup_context.view_paths = [resolver]
|
22
|
+
assign(:collection, collection)
|
23
|
+
MultiJson.load(render(template: source_key))
|
24
|
+
end
|
25
|
+
|
26
|
+
def view_prefix
|
27
|
+
if Rails::VERSION::MAJOR > 4
|
28
|
+
'jbuilder/views'
|
29
|
+
else
|
30
|
+
'jbuilder'
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe 'paginating_cache', type: :view do
|
35
|
+
let(:collection) { double(total_entries: 30, total_pages: 10, per_page: 5, current_page: 2, cache_key: 'test') }
|
36
|
+
context 'when caching disabled' do
|
37
|
+
it 'sets headers and yield view' do
|
38
|
+
result = jbuild('test.json.jbuilder', collection)
|
39
|
+
expect(result['title']).to eq 'test'
|
40
|
+
expect(view.controller.response.headers['X-Total-Count']).to eq '30'
|
41
|
+
expect(view.controller.response.headers['X-Total-Pages']).to eq '10'
|
42
|
+
expect(view.controller.response.headers['X-Per-Page']).to eq '5'
|
43
|
+
expect(view.controller.response.headers['X-Page']).to eq '2'
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
context 'when caching enable' do
|
48
|
+
before do
|
49
|
+
view.controller.perform_caching = true
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'cache result with collection key' do
|
53
|
+
expect(Rails.cache).to receive(:fetch).with("#{view_prefix}/test", skip_digest: true).and_call_original
|
54
|
+
|
55
|
+
jbuild('test.json.jbuilder', collection)
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'cache results with custom composite key' do
|
59
|
+
expect(Rails.cache).to \
|
60
|
+
receive(:fetch).with("#{view_prefix}/v1/test", expire_in: 30.minutes, skip_digest: true).and_call_original
|
61
|
+
|
62
|
+
jbuild('test_with_cache_key.json.jbuilder', collection)
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'sets headers and yield view' do
|
66
|
+
result = jbuild('test.json.jbuilder', collection)
|
67
|
+
expect(result['title']).to eq 'test'
|
68
|
+
expect(view.controller.response.headers['X-Total-Count']).to eq '30'
|
69
|
+
expect(view.controller.response.headers['X-Total-Pages']).to eq '10'
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Apress::Api::DelayedFireCallback, type: :interactor do
|
4
|
+
let(:result) { described_class.call(event: 'some_event', params: {test: 1}) }
|
5
|
+
|
6
|
+
describe '#call' do
|
7
|
+
it 'calls job with correct params' do
|
8
|
+
expect(Resque).to receive(:enqueue).with(
|
9
|
+
::Apress::Api::FireCallbackJob,
|
10
|
+
'service',
|
11
|
+
'some_event',
|
12
|
+
test: 1
|
13
|
+
)
|
14
|
+
|
15
|
+
result
|
16
|
+
end
|
17
|
+
|
18
|
+
context 'when multiple services' do
|
19
|
+
before do
|
20
|
+
allow(Apress::Api::Callbacks::Config).to \
|
21
|
+
receive(:services).with('some_event').and_return(%w(service_1 service_2))
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'calls 2 jobs' do
|
25
|
+
expect(Resque).to receive(:enqueue).with(
|
26
|
+
::Apress::Api::FireCallbackJob,
|
27
|
+
'service_1',
|
28
|
+
'some_event',
|
29
|
+
test: 1
|
30
|
+
)
|
31
|
+
|
32
|
+
expect(Resque).to receive(:enqueue).with(
|
33
|
+
::Apress::Api::FireCallbackJob,
|
34
|
+
'service_2',
|
35
|
+
'some_event',
|
36
|
+
test: 1
|
37
|
+
)
|
38
|
+
|
39
|
+
result
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module ErrorClient
|
2
|
+
class FireCallback < Apress::Api::Callbacks::BaseCallback
|
3
|
+
class RepeatError < StandardError
|
4
|
+
end
|
5
|
+
add_retry_exceptions ArgumentError
|
6
|
+
add_repeat_exceptions RepeatError
|
7
|
+
|
8
|
+
delegate :event, to: :context
|
9
|
+
def call
|
10
|
+
raise RepeatError if event == 'repeat_error'
|
11
|
+
raise ArgumentError
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|