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,12 @@
1
+ module Apress
2
+ module Api
3
+ module Callbacks
4
+ class RepeatCallbackError < StandardError
5
+ def initialize(message, backtrace)
6
+ super(message)
7
+ set_backtrace(backtrace)
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,33 @@
1
+ require 'rails/engine'
2
+
3
+ module Apress
4
+ module Api
5
+ class Engine < Rails::Engine
6
+ config.autoload_paths << config.root.join("lib")
7
+ config.paths.add 'app/docs', eager_load: false
8
+
9
+ Apress::Documentation.add_load_path(config.root.join('app/docs'))
10
+
11
+ initializer "apress-api", before: :load_init_rb do |app|
12
+ app.config.paths["db/migrate"].concat(config.paths["db/migrate"].expanded)
13
+
14
+ app.config.api = {
15
+ secret_token_ttl: 1.hour,
16
+ refresh_token_ttl: 1.week,
17
+ v1_doc_path: 'docs/swagger/v1.json'
18
+ }
19
+
20
+ ::MultiJson.use :oj
21
+
22
+ require 'jbuilder/jbuilder_template'
23
+ JbuilderTemplate.send :include, Apress::Api::Extensions::Jbuilder::JbuilderTemplate
24
+ end
25
+
26
+ initializer "apress-api-factories", after: "factory_girl.set_factory_paths" do
27
+ if defined?(FactoryGirl)
28
+ FactoryGirl.definition_file_paths.unshift root.join("spec", "factories")
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,41 @@
1
+ module Apress
2
+ module Api
3
+ module Extensions
4
+ module Jbuilder
5
+ module JbuilderTemplate
6
+ def paginating_cache!(collection, key = nil, options = nil)
7
+ if @context.controller.perform_caching
8
+ result = Rails.cache.fetch(_cache_key(key || collection, options), options) do
9
+ {
10
+ headers: _pagination_headers(collection),
11
+ content: _scope { yield self }
12
+ }
13
+ end
14
+
15
+ _set_pagination_headers(result[:headers])
16
+
17
+ merge! result[:content]
18
+ else
19
+ _set_pagination_headers(_pagination_headers(collection))
20
+
21
+ yield
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def _set_pagination_headers(headers)
28
+ @context.controller.response.headers.merge!(headers)
29
+ end
30
+
31
+ def _pagination_headers(collection)
32
+ ::Apress::Api::ApiController::PaginationHelper.headers(
33
+ collection,
34
+ @context.controller.request.url
35
+ )
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,48 @@
1
+ # coding: utf-8
2
+ require 'rspec/core'
3
+ require 'rspec/expectations'
4
+ require 'swagger'
5
+
6
+ require_relative 'rspec/utils'
7
+
8
+ RSpec::Matchers.define :correspond_to_schema do |schema, options|
9
+ match do |response|
10
+ if schema.present?
11
+ options ||= {}
12
+ JSON::Validator.validate!(schema, response.body.to_s, options)
13
+ else
14
+ true
15
+ end
16
+ end
17
+ end
18
+
19
+ # NOTE: Для того, чтобы находить нужную спецификацию ресурса по конкретному запросу (request.path),
20
+ # нужно уметь находить соответсвие Path -> URITemplatePath
21
+ RSpec.shared_examples 'an api response' do |status|
22
+ let(:method) { request.method.downcase.to_sym }
23
+ let(:schema) do
24
+ begin
25
+ schema = @api.paths[resource][method].responses[response.code].schema
26
+ schema.definitions = @api.definitions if schema.present?
27
+ schema
28
+ rescue
29
+ fail "Swagger schema does not exist: #{dump_response}"
30
+ end
31
+ end
32
+
33
+ it { expect(response).to have_http_status(status).and correspond_to_schema(schema) }
34
+ end
35
+
36
+ RSpec.configure do |c|
37
+ c.include Apress::Api::Rspec::Utils, type: :request
38
+
39
+ c.before(:context, :api) do
40
+ classes = Apress::Api::Swagger::Schema.swagger_classes
41
+ @swagger_schema = ::Swagger::Blocks.build_root_json(classes)
42
+ @api = Swagger.build(@swagger_schema.to_json, format: :json)
43
+ end
44
+
45
+ c.after(:example, :verbose, type: :request) do
46
+ puts dump_response
47
+ end
48
+ end
@@ -0,0 +1,17 @@
1
+ require 'json'
2
+
3
+ module Apress
4
+ module Api
5
+ module Rspec
6
+ module Utils
7
+ def dump_response
8
+ <<-EOS.strip_heredoc
9
+ Request: #{request.method} #{request.original_url}
10
+ Status: #{response.code}
11
+ #{JSON.pretty_generate(JSON.parse(response.body.to_s))}
12
+ EOS
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,9 @@
1
+ require 'json-schema'
2
+ require 'rspec/expectations'
3
+
4
+ RSpec::Matchers.define :match_json_schema do |schema, options|
5
+ match do |json|
6
+ options = {strict: true}.merge!(options || {})
7
+ JSON::Validator.validate!(schema, json, options)
8
+ end
9
+ end
@@ -0,0 +1,5 @@
1
+ module Apress
2
+ module Api
3
+ VERSION = '1.22.0'.freeze
4
+ end
5
+ end
@@ -0,0 +1,12 @@
1
+ namespace :docs do
2
+ desc 'Generate api documentation'
3
+ task generate: :environment do
4
+ filename = 'swagger_v1.json'
5
+ path = Rails.application.config.api[:v1_doc_path]
6
+ FileUtils.mkdir_p(File.dirname(path))
7
+
8
+ Apress::Api::Swagger::Generator.new(path).generate_file
9
+
10
+ puts 'Done.'
11
+ end
12
+ end
@@ -0,0 +1,79 @@
1
+ require "spec_helper"
2
+
3
+ describe Apress::Api::ApiController::Base, type: :controller do
4
+ let(:client) { create "api/client" }
5
+
6
+ context "when api client is required, by default" do
7
+ controller do
8
+ def index
9
+ render json: {status: "ok"}
10
+ end
11
+ end
12
+
13
+ describe "#authenticate" do
14
+ before do
15
+ class_double(Apress::Api::AuthService, new: auth_service).as_stubbed_const
16
+ end
17
+
18
+ context "when auth successfull" do
19
+ let(:auth_service) { double("auth_service", call: true, client: client) }
20
+
21
+ it "assigns current_api_client" do
22
+ get :index
23
+ expect(response.status).to eq 200
24
+ expect(controller.current_api_client).to eq client
25
+ end
26
+ end
27
+
28
+ context "when auth failed" do
29
+ let(:auth_service) { double("auth_service", call: false, client: nil) }
30
+
31
+ it "returns 403" do
32
+ get :index
33
+ expect(response.status).to eq 403
34
+ expect(controller.current_api_client).to be nil
35
+ end
36
+ end
37
+ end
38
+ end
39
+
40
+ context "when api client is optional" do
41
+ controller do
42
+ if (Rails::VERSION::MAJOR == 4 && Rails::VERSION::MINOR == 2) || Rails::VERSION::MAJOR > 4
43
+ skip_before_action :authenticate
44
+ else
45
+ skip_before_filter :authenticate
46
+ end
47
+
48
+ def index
49
+ render json: {status: "ok"}
50
+ end
51
+ end
52
+
53
+ describe "#authenticate" do
54
+ before do
55
+ class_double(Apress::Api::AuthService, new: auth_service).as_stubbed_const
56
+ end
57
+
58
+ context "when auth successfull" do
59
+ let(:auth_service) { double("auth_service", call: true, client: client) }
60
+
61
+ it "assigns current_api_client" do
62
+ get :index
63
+ expect(response.status).to eq 200
64
+ expect(controller.current_api_client).to eq client
65
+ end
66
+ end
67
+
68
+ context "when auth failed" do
69
+ let(:auth_service) { double("auth_service", call: false, client: nil) }
70
+
71
+ it "returns 200 and not assign current_api_client" do
72
+ get :index
73
+ expect(response.status).to eq 200
74
+ expect(controller.current_api_client).to be nil
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,199 @@
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::ApiController::Base, type: :controller do
12
+ describe '#prepare_pagination' do
13
+ controller do
14
+ if (Rails::VERSION::MAJOR == 4 && Rails::VERSION::MINOR == 2) || Rails::VERSION::MAJOR > 4
15
+ skip_before_action :authenticate
16
+ else
17
+ skip_before_filter :authenticate
18
+ end
19
+
20
+ def index
21
+ prepare_pagination(per_page: {max: 100, default: 40})
22
+ head 204
23
+ end
24
+ end
25
+
26
+ context 'when params present' do
27
+ context 'when params are valid' do
28
+ it 'sets variables' do
29
+ get :index, form_params(page: 2, per_page: 5)
30
+
31
+ expect(assigns(:page)).to eq 2
32
+ expect(assigns(:per_page)).to eq 5
33
+ end
34
+ end
35
+
36
+ context 'when params are invalid' do
37
+ it 'returns 400 for negative page' do
38
+ get :index, form_params(page: -1)
39
+
40
+ expect(response.status).to eq 400
41
+ end
42
+
43
+ it 'returns 400 for > max per_page value' do
44
+ get :index, form_params(per_page: 101)
45
+
46
+ expect(response.status).to eq 400
47
+ end
48
+ end
49
+ end
50
+
51
+ context 'when no params are set' do
52
+ it 'sets defaults' do
53
+ get :index
54
+
55
+ expect(assigns(:page)).to eq 1
56
+ expect(assigns(:per_page)).to eq 40
57
+ end
58
+ end
59
+ end
60
+
61
+ describe "#pagination_headers" do
62
+ let(:current_page) { 1 }
63
+ let(:collection) do
64
+ double(
65
+ total_entries: 30,
66
+ total_pages: 3,
67
+ per_page: 10,
68
+ current_page: current_page
69
+ )
70
+ end
71
+
72
+ context 'when there are no other values in Link header' do
73
+ controller do
74
+ if (Rails::VERSION::MAJOR == 4 && Rails::VERSION::MINOR == 2) || Rails::VERSION::MAJOR > 4
75
+ skip_before_action :authenticate
76
+ else
77
+ skip_before_filter :authenticate
78
+ end
79
+
80
+ def index
81
+ pagination_headers(collection)
82
+ render json: {status: 'ok'}
83
+ end
84
+ end
85
+
86
+ before do
87
+ allow(controller).to receive(:collection).and_return(collection)
88
+ get :index
89
+ end
90
+
91
+ it 'sets correct X-Total-Count header' do
92
+ expect(response.header['X-Total-Count']).to eq '30'
93
+ end
94
+
95
+ it 'sets correct X-Total-Pages header' do
96
+ expect(response.header['X-Total-Pages']).to eq '3'
97
+ end
98
+
99
+ it 'sets correct X-Per-Page header' do
100
+ expect(response.header['X-Per-Page']).to eq '10'
101
+ end
102
+
103
+ context 'when current page is a first page' do
104
+ it 'sets correct X-Page header' do
105
+ expect(response.header['X-Page']).to eq '1'
106
+ end
107
+
108
+ it 'does not add pagination links for first and previous pages to Link header' do
109
+ expect(response.header['Link'].to_s).not_to include('rel="first"', 'rel="prev"')
110
+ end
111
+
112
+ it 'adds pagination links for next and last pages to Link header' do
113
+ expect(response.header['Link'].to_s.split(', ')).to include(
114
+ %(<#{request.url}?page=2>; rel="next"),
115
+ %(<#{request.url}?page=3>; rel="last")
116
+ )
117
+ end
118
+ end
119
+
120
+ context 'when current page is somewhere in the middle' do
121
+ let(:current_page) { 2 }
122
+
123
+ it 'sets correct X-Page header' do
124
+ expect(response.header['X-Page']).to eq '2'
125
+ end
126
+
127
+ it 'adds pagination links for first, previous, next and last pages to Link header' do
128
+ expect(response.header['Link'].to_s.split(', ')).to include(
129
+ %(<#{request.url}?page=1>; rel="first"),
130
+ %(<#{request.url}?page=1>; rel="prev"),
131
+ %(<#{request.url}?page=3>; rel="next"),
132
+ %(<#{request.url}?page=3>; rel="last")
133
+ )
134
+ end
135
+ end
136
+
137
+ context 'when current page is a last page' do
138
+ it 'sets correct X-Page header' do
139
+ expect(response.header['X-Page']).to eq '3'
140
+ end
141
+
142
+ let(:current_page) { 3 }
143
+
144
+ it 'does not add pagination links for next and last pages to Link header' do
145
+ expect(response.header['Link'].to_s).not_to include('rel="next"', 'rel="last"')
146
+ end
147
+
148
+ it 'adds pagination links for first and previous pages to Link header' do
149
+ expect(response.header['Link'].to_s.split(', ')).to include(
150
+ %(<#{request.url}?page=1>; rel="first"),
151
+ %(<#{request.url}?page=2>; rel="prev")
152
+ )
153
+ end
154
+ end
155
+
156
+ context 'when there is only one page' do
157
+ let(:collection) do
158
+ double(
159
+ total_entries: 9,
160
+ total_pages: 1,
161
+ per_page: 10,
162
+ current_page: 1
163
+ )
164
+ end
165
+
166
+ it 'does not add pagination links to Link header' do
167
+ expect(response.header['Link'].to_s).not_to include('rel="first"', 'rel="prev"', 'rel="last"', 'rel="next"')
168
+ end
169
+ end
170
+ end
171
+
172
+ context 'when Link header already has some values' do
173
+ controller do
174
+ if (Rails::VERSION::MAJOR == 4 && Rails::VERSION::MINOR == 2) || Rails::VERSION::MAJOR > 4
175
+ skip_before_action :authenticate
176
+ else
177
+ skip_before_filter :authenticate
178
+ end
179
+
180
+ def index
181
+ headers['Link'] = '<http://test.host/help>; rel="help"'
182
+
183
+ pagination_headers(collection)
184
+ render json: {status: 'ok'}
185
+ end
186
+ end
187
+
188
+ let(:current_page) { 2 }
189
+
190
+ before do
191
+ allow(controller).to receive(:collection).and_return(collection)
192
+ end
193
+
194
+ it do
195
+ expect { get :index }.to raise_error(Apress::Api::ApiController::Pagination::LinkHeaderAppendNotImplemented)
196
+ end
197
+ end
198
+ end
199
+ end