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.
- 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
|