soapy_bing 0.0.1
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 +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
|