direct-api-v5 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -1
  3. data/.rspec +2 -0
  4. data/.rubocop.yml +19 -0
  5. data/.travis.yml +17 -0
  6. data/Gemfile +2 -0
  7. data/README.md +127 -3
  8. data/direct-api-v5.gemspec +11 -5
  9. data/lib/direct/api/v5.rb +48 -3
  10. data/lib/direct/api/v5/client.rb +18 -0
  11. data/lib/direct/api/v5/default_settings.rb +42 -0
  12. data/lib/direct/api/v5/errors.rb +10 -0
  13. data/lib/direct/api/v5/params_builder.rb +46 -0
  14. data/lib/direct/api/v5/refinements/camelize.rb +22 -0
  15. data/lib/direct/api/v5/request.rb +66 -0
  16. data/lib/direct/api/v5/response.rb +32 -0
  17. data/lib/direct/api/v5/response/error.rb +21 -0
  18. data/lib/direct/api/v5/response/units.rb +34 -0
  19. data/lib/direct/api/v5/service.rb +32 -0
  20. data/lib/direct/api/v5/settings.rb +19 -0
  21. data/lib/direct/api/v5/version.rb +1 -1
  22. data/spec/acceptance/error_spec.rb +32 -0
  23. data/spec/acceptance/get_campaigns_spec.rb +72 -0
  24. data/spec/direct/api/v5/client_spec.rb +28 -0
  25. data/spec/direct/api/v5/default_settings_spec.rb +42 -0
  26. data/spec/direct/api/v5/params_builder_spec.rb +39 -0
  27. data/spec/direct/api/v5/refinements/camelize_spec.rb +21 -0
  28. data/spec/direct/api/v5/request_spec.rb +60 -0
  29. data/spec/direct/api/v5/response/error_spec.rb +30 -0
  30. data/spec/direct/api/v5/response/units_spec.rb +31 -0
  31. data/spec/direct/api/v5/response_spec.rb +66 -0
  32. data/spec/direct/api/v5/settings_spec.rb +42 -0
  33. data/spec/direct/api/v5_spec.rb +37 -0
  34. data/spec/fixtures/settings.yml +5 -0
  35. data/spec/shared/direct_api_helper.rb +66 -0
  36. data/spec/spec_helper.rb +25 -0
  37. data/spec/support/webmock.rb +3 -0
  38. metadata +127 -11
@@ -0,0 +1,66 @@
1
+ module Direct::API::V5
2
+ class Request
3
+ REQUEST_TIMEOUT = 300
4
+
5
+ def initialize(settings:, service_name:, api_method:, params: {})
6
+ @settings = settings
7
+ @service_name = service_name
8
+ @api_method = api_method
9
+ @params = params
10
+ end
11
+
12
+ def send
13
+ response = make_http_request
14
+ response_body = parse_response(response.body)
15
+
16
+ [response_body, response.headers]
17
+ end
18
+
19
+ private
20
+
21
+ def make_http_request
22
+ connection = Faraday.new(url: "https://#{api_host}")
23
+
24
+ connection.post do |request|
25
+ request.options.timeout = REQUEST_TIMEOUT
26
+ request.url api_url
27
+ request.body = body
28
+ headers.each do |key, value|
29
+ request.headers[key] = value
30
+ end
31
+ end
32
+ rescue Faraday::Error => e
33
+ raise RequestError, e.message
34
+ end
35
+
36
+ def parse_response(response_body)
37
+ MultiJson.load(response_body, symbolize_keys: true)
38
+ rescue MultiJson::ParseError => e
39
+ raise InvalidResponseError, e.message
40
+ end
41
+
42
+ def api_host
43
+ @settings.host
44
+ end
45
+
46
+ def api_url
47
+ "/json/v5/#{@service_name}/"
48
+ end
49
+
50
+ def headers
51
+ {
52
+ 'Content-Type' => 'application/json; charset=utf-8',
53
+ 'Accept-Language' => @settings.language,
54
+ 'Authorization' => "Bearer #{@settings.auth_token}",
55
+ 'Client-Login' => @settings.client_login
56
+ }
57
+ end
58
+
59
+ def body
60
+ MultiJson.dump(
61
+ method: @api_method,
62
+ params: ParamsBuilder.new(@params).build
63
+ )
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,32 @@
1
+ module Direct::API::V5
2
+ class Response
3
+ attr_reader :body
4
+ attr_reader :headers
5
+
6
+ def initialize(body, headers)
7
+ @body = body
8
+ @headers = headers
9
+ end
10
+
11
+ def result
12
+ body[:result]
13
+ end
14
+
15
+ def request_id
16
+ @request_id ||= headers[:RequestId]
17
+ end
18
+
19
+ def units
20
+ @units ||= Units.new(headers[:Units])
21
+ end
22
+
23
+ def error?
24
+ body.key?(:error)
25
+ end
26
+
27
+ def error
28
+ return unless error?
29
+ @error ||= Error.new(body[:error])
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,21 @@
1
+ module Direct::API::V5
2
+ class Response::Error
3
+ attr_reader :code
4
+ attr_reader :message
5
+ attr_reader :details
6
+
7
+ def initialize(error_data = {})
8
+ @code = error_data[:error_code]
9
+ @message = error_data[:error_string]
10
+ @details = error_data[:error_detail]
11
+ end
12
+
13
+ def to_h
14
+ {
15
+ code: code,
16
+ message: message,
17
+ details: details
18
+ }
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,34 @@
1
+ module Direct::API::V5
2
+ class Response::Units
3
+ def initialize(raw_value)
4
+ @raw_value = raw_value
5
+ end
6
+
7
+ def raw
8
+ @raw_value
9
+ end
10
+
11
+ # Spent units (by request)
12
+ def spent
13
+ parts[0]
14
+ end
15
+
16
+ # Available units (in current hour)
17
+ def available
18
+ parts[1]
19
+ end
20
+
21
+ # Units daily limit
22
+ def daily_limit
23
+ parts[2]
24
+ end
25
+
26
+ private
27
+
28
+ def parts
29
+ @parts ||= @raw_value.to_s.strip.split('/').map do |part|
30
+ part =~ /^\d+$/ ? part.to_i : nil
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,32 @@
1
+ module Direct::API::V5
2
+ class Service
3
+ def initialize(client:, service_name:)
4
+ @client = client
5
+ @service_name = service_name
6
+ end
7
+
8
+ # Direct API methods
9
+ def method_missing(method, *args)
10
+ params = args[0] || {}
11
+ if params.is_a?(Hash)
12
+ call_api_method(method, params)
13
+ else
14
+ super
15
+ end
16
+ end
17
+
18
+ protected
19
+
20
+ def call_api_method(method_name, params = {})
21
+ request = Request.new(
22
+ settings: @client.settings,
23
+ service_name: @service_name,
24
+ api_method: method_name,
25
+ params: params
26
+ )
27
+ body, headers = request.send
28
+
29
+ Response.new(body, headers)
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,19 @@
1
+ module Direct
2
+ module API
3
+ module V5
4
+ class Settings
5
+ attr_accessor :host
6
+ attr_accessor :language
7
+ attr_accessor :auth_token
8
+ attr_accessor :client_login
9
+
10
+ def initialize(settings = {})
11
+ @host = settings[:host] || DefaultSettings.host
12
+ @language = settings[:language] || DefaultSettings.language
13
+ @auth_token = settings[:auth_token] || DefaultSettings.auth_token
14
+ @client_login = settings[:client_login] || DefaultSettings.client_login
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -1,7 +1,7 @@
1
1
  module Direct
2
2
  module API
3
3
  module V5
4
- VERSION = '0.0.1'
4
+ VERSION = '0.1.0'.freeze
5
5
  end
6
6
  end
7
7
  end
@@ -0,0 +1,32 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'api error', type: :acceptance do
4
+ let(:api) { make_direct_api_client }
5
+
6
+ before do
7
+ request_body = {
8
+ method: 'get',
9
+ params: {}
10
+ }
11
+
12
+ response_body = {
13
+ error: {
14
+ error_code: 54,
15
+ error_string: 'No rights',
16
+ error_detail: 'No rights to indicated client'
17
+ }
18
+ }
19
+
20
+ stub_direct_api_request(:campaigns, request_body, response_body)
21
+ end
22
+
23
+ subject(:response) { api.campaigns.get }
24
+
25
+ it 'return error' do
26
+ expect(response.error?).to be_truthy
27
+
28
+ expect(response.error.code).to eq(54)
29
+ expect(response.error.message).to eq('No rights')
30
+ expect(response.error.details).to eq('No rights to indicated client')
31
+ end
32
+ end
@@ -0,0 +1,72 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'campaigns.get', type: :acceptance do
4
+ let(:api) { make_direct_api_client }
5
+
6
+ before do
7
+ request_body = {
8
+ method: 'get',
9
+ params: {
10
+ FieldNames: %w(Id Name State StartDate),
11
+ TextCampaignFieldNames: %w(CounterIds RelevantKeywords),
12
+ SelectionCriteria: {
13
+ Types: %w(TEXT_CAMPAIGN),
14
+ States: %w(ON SUSPENDED),
15
+ StatusesPayment: %w(ALLOWED)
16
+ }
17
+ }
18
+ }
19
+
20
+ response_body = {
21
+ result: {
22
+ Campaigns: [
23
+ { Id: 1, Name: 'Campaign 1', State: 'ON', StartDate: '2016-01-01' },
24
+ { Id: 2, Name: 'Campaign 2', State: 'SUSPENDED', StartDate: '2016-02-01' }
25
+ ]
26
+ }
27
+ }
28
+
29
+ stub_direct_api_request(:campaigns, request_body, response_body)
30
+ end
31
+
32
+ subject(:response) do
33
+ api.campaigns.get(
34
+ fields: [:id, :name, :state, :start_date],
35
+ text_campaign_fields: [:counter_ids, :relevant_keywords],
36
+ criteria: { types: %w(TEXT_CAMPAIGN), states: %w(ON SUSPENDED), statuses_payment: %w(ALLOWED) }
37
+ )
38
+ end
39
+
40
+ let(:campaigns) { response.result[:Campaigns] }
41
+ let(:campaign) { campaigns[0] }
42
+
43
+ it 'is not error' do
44
+ expect(response.error?).to be_falsey
45
+ end
46
+
47
+ it 'have request_id' do
48
+ expect(response.request_id).to eq(direct_api_request_id)
49
+ end
50
+
51
+ it 'have units' do
52
+ expect(response.units.raw).to eq(direct_api_units) # '10/20828/64000'
53
+
54
+ expect(response.units.spent).to eq(10)
55
+ expect(response.units.available).to eq(20_828)
56
+ expect(response.units.daily_limit).to eq(64_000)
57
+ end
58
+
59
+ it 'have campaigns' do
60
+ expect(campaigns.size).to eq(2)
61
+ end
62
+
63
+ it 'campaign contains data' do
64
+ expected = {
65
+ Id: 1,
66
+ Name: 'Campaign 1',
67
+ State: 'ON',
68
+ StartDate: '2016-01-01'
69
+ }
70
+ expect(campaign).to eq(expected)
71
+ end
72
+ end
@@ -0,0 +1,28 @@
1
+ require 'spec_helper'
2
+
3
+ describe Direct::API::V5::Client do
4
+ let(:client) { described_class.new }
5
+
6
+ describe '::new' do
7
+ let(:settings) { { param1: 'value1', param2: 'value2' } }
8
+ let(:settings_obj) { double('settings') }
9
+
10
+ subject { described_class.new(settings) }
11
+
12
+ it 'create Settings object' do
13
+ allow(Direct::API::V5::Settings).to receive(:new).with(settings).and_return(settings_obj)
14
+ expect(subject.settings).to eq(settings_obj)
15
+ end
16
+ end
17
+
18
+ describe '#service_name' do
19
+ let(:service_obj) { double('service_obj') }
20
+
21
+ it 'return service object' do
22
+ params = { client: client, service_name: :campaigns }
23
+ allow(Direct::API::V5::Service).to receive(:new).with(params).and_return(service_obj)
24
+
25
+ expect(client.campaigns).to eq(service_obj)
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,42 @@
1
+ require 'spec_helper'
2
+
3
+ describe Direct::API::V5::DefaultSettings do
4
+ describe 'default values' do
5
+ it { expect(described_class.host).to eq('api.direct.yandex.com') }
6
+ it { expect(described_class.language).to eq('en') }
7
+ it { expect(described_class.auth_token).to be_nil }
8
+ it { expect(described_class.client_login).to be_nil }
9
+ end
10
+
11
+ describe '::load_from_yml' do
12
+ let(:path) { File.join(File.dirname(__FILE__), '../../../fixtures/settings.yml') }
13
+
14
+ before { described_class.load_from_yml(path) }
15
+
16
+ it { expect(described_class.host).to eq('api-sandbox.direct.yandex.com') }
17
+ it { expect(described_class.language).to eq('ru') }
18
+ it { expect(described_class.auth_token).to eq('token_string') }
19
+ it { expect(described_class.client_login).to eq('login_string') }
20
+ end
21
+
22
+ describe '::configure' do
23
+ let(:host) { 'new_host.direct.yandex.com' }
24
+ let(:language) { 'uk' }
25
+ let(:auth_token) { 'custom_token' }
26
+ let(:client_login) { 'custom_client' }
27
+
28
+ before do
29
+ described_class.configure do |conf|
30
+ conf.host = host
31
+ conf.auth_token = auth_token
32
+ conf.client_login = client_login
33
+ conf.language = language
34
+ end
35
+ end
36
+
37
+ it { expect(described_class.host).to eq(host) }
38
+ it { expect(described_class.language).to eq(language) }
39
+ it { expect(described_class.auth_token).to eq(auth_token) }
40
+ it { expect(described_class.client_login).to eq(client_login) }
41
+ end
42
+ end
@@ -0,0 +1,39 @@
1
+ require 'spec_helper'
2
+
3
+ describe Direct::API::V5::ParamsBuilder do
4
+ let(:input_params) do
5
+ {
6
+ criteria: {
7
+ types: %w(TEXT_CAMPAIGN),
8
+ states: %w(ON OFF)
9
+ },
10
+ fields: [:id, :name, :state, :status, :status_payment],
11
+ text_campaign_fields: [:counter_ids],
12
+ page: {
13
+ limit: 100,
14
+ offset: 200
15
+ }
16
+ }
17
+ end
18
+
19
+ let(:output_params) do
20
+ {
21
+ SelectionCriteria: {
22
+ Types: %w(TEXT_CAMPAIGN),
23
+ States: %w(ON OFF)
24
+ },
25
+ FieldNames: [:Id, :Name, :State, :Status, :StatusPayment],
26
+ TextCampaignFieldNames: [:CounterIds],
27
+ Page: {
28
+ Limit: 100,
29
+ Offset: 200
30
+ }
31
+ }
32
+ end
33
+
34
+ subject { described_class.new(input_params).build }
35
+
36
+ it 'prepare params' do
37
+ is_expected.to eq(output_params)
38
+ end
39
+ end
@@ -0,0 +1,21 @@
1
+ require 'spec_helper'
2
+
3
+ describe Direct::API::V5::Refinements::Camelize do
4
+ using described_class
5
+
6
+ describe 'String#camelize' do
7
+ it 'camelize string' do
8
+ expect('consider_working_weekends'.camelize).to eq('ConsiderWorkingWeekends')
9
+ end
10
+
11
+ it 'return self if already camelized' do
12
+ expect('ConsiderWorkingWeekends'.camelize).to eq('ConsiderWorkingWeekends')
13
+ end
14
+ end
15
+
16
+ describe 'Symbol#camelize' do
17
+ it 'camelize symbol' do
18
+ expect(:consider_working_weekends.camelize).to eq(:ConsiderWorkingWeekends)
19
+ end
20
+ end
21
+ end