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.
- checksums.yaml +4 -4
- data/.gitignore +2 -1
- data/.rspec +2 -0
- data/.rubocop.yml +19 -0
- data/.travis.yml +17 -0
- data/Gemfile +2 -0
- data/README.md +127 -3
- data/direct-api-v5.gemspec +11 -5
- data/lib/direct/api/v5.rb +48 -3
- data/lib/direct/api/v5/client.rb +18 -0
- data/lib/direct/api/v5/default_settings.rb +42 -0
- data/lib/direct/api/v5/errors.rb +10 -0
- data/lib/direct/api/v5/params_builder.rb +46 -0
- data/lib/direct/api/v5/refinements/camelize.rb +22 -0
- data/lib/direct/api/v5/request.rb +66 -0
- data/lib/direct/api/v5/response.rb +32 -0
- data/lib/direct/api/v5/response/error.rb +21 -0
- data/lib/direct/api/v5/response/units.rb +34 -0
- data/lib/direct/api/v5/service.rb +32 -0
- data/lib/direct/api/v5/settings.rb +19 -0
- data/lib/direct/api/v5/version.rb +1 -1
- data/spec/acceptance/error_spec.rb +32 -0
- data/spec/acceptance/get_campaigns_spec.rb +72 -0
- data/spec/direct/api/v5/client_spec.rb +28 -0
- data/spec/direct/api/v5/default_settings_spec.rb +42 -0
- data/spec/direct/api/v5/params_builder_spec.rb +39 -0
- data/spec/direct/api/v5/refinements/camelize_spec.rb +21 -0
- data/spec/direct/api/v5/request_spec.rb +60 -0
- data/spec/direct/api/v5/response/error_spec.rb +30 -0
- data/spec/direct/api/v5/response/units_spec.rb +31 -0
- data/spec/direct/api/v5/response_spec.rb +66 -0
- data/spec/direct/api/v5/settings_spec.rb +42 -0
- data/spec/direct/api/v5_spec.rb +37 -0
- data/spec/fixtures/settings.yml +5 -0
- data/spec/shared/direct_api_helper.rb +66 -0
- data/spec/spec_helper.rb +25 -0
- data/spec/support/webmock.rb +3 -0
- 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
|
@@ -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
|