soapy_bing 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.env +6 -0
- data/.gitignore +6 -0
- data/.rspec +2 -0
- data/.ruby-version +1 -0
- data/Gemfile +6 -0
- data/README.md +63 -0
- data/Rakefile +7 -0
- data/circle.yml +6 -0
- data/lib/soapy_bing/account.rb +12 -0
- data/lib/soapy_bing/ads/reports/base.rb +83 -0
- data/lib/soapy_bing/ads/reports/campaign_performance_report.rb +23 -0
- data/lib/soapy_bing/ads/reports/parsers/csv_parser.rb +45 -0
- data/lib/soapy_bing/ads/reports/parsers.rb +1 -0
- data/lib/soapy_bing/ads/reports.rb +3 -0
- data/lib/soapy_bing/ads.rb +22 -0
- data/lib/soapy_bing/helpers/class_name.rb +9 -0
- data/lib/soapy_bing/helpers.rb +1 -0
- data/lib/soapy_bing/oauth_credentials.rb +43 -0
- data/lib/soapy_bing/param_guard.rb +26 -0
- data/lib/soapy_bing/soap/request/base.rb +37 -0
- data/lib/soapy_bing/soap/request/poll_generate_report_request.rb +33 -0
- data/lib/soapy_bing/soap/request/submit_generate_report_request.rb +16 -0
- data/lib/soapy_bing/soap/request.rb +3 -0
- data/lib/soapy_bing/soap/response/base.rb +16 -0
- data/lib/soapy_bing/soap/response/payload.rb +15 -0
- data/lib/soapy_bing/soap/response/poll_generate_report_response.rb +13 -0
- data/lib/soapy_bing/soap/response/report_status.rb +33 -0
- data/lib/soapy_bing/soap/response/submit_generate_report_response.rb +11 -0
- data/lib/soapy_bing/soap/response.rb +5 -0
- data/lib/soapy_bing/soap/template_renderer.rb +21 -0
- data/lib/soapy_bing/soap/templates/poll_generate_report.erb.xml +18 -0
- data/lib/soapy_bing/soap/templates/submit_generate_report.erb.xml +46 -0
- data/lib/soapy_bing/soap.rb +3 -0
- data/lib/soapy_bing/version.rb +3 -0
- data/lib/soapy_bing.rb +11 -0
- data/lib/tasks/console.rake +5 -0
- data/lib/tasks/coverage.rake +6 -0
- data/lib/tasks/spec.rake +4 -0
- data/soapy_bing.gemspec +34 -0
- data/spec/fixtures/reports/campaign_performance_report.csv +37 -0
- data/spec/fixtures/reports/campaign_performance_report.json +146 -0
- data/spec/fixtures/soap_templates/simple.erb.xml +2 -0
- data/spec/fixtures/vcr_cassettes/campaign_performance_report/with_pending_status.yml +168 -0
- data/spec/fixtures/vcr_cassettes/campaign_performance_report/with_successful_status.yml +284 -0
- data/spec/fixtures/vcr_cassettes/oauth_credentials/access_token/with_successful_status.yml +42 -0
- data/spec/integration/soapy_bing/ads/reports/campaign_performance_report_spec.rb +39 -0
- data/spec/integration/soapy_bing/oauth_credentials_spec.rb +10 -0
- data/spec/simplecov_setup.rb +9 -0
- data/spec/soapy_bing/account_spec.rb +80 -0
- data/spec/soapy_bing/ads/reports/campaign_performance_report_spec.rb +41 -0
- data/spec/soapy_bing/ads/reports/parsers/csv_parser_spec.rb +31 -0
- data/spec/soapy_bing/ads_spec.rb +32 -0
- data/spec/soapy_bing/helpers/class_name_spec.rb +14 -0
- data/spec/soapy_bing/oauth_credentials_spec.rb +108 -0
- data/spec/soapy_bing/param_guard_spec.rb +43 -0
- data/spec/soapy_bing/soap/request/base_spec.rb +54 -0
- data/spec/soapy_bing/soap/request/poll_generate_report_request_spec.rb +59 -0
- data/spec/soapy_bing/soap/response/base_spec.rb +12 -0
- data/spec/soapy_bing/soap/response/payload_spec.rb +25 -0
- data/spec/soapy_bing/soap/response/poll_generate_report_response_spec.rb +25 -0
- data/spec/soapy_bing/soap/response/report_status_spec.rb +91 -0
- data/spec/soapy_bing/soap/response/submit_generate_report_response_spec.rb +19 -0
- data/spec/soapy_bing/soap/template_renderer_spec.rb +24 -0
- data/spec/spec_helper.rb +32 -0
- data/spec/support/dotenv.rb +3 -0
- data/spec/support/vcr.rb +101 -0
- metadata +305 -0
@@ -0,0 +1,108 @@
|
|
1
|
+
RSpec.describe SoapyBing::OauthCredentials do
|
2
|
+
describe '#initialize' do
|
3
|
+
subject { described_class.new(credentials) }
|
4
|
+
|
5
|
+
context 'when oauth credentials passed explicitly' do
|
6
|
+
let(:credentials) { { client_id: 'foo', client_secret: 'bar', refresh_token: 'baz' } }
|
7
|
+
before do
|
8
|
+
allow(ENV).to receive(:[]).with('BING_ADS_OAUTH_CLIENT_ID').and_return('foo_env')
|
9
|
+
allow(ENV).to receive(:[]).with('BING_ADS_OAUTH_CLIENT_SECRET').and_return('bar_env')
|
10
|
+
allow(ENV).to receive(:[]).with('BING_ADS_OAUTH_REFRESH_TOKEN').and_return('baz_env')
|
11
|
+
allow(ENV).to receive(:[]).with('BING_ADS_OAUTH_TOKEN_URL')
|
12
|
+
end
|
13
|
+
|
14
|
+
it '#client_id is set' do
|
15
|
+
expect(subject.client_id).to eq 'foo'
|
16
|
+
end
|
17
|
+
|
18
|
+
it '#client_secret is set' do
|
19
|
+
expect(subject.client_secret).to eq 'bar'
|
20
|
+
end
|
21
|
+
|
22
|
+
it '#refresh_token is set' do
|
23
|
+
expect(subject.refresh_token).to eq 'baz'
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
context 'when oauth credentials passed via Enviromenment variables' do
|
28
|
+
let(:credentials) { {} }
|
29
|
+
before do
|
30
|
+
allow(ENV).to receive(:[]).with('BING_ADS_OAUTH_CLIENT_ID').and_return('foo_env')
|
31
|
+
allow(ENV).to receive(:[]).with('BING_ADS_OAUTH_CLIENT_SECRET').and_return('bar_env')
|
32
|
+
allow(ENV).to receive(:[]).with('BING_ADS_OAUTH_REFRESH_TOKEN').and_return('baz_env')
|
33
|
+
allow(ENV).to receive(:[]).with('BING_ADS_OAUTH_TOKEN_URL')
|
34
|
+
end
|
35
|
+
|
36
|
+
it '#client_id is set' do
|
37
|
+
expect(subject.client_id).to eq 'foo_env'
|
38
|
+
end
|
39
|
+
|
40
|
+
it '#client_secret is set' do
|
41
|
+
expect(subject.client_secret).to eq 'bar_env'
|
42
|
+
end
|
43
|
+
|
44
|
+
it '#refresh_token is set' do
|
45
|
+
expect(subject.refresh_token).to eq 'baz_env'
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
context 'when no oauth credentials passed' do
|
50
|
+
let(:credentials) { { client_id: 'foo', client_secret: 'bar', refresh_token: 'baz' } }
|
51
|
+
before do
|
52
|
+
%w( BING_ADS_OAUTH_CLIENT_ID
|
53
|
+
BING_ADS_OAUTH_CLIENT_SECRET
|
54
|
+
BING_ADS_OAUTH_REFRESH_TOKEN ).each do |var|
|
55
|
+
allow(ENV).to receive(:[]).with(var).and_return(nil)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'throws exception on missing :client_id' do
|
60
|
+
credentials.delete(:client_id)
|
61
|
+
expect { subject }.to raise_error SoapyBing::ParamGuard::ParamRequiredError,
|
62
|
+
'client_id have to be passed explicitly or via ENV[\'BING_ADS_OAUTH_CLIENT_ID\']'
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'throws exception on missing :client_secret' do
|
66
|
+
credentials.delete(:client_secret)
|
67
|
+
expect { subject }.to raise_error SoapyBing::ParamGuard::ParamRequiredError,
|
68
|
+
'client_secret have to be passed explicitly or via ENV[\'BING_ADS_OAUTH_CLIENT_SECRET\']'
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'throws exception on missing :refresh_token' do
|
72
|
+
credentials.delete(:refresh_token)
|
73
|
+
expect { subject }.to raise_error SoapyBing::ParamGuard::ParamRequiredError,
|
74
|
+
'refresh_token have to be passed explicitly or via ENV[\'BING_ADS_OAUTH_REFRESH_TOKEN\']'
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
describe '#access_token' do
|
80
|
+
let(:credentials) { { client_id: 'foo', client_secret: 'bar', refresh_token: 'baz' } }
|
81
|
+
let(:response) { double(:response) }
|
82
|
+
|
83
|
+
before do
|
84
|
+
expect(response).to receive(:code).once.and_return(status_code)
|
85
|
+
expect(HTTParty).to receive(:post).once.and_return(response)
|
86
|
+
end
|
87
|
+
|
88
|
+
context 'when there is good response' do
|
89
|
+
let(:status_code) { 200 }
|
90
|
+
|
91
|
+
before { expect(response).to receive(:[]).once.with('access_token').and_return('my-token') }
|
92
|
+
|
93
|
+
it 'memoizes http request response' do
|
94
|
+
2.times { expect(subject.access_token).to eq 'my-token' }
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
context 'when there is bad response' do
|
99
|
+
let(:status_code) { 401 }
|
100
|
+
|
101
|
+
it 'throws exception in case of bad status code' do
|
102
|
+
expect { subject.access_token }.to raise_error(
|
103
|
+
SoapyBing::OauthCredentials::TokenRefreshError
|
104
|
+
)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
RSpec.describe SoapyBing::ParamGuard do
|
2
|
+
describe '#require!' do
|
3
|
+
let(:param_guard) { described_class.new(options, env_namespace: 'MY') }
|
4
|
+
subject { param_guard.require!(:foo) }
|
5
|
+
|
6
|
+
context 'when option is empty' do
|
7
|
+
let(:options) { {} }
|
8
|
+
|
9
|
+
context 'and environment variable is empty too' do
|
10
|
+
it 'thows exception' do
|
11
|
+
expect { subject }.to raise_exception SoapyBing::ParamGuard::ParamRequiredError,
|
12
|
+
'foo have to be passed explicitly or via ENV[\'MY_FOO\']'
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
context 'but environment variable is present' do
|
17
|
+
before { allow(ENV).to receive(:[]).with('MY_FOO').and_return('bar_env') }
|
18
|
+
|
19
|
+
it 'returns environment variable value' do
|
20
|
+
expect(subject).to eq 'bar_env'
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
context 'when option is present' do
|
26
|
+
let(:options) { { foo: 'bar' } }
|
27
|
+
|
28
|
+
context 'and environment variable is present too' do
|
29
|
+
before { allow(ENV).to receive(:[]).with('MY_FOO').and_return('bar_env') }
|
30
|
+
|
31
|
+
it 'returns option value' do
|
32
|
+
expect(subject).to eq 'bar'
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
context 'but environment variable is empty' do
|
37
|
+
it 'returns option value' do
|
38
|
+
expect(subject).to eq 'bar'
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
RSpec.describe SoapyBing::Soap::Request::Base do
|
2
|
+
let(:req_context) { { foo: 'Bar' } }
|
3
|
+
subject { described_class.new(context: req_context) }
|
4
|
+
|
5
|
+
before { stub_const('MyCustomRequest', Class.new(described_class) {}) }
|
6
|
+
let(:my_custom_request) { MyCustomRequest.new(context: req_context) }
|
7
|
+
|
8
|
+
describe '#context' do
|
9
|
+
it 'keeps initialized value' do
|
10
|
+
expect(subject.context).to eq req_context
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe '#post' do
|
15
|
+
let(:body) { 'my body' }
|
16
|
+
let(:headers) { { 'My' => 'Header' } }
|
17
|
+
|
18
|
+
it 'delegates post request to HTTParty' do
|
19
|
+
expect(HTTParty).to receive(:post).with(
|
20
|
+
'http://example.com',
|
21
|
+
hash_including(body: body, headers: hash_including(headers))
|
22
|
+
)
|
23
|
+
subject.post('http://example.com', body: body, headers: headers)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe '#default_body' do
|
28
|
+
subject { my_custom_request.default_body }
|
29
|
+
it 'renders request body template' do
|
30
|
+
renderer = double('Renderer')
|
31
|
+
expect(SoapyBing::Soap::TemplateRenderer).to receive(:new)
|
32
|
+
.with(hash_including(req_context)).and_return(renderer)
|
33
|
+
expect(renderer).to receive(:render).with('my_custom')
|
34
|
+
subject
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe '#default_headers' do
|
39
|
+
subject { my_custom_request.default_headers }
|
40
|
+
it 'contains soap action' do
|
41
|
+
expect(subject).to include('SOAPAction' => 'MyCustom')
|
42
|
+
end
|
43
|
+
it 'contains default headers' do
|
44
|
+
expect(subject).to include(described_class::DEFAULT_HTTP_HEADERS)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe '#action_name' do
|
49
|
+
subject { my_custom_request.action_name }
|
50
|
+
it 'resolves request\'s class soap action' do
|
51
|
+
expect(subject).to eq 'MyCustom'
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
RSpec.describe SoapyBing::Soap::Request::PollGenerateReportRequest do
|
2
|
+
describe '#perform' do
|
3
|
+
let(:response_body) do
|
4
|
+
{
|
5
|
+
'Envelope' => {
|
6
|
+
'Body' => {
|
7
|
+
'PollGenerateReportResponse' => {
|
8
|
+
'ReportRequestStatus' => {
|
9
|
+
'Status' => nil,
|
10
|
+
'ReportDownloadUrl' => 'http://example.com'
|
11
|
+
}
|
12
|
+
}
|
13
|
+
}
|
14
|
+
}
|
15
|
+
}
|
16
|
+
end
|
17
|
+
|
18
|
+
let(:pending_response_body) do
|
19
|
+
response_body['Envelope']['Body']['PollGenerateReportResponse']['ReportRequestStatus']
|
20
|
+
.merge!('Status' => 'Pending')
|
21
|
+
response_body
|
22
|
+
end
|
23
|
+
let(:successful_response_body) do
|
24
|
+
response_body['Envelope']['Body']['PollGenerateReportResponse']['ReportRequestStatus']
|
25
|
+
.merge!('Status' => 'Success')
|
26
|
+
response_body
|
27
|
+
end
|
28
|
+
|
29
|
+
before do
|
30
|
+
call_count = 0
|
31
|
+
allow(HTTParty).to receive(:post) do
|
32
|
+
call_count += 1
|
33
|
+
call_count == 3 ? successful_response_body : pending_response_body
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
subject do
|
38
|
+
described_class
|
39
|
+
.new(
|
40
|
+
context: {
|
41
|
+
oauth: double(:oauth_credentials).as_null_object,
|
42
|
+
account: double(:account).as_null_object
|
43
|
+
}
|
44
|
+
)
|
45
|
+
.perform
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'polls until successful response' do
|
49
|
+
expect(HTTParty).to receive(:post).exactly(3).times
|
50
|
+
expect(subject.payload).to eq 'http://example.com'
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'throws PollingTimeoutError when exceeded polling tries' do
|
54
|
+
stub_const('SoapyBing::Soap::Request::PollGenerateReportRequest::POLLING_TRIES', 1)
|
55
|
+
expect(HTTParty).to receive(:post).once
|
56
|
+
expect { subject }.to raise_error described_class::PollingTimeoutError
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
RSpec.describe SoapyBing::Soap::Response::Base do
|
2
|
+
before { stub_const('MyCustomResponse', Class.new(described_class) {}) }
|
3
|
+
let(:response_body) { 'Some response body' }
|
4
|
+
let(:my_custom_response) { MyCustomResponse.new(response_body) }
|
5
|
+
|
6
|
+
describe '#body' do
|
7
|
+
subject { my_custom_response.body }
|
8
|
+
it 'keeps initialized value' do
|
9
|
+
expect(subject).to eq response_body
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
RSpec.describe SoapyBing::Soap::Response::Payload do
|
2
|
+
before do
|
3
|
+
stub_const(
|
4
|
+
'MyCustomResponse',
|
5
|
+
Class
|
6
|
+
.new
|
7
|
+
.include(described_class)
|
8
|
+
)
|
9
|
+
end
|
10
|
+
|
11
|
+
subject { MyCustomResponse.new }
|
12
|
+
|
13
|
+
describe '#payload' do
|
14
|
+
it 'memoize #extract_payload value' do
|
15
|
+
expect_any_instance_of(MyCustomResponse).to receive(:extract_payload).once.and_return(true)
|
16
|
+
2.times { subject.payload }
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe '#extract_payload' do
|
21
|
+
it 'throws NotImplementedError' do
|
22
|
+
expect { subject.extract_payload }.to raise_error NotImplementedError
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
RSpec.describe SoapyBing::Soap::Response::PollGenerateReportResponse do
|
2
|
+
let(:url) { 'http://my-site.com' }
|
3
|
+
let(:response_hash) do
|
4
|
+
{
|
5
|
+
'Envelope' => {
|
6
|
+
'Body' => {
|
7
|
+
'PollGenerateReportResponse' => {
|
8
|
+
'ReportRequestStatus' => {
|
9
|
+
'ReportDownloadUrl' => url
|
10
|
+
}
|
11
|
+
}
|
12
|
+
}
|
13
|
+
}
|
14
|
+
}
|
15
|
+
end
|
16
|
+
let(:subject) { described_class.new(response_hash) }
|
17
|
+
|
18
|
+
it 'includes ReportStatus' do
|
19
|
+
expect(described_class.ancestors).to include SoapyBing::Soap::Response::ReportStatus
|
20
|
+
end
|
21
|
+
|
22
|
+
it '#extract_payload returns download url' do
|
23
|
+
expect(subject.extract_payload).to eq url
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
RSpec.describe SoapyBing::Soap::Response::ReportStatus do
|
2
|
+
before do
|
3
|
+
stub_const(
|
4
|
+
'MyCustomResponse',
|
5
|
+
Class
|
6
|
+
.new(SoapyBing::Soap::Response::Base)
|
7
|
+
.include(described_class)
|
8
|
+
)
|
9
|
+
end
|
10
|
+
let(:response_hash) do
|
11
|
+
{
|
12
|
+
'Envelope' => {
|
13
|
+
'Body' => {
|
14
|
+
'MyCustomResponse' => {
|
15
|
+
'ReportRequestStatus' => {
|
16
|
+
'Status' => nil
|
17
|
+
}
|
18
|
+
}
|
19
|
+
}
|
20
|
+
}
|
21
|
+
}
|
22
|
+
end
|
23
|
+
let(:subject) { MyCustomResponse.new(response_hash) }
|
24
|
+
|
25
|
+
describe 'status' do
|
26
|
+
before do
|
27
|
+
response_hash['Envelope']['Body']['MyCustomResponse']['ReportRequestStatus']
|
28
|
+
.merge!('Status' => status)
|
29
|
+
end
|
30
|
+
|
31
|
+
context 'when error' do
|
32
|
+
let(:status) { 'Error' }
|
33
|
+
|
34
|
+
it '#status is Error' do
|
35
|
+
expect(subject.status).to eq status
|
36
|
+
end
|
37
|
+
|
38
|
+
it '#error? is false' do
|
39
|
+
expect(subject).to be_error
|
40
|
+
end
|
41
|
+
|
42
|
+
it '#success? is false' do
|
43
|
+
expect(subject).not_to be_success
|
44
|
+
end
|
45
|
+
|
46
|
+
it '#pending? is false' do
|
47
|
+
expect(subject).not_to be_pending
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
context 'when success' do
|
52
|
+
let(:status) { 'Success' }
|
53
|
+
|
54
|
+
it '#status is Success' do
|
55
|
+
expect(subject.status).to eq status
|
56
|
+
end
|
57
|
+
|
58
|
+
it '#error? is false' do
|
59
|
+
expect(subject).not_to be_error
|
60
|
+
end
|
61
|
+
|
62
|
+
it '#success? is true' do
|
63
|
+
expect(subject).to be_success
|
64
|
+
end
|
65
|
+
|
66
|
+
it '#pending? is false' do
|
67
|
+
expect(subject).not_to be_pending
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
context 'when pending' do
|
72
|
+
let(:status) { 'Pending' }
|
73
|
+
|
74
|
+
it '#status is Pending' do
|
75
|
+
expect(subject.status).to eq status
|
76
|
+
end
|
77
|
+
|
78
|
+
it '#error? is false' do
|
79
|
+
expect(subject).not_to be_error
|
80
|
+
end
|
81
|
+
|
82
|
+
it '#success? is false' do
|
83
|
+
expect(subject).not_to be_success
|
84
|
+
end
|
85
|
+
|
86
|
+
it '#pending? is true' do
|
87
|
+
expect(subject).to be_pending
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
RSpec.describe SoapyBing::Soap::Response::SubmitGenerateReportResponse do
|
2
|
+
let(:request_id) { 'foobarbazqux' }
|
3
|
+
let(:response_hash) do
|
4
|
+
{
|
5
|
+
'Envelope' => {
|
6
|
+
'Body' => {
|
7
|
+
'SubmitGenerateReportResponse' => {
|
8
|
+
'ReportRequestId' => request_id
|
9
|
+
}
|
10
|
+
}
|
11
|
+
}
|
12
|
+
}
|
13
|
+
end
|
14
|
+
let(:subject) { described_class.new(response_hash) }
|
15
|
+
|
16
|
+
it '#extract_payload returns request id' do
|
17
|
+
expect(subject.extract_payload).to eq request_id
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
RSpec.describe SoapyBing::Soap::TemplateRenderer do
|
2
|
+
describe '::TEMPLATE_PATH' do
|
3
|
+
let(:files) { Dir.glob(File.join(described_class::TEMPLATE_PATH, '*.erb.xml')) }
|
4
|
+
|
5
|
+
it 'points to folder with *.erb.xml files' do
|
6
|
+
expect(files.size).to be > 1
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
describe '#render' do
|
11
|
+
let(:renderer) { described_class.new(greeting: 'Hello', target: 'World', provocation: '< &') }
|
12
|
+
|
13
|
+
before do
|
14
|
+
stub_const(
|
15
|
+
"#{described_class}::TEMPLATE_PATH",
|
16
|
+
File.join('spec', 'fixtures', 'soap_templates')
|
17
|
+
)
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'returns text with interpolated variables' do
|
21
|
+
expect(renderer.render(:simple)).to eq "Hello, World!\n< &\n"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'simplecov_setup'
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
Bundler.require(:default, :development)
|
5
|
+
|
6
|
+
RSpec.configure do |config|
|
7
|
+
config.expect_with :rspec do |expectations|
|
8
|
+
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
|
9
|
+
end
|
10
|
+
|
11
|
+
config.mock_with :rspec do |mocks|
|
12
|
+
mocks.verify_partial_doubles = true
|
13
|
+
end
|
14
|
+
|
15
|
+
config.filter_run :focus
|
16
|
+
config.run_all_when_everything_filtered = true
|
17
|
+
|
18
|
+
config.disable_monkey_patching!
|
19
|
+
config.expose_dsl_globally = false
|
20
|
+
|
21
|
+
if config.files_to_run.one?
|
22
|
+
config.default_formatter = 'doc'
|
23
|
+
end
|
24
|
+
|
25
|
+
config.order = :random
|
26
|
+
|
27
|
+
Kernel.srand config.seed
|
28
|
+
end
|
29
|
+
|
30
|
+
Dir['./spec/support/**/*.rb'].sort.each { |f| require f }
|
31
|
+
|
32
|
+
require 'soapy_bing'
|
data/spec/support/vcr.rb
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
require 'cgi'
|
2
|
+
require 'vcr'
|
3
|
+
require 'active_support/core_ext/hash/conversions'
|
4
|
+
|
5
|
+
VCR.configure do |c|
|
6
|
+
c.configure_rspec_metadata!
|
7
|
+
c.cassette_library_dir = 'spec/fixtures/vcr_cassettes'
|
8
|
+
c.hook_into :webmock
|
9
|
+
c.default_cassette_options = { match_requests_on: %i(method uri body) }
|
10
|
+
|
11
|
+
c.filter_sensitive_data('bing-ads-oauth-client-id') { ENV['BING_ADS_OAUTH_CLIENT_ID'] }
|
12
|
+
c.filter_sensitive_data('bing-ads-oauth-client-secret') { ENV['BING_ADS_OAUTH_CLIENT_SECRET'] }
|
13
|
+
c.filter_sensitive_data('bing-ads-oauth-refresh-token') do
|
14
|
+
CGI.escape(ENV['BING_ADS_OAUTH_REFRESH_TOKEN'])
|
15
|
+
end
|
16
|
+
c.filter_sensitive_data('bing-ads-oauth-refresh-token') do |interaction|
|
17
|
+
if interaction.response.headers['Content-Type'].first == 'application/json'
|
18
|
+
JSON.parse(interaction.response.body)['refresh_token']
|
19
|
+
end
|
20
|
+
end
|
21
|
+
c.filter_sensitive_data('bing-ads-oauth-authentication-token') do |interaction|
|
22
|
+
if interaction.response.headers['Content-Type'].first == 'application/json'
|
23
|
+
JSON.parse(interaction.response.body)['access_token']
|
24
|
+
end
|
25
|
+
end
|
26
|
+
c.filter_sensitive_data('bing-ads-oauth-user-id') do |interaction|
|
27
|
+
if interaction.response.headers['Content-Type'].first == 'application/json'
|
28
|
+
JSON.parse(interaction.response.body)['user_id']
|
29
|
+
end
|
30
|
+
end
|
31
|
+
c.filter_sensitive_data('bing-ads-developer-token') { ENV['BING_ADS_DEVELOPER_TOKEN'] }
|
32
|
+
c.filter_sensitive_data('bing-ads-account-id') { ENV['BING_ADS_ACCOUNT_ID'] }
|
33
|
+
c.filter_sensitive_data('bing-ads-customer-id') { ENV['BING_ADS_CUSTOMER_ID'] }
|
34
|
+
|
35
|
+
c.filter_sensitive_data('bing-ads-report-tracking-id') do |interaction|
|
36
|
+
if interaction.response.headers['Content-Type'].first == 'text/xml; charset=utf-8'
|
37
|
+
Hash.from_xml(interaction.response.body)['Envelope']['Header']['TrackingId']
|
38
|
+
end
|
39
|
+
end
|
40
|
+
c.filter_sensitive_data('bing-ads-report-request-id') do |interaction|
|
41
|
+
if interaction.response.headers['Content-Type'].first == 'text/xml; charset=utf-8'
|
42
|
+
body = Hash.from_xml(interaction.response.body)['Envelope']['Body']
|
43
|
+
if body['SubmitGenerateReportResponse']
|
44
|
+
body['SubmitGenerateReportResponse']['ReportRequestId']
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
c.filter_sensitive_data('bing-ads-report-request-id') do |interaction|
|
49
|
+
if interaction.response.headers['Content-Type'].first == 'text/xml; charset=utf-8'
|
50
|
+
body = Hash.from_xml(interaction.request.body)['Envelope']['Body']
|
51
|
+
if body['PollGenerateReportRequest']
|
52
|
+
body['PollGenerateReportRequest']['ReportRequestId']
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
c.filter_sensitive_data('bing-ads-oauth-authentication-token') do |interaction|
|
57
|
+
if interaction.response.headers['Content-Type'].first == 'text/xml; charset=utf-8'
|
58
|
+
Hash.from_xml(interaction.request.body)['Envelope']['Header']['AuthenticationToken']
|
59
|
+
end
|
60
|
+
end
|
61
|
+
c.filter_sensitive_data('bing-ads-report-download-id') do |interaction|
|
62
|
+
if interaction.request.uri =~ %r{https://(.*)\?q=(.*)}
|
63
|
+
Regexp.last_match(2)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
c.filter_sensitive_data('bing-ads-report-download-id') do |interaction|
|
67
|
+
if interaction.response.headers['Content-Type'].first == 'text/xml; charset=utf-8'
|
68
|
+
response = Hash.from_xml(interaction.response.body)
|
69
|
+
report = response['Envelope']['Body']['PollGenerateReportResponse']
|
70
|
+
if report && report ['ReportRequestStatus']
|
71
|
+
if report['ReportRequestStatus']['ReportDownloadUrl'] =~ %r{https://(.*)\?q=(.*)}
|
72
|
+
Regexp.last_match(2)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
c.before_record do |interaction|
|
79
|
+
# auto-generate report payload fixtures
|
80
|
+
# spec/fixtures/reports/campaign_performance_report.json
|
81
|
+
# spec/fixtures/reports/campaing_performance_report.csv
|
82
|
+
if interaction.response.headers['Content-Type'].first == 'application/x-zip-compressed'
|
83
|
+
# refactor zip into module
|
84
|
+
csv_data = Zip::InputStream.open(StringIO.new(interaction.response.body)) do |archive_io|
|
85
|
+
file_io = archive_io.get_next_entry.get_input_stream
|
86
|
+
file_io.read
|
87
|
+
end
|
88
|
+
|
89
|
+
fixtures_dir = File.join('spec', 'fixtures', 'reports')
|
90
|
+
File.open(File.join(fixtures_dir, 'campaign_performance_report.json'), 'wb') do |file|
|
91
|
+
parser = SoapyBing::Ads::Reports::Parsers::CSVParser.new(csv_data)
|
92
|
+
file.write(JSON.pretty_generate(parser.rows))
|
93
|
+
end
|
94
|
+
File.open(File.join(fixtures_dir, 'campaign_performance_report.csv'), 'wb') do |file|
|
95
|
+
file.write(csv_data)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
c.ignore_hosts 'codeclimate.com' # allow codeclimate-test-reporter to phone home
|
101
|
+
end
|