apress-api 1.22.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (94) hide show
  1. checksums.yaml +7 -0
  2. data/.drone.yml +30 -0
  3. data/.gitignore +15 -0
  4. data/.rspec +4 -0
  5. data/Appraisals +31 -0
  6. data/CHANGELOG.md +227 -0
  7. data/Gemfile +8 -0
  8. data/LICENSE.txt +22 -0
  9. data/README.md +31 -0
  10. data/Rakefile +2 -0
  11. data/app/controllers/apress/api/deprecated_versions_controller.rb +15 -0
  12. data/app/controllers/apress/api/v1/callbacks_controller.rb +30 -0
  13. data/app/controllers/apress/api/v1/tokens_controller.rb +24 -0
  14. data/app/docs/schema/api/v1/types/apress/api/link.rb +29 -0
  15. data/app/docs/schema/api/v1/types/apress/api/links.rb +24 -0
  16. data/app/docs/swagger/v1/controllers/apress/api/tokens_controller.rb +64 -0
  17. data/app/docs/swagger/v1/default_responses/bad_request.rb +16 -0
  18. data/app/docs/swagger/v1/default_responses/not_found.rb +16 -0
  19. data/app/docs/swagger/v1/default_responses/unauthenticated.rb +16 -0
  20. data/app/docs/swagger/v1/default_responses/unauthorized.rb +16 -0
  21. data/app/docs/swagger/v1/default_responses/unprocessable.rb +16 -0
  22. data/app/docs/swagger/v1/default_responses/updates_locked.rb +13 -0
  23. data/app/docs/swagger/v1/models/apress/api/client.rb +53 -0
  24. data/app/docs/swagger/v1/models/apress/api/simple_error.rb +27 -0
  25. data/app/docs/swagger/v1/models/apress/api/unproccesable_error.rb +16 -0
  26. data/app/docs/swagger/v1/models/apress/api/unprocessable_error.rb +37 -0
  27. data/app/docs/swagger/v1/root.rb +28 -0
  28. data/app/interactors/apress/api/callbacks/base_callback.rb +42 -0
  29. data/app/interactors/apress/api/delayed_fire_callback.rb +25 -0
  30. data/app/jobs/apress/api/event_handler_enqueueing_job.rb +19 -0
  31. data/app/jobs/apress/api/fire_callback_job.rb +25 -0
  32. data/app/models/apress/api/client.rb +60 -0
  33. data/app/policies/apress/api/callback_policy.rb +15 -0
  34. data/app/services/apress/api/auth_service.rb +37 -0
  35. data/app/views/apress/api/shared/_exception.json.jbuilder +4 -0
  36. data/app/views/apress/api/shared/error.json.jbuilder +5 -0
  37. data/app/views/apress/api/shared/parameter_missing_errors.json.jbuilder +9 -0
  38. data/app/views/apress/api/shared/unprocessable_errors.json.jbuilder +9 -0
  39. data/app/views/apress/api/v1/clients/_client.json.jbuilder +2 -0
  40. data/app/views/apress/api/v1/tokens/create.json.jbuilder +4 -0
  41. data/apress-api.gemspec +46 -0
  42. data/config/routes.rb +17 -0
  43. data/db/migrate/20150716000000_create_api_clients.rb +38 -0
  44. data/dip.yml +48 -0
  45. data/docker-compose.development.yml +18 -0
  46. data/docker-compose.drone.yml +7 -0
  47. data/docker-compose.yml +20 -0
  48. data/lib/apress-api.rb +1 -0
  49. data/lib/apress/api.rb +25 -0
  50. data/lib/apress/api/api_controller/authentification.rb +33 -0
  51. data/lib/apress/api/api_controller/base.rb +63 -0
  52. data/lib/apress/api/api_controller/compatibility.rb +26 -0
  53. data/lib/apress/api/api_controller/pagination.rb +60 -0
  54. data/lib/apress/api/api_controller/pagination_helper.rb +55 -0
  55. data/lib/apress/api/api_controller/rescue.rb +89 -0
  56. data/lib/apress/api/api_controller/responds.rb +25 -0
  57. data/lib/apress/api/callbacks/config.rb +56 -0
  58. data/lib/apress/api/callbacks/fire_callback_error.rb +12 -0
  59. data/lib/apress/api/callbacks/integration.rb +28 -0
  60. data/lib/apress/api/callbacks/repeat_callback_error.rb +12 -0
  61. data/lib/apress/api/engine.rb +33 -0
  62. data/lib/apress/api/extensions/jbuilder/jbuilder_template.rb +41 -0
  63. data/lib/apress/api/rspec.rb +48 -0
  64. data/lib/apress/api/rspec/utils.rb +17 -0
  65. data/lib/apress/api/testing/json_matcher.rb +9 -0
  66. data/lib/apress/api/version.rb +5 -0
  67. data/lib/tasks/docs.rake +12 -0
  68. data/spec/controllers/api_controller/authentification_spec.rb +79 -0
  69. data/spec/controllers/api_controller/pagination_spec.rb +199 -0
  70. data/spec/controllers/api_controller/rescue_spec.rb +167 -0
  71. data/spec/controllers/deprecated_versions_controller_spec.rb +10 -0
  72. data/spec/controllers/v1/callbacks_controller_spec.rb +50 -0
  73. data/spec/controllers/v1/tokens_controller_spec.rb +53 -0
  74. data/spec/factories/client_factory.rb +4 -0
  75. data/spec/helpers/paginating_cache_spec.rb +72 -0
  76. data/spec/interactors/apress/api/delayed_fire_callback_spec.rb +43 -0
  77. data/spec/internal/app/integrations/error_client/fire_callback.rb +14 -0
  78. data/spec/internal/app/integrations/service_client/fire_callback.rb +7 -0
  79. data/spec/internal/app/jobs/handler_job.rb +5 -0
  80. data/spec/internal/app/jobs/second_handler_job.rb +5 -0
  81. data/spec/internal/app/models/dummy_model.rb +15 -0
  82. data/spec/internal/config/database.yml +5 -0
  83. data/spec/internal/config/environments/test.rb +5 -0
  84. data/spec/internal/config/initializers/api.rb +10 -0
  85. data/spec/internal/config/routes.rb +3 -0
  86. data/spec/internal/db/schema.rb +5 -0
  87. data/spec/internal/log/.gitignore +1 -0
  88. data/spec/jobs/apress/api/event_handler_equeueing_job_spec.rb +31 -0
  89. data/spec/jobs/apress/api/fire_callback_job_spec.rb +34 -0
  90. data/spec/lib/apress/api/callbacks/integration_spec.rb +24 -0
  91. data/spec/models/client_spec.rb +25 -0
  92. data/spec/services/auth_service_spec.rb +64 -0
  93. data/spec/spec_helper.rb +34 -0
  94. 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,10 @@
1
+ require "spec_helper"
2
+
3
+ describe Apress::Api::DeprecatedVersionsController, type: :controller do
4
+ describe "#show" do
5
+ it "returns 410" do
6
+ get :show
7
+ expect(response.status).to eq 410
8
+ end
9
+ end
10
+ 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,4 @@
1
+ FactoryGirl.define do
2
+ factory 'api/client', class: Apress::Api::Client do
3
+ end
4
+ 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
@@ -0,0 +1,7 @@
1
+ module ServiceClient
2
+ class FireCallback < Apress::Api::Callbacks::BaseCallback
3
+ def call
4
+ "Fired"
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,5 @@
1
+ class HandlerJob
2
+ def self.perform
3
+ "Do work"
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ class SecondHandlerJob
2
+ def self.perform
3
+ "Do work"
4
+ end
5
+ end