apress-api 1.22.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 (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