direct-api-v5 0.0.1 → 0.1.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 (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