soapy_bing 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. checksums.yaml +7 -0
  2. data/.env +6 -0
  3. data/.gitignore +6 -0
  4. data/.rspec +2 -0
  5. data/.ruby-version +1 -0
  6. data/Gemfile +6 -0
  7. data/README.md +63 -0
  8. data/Rakefile +7 -0
  9. data/circle.yml +6 -0
  10. data/lib/soapy_bing/account.rb +12 -0
  11. data/lib/soapy_bing/ads/reports/base.rb +83 -0
  12. data/lib/soapy_bing/ads/reports/campaign_performance_report.rb +23 -0
  13. data/lib/soapy_bing/ads/reports/parsers/csv_parser.rb +45 -0
  14. data/lib/soapy_bing/ads/reports/parsers.rb +1 -0
  15. data/lib/soapy_bing/ads/reports.rb +3 -0
  16. data/lib/soapy_bing/ads.rb +22 -0
  17. data/lib/soapy_bing/helpers/class_name.rb +9 -0
  18. data/lib/soapy_bing/helpers.rb +1 -0
  19. data/lib/soapy_bing/oauth_credentials.rb +43 -0
  20. data/lib/soapy_bing/param_guard.rb +26 -0
  21. data/lib/soapy_bing/soap/request/base.rb +37 -0
  22. data/lib/soapy_bing/soap/request/poll_generate_report_request.rb +33 -0
  23. data/lib/soapy_bing/soap/request/submit_generate_report_request.rb +16 -0
  24. data/lib/soapy_bing/soap/request.rb +3 -0
  25. data/lib/soapy_bing/soap/response/base.rb +16 -0
  26. data/lib/soapy_bing/soap/response/payload.rb +15 -0
  27. data/lib/soapy_bing/soap/response/poll_generate_report_response.rb +13 -0
  28. data/lib/soapy_bing/soap/response/report_status.rb +33 -0
  29. data/lib/soapy_bing/soap/response/submit_generate_report_response.rb +11 -0
  30. data/lib/soapy_bing/soap/response.rb +5 -0
  31. data/lib/soapy_bing/soap/template_renderer.rb +21 -0
  32. data/lib/soapy_bing/soap/templates/poll_generate_report.erb.xml +18 -0
  33. data/lib/soapy_bing/soap/templates/submit_generate_report.erb.xml +46 -0
  34. data/lib/soapy_bing/soap.rb +3 -0
  35. data/lib/soapy_bing/version.rb +3 -0
  36. data/lib/soapy_bing.rb +11 -0
  37. data/lib/tasks/console.rake +5 -0
  38. data/lib/tasks/coverage.rake +6 -0
  39. data/lib/tasks/spec.rake +4 -0
  40. data/soapy_bing.gemspec +34 -0
  41. data/spec/fixtures/reports/campaign_performance_report.csv +37 -0
  42. data/spec/fixtures/reports/campaign_performance_report.json +146 -0
  43. data/spec/fixtures/soap_templates/simple.erb.xml +2 -0
  44. data/spec/fixtures/vcr_cassettes/campaign_performance_report/with_pending_status.yml +168 -0
  45. data/spec/fixtures/vcr_cassettes/campaign_performance_report/with_successful_status.yml +284 -0
  46. data/spec/fixtures/vcr_cassettes/oauth_credentials/access_token/with_successful_status.yml +42 -0
  47. data/spec/integration/soapy_bing/ads/reports/campaign_performance_report_spec.rb +39 -0
  48. data/spec/integration/soapy_bing/oauth_credentials_spec.rb +10 -0
  49. data/spec/simplecov_setup.rb +9 -0
  50. data/spec/soapy_bing/account_spec.rb +80 -0
  51. data/spec/soapy_bing/ads/reports/campaign_performance_report_spec.rb +41 -0
  52. data/spec/soapy_bing/ads/reports/parsers/csv_parser_spec.rb +31 -0
  53. data/spec/soapy_bing/ads_spec.rb +32 -0
  54. data/spec/soapy_bing/helpers/class_name_spec.rb +14 -0
  55. data/spec/soapy_bing/oauth_credentials_spec.rb +108 -0
  56. data/spec/soapy_bing/param_guard_spec.rb +43 -0
  57. data/spec/soapy_bing/soap/request/base_spec.rb +54 -0
  58. data/spec/soapy_bing/soap/request/poll_generate_report_request_spec.rb +59 -0
  59. data/spec/soapy_bing/soap/response/base_spec.rb +12 -0
  60. data/spec/soapy_bing/soap/response/payload_spec.rb +25 -0
  61. data/spec/soapy_bing/soap/response/poll_generate_report_response_spec.rb +25 -0
  62. data/spec/soapy_bing/soap/response/report_status_spec.rb +91 -0
  63. data/spec/soapy_bing/soap/response/submit_generate_report_response_spec.rb +19 -0
  64. data/spec/soapy_bing/soap/template_renderer_spec.rb +24 -0
  65. data/spec/spec_helper.rb +32 -0
  66. data/spec/support/dotenv.rb +3 -0
  67. data/spec/support/vcr.rb +101 -0
  68. 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&lt; &amp;\n"
22
+ end
23
+ end
24
+ end
@@ -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'
@@ -0,0 +1,3 @@
1
+ require 'dotenv'
2
+
3
+ Dotenv.load('.env.local', '.env')
@@ -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