bps-google-api 0.2.0 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4bb3f5af2dbe6a57df4945c7d082aab5e6f68edb
4
- data.tar.gz: 225fd27b21ae836d6d051bf32a81b96e17ed71f7
3
+ metadata.gz: 9db47ec4d8765e23f5eaf19a0d0fab4927fdde18
4
+ data.tar.gz: 3bdc229d7f57a1cb9a1efbb5d86b88f1b38c6117
5
5
  SHA512:
6
- metadata.gz: 031d62aac72e1de816ac5d309fed95d8886c7b205633f25c9accf6ca8e43fa389c62334943a57f9ed4a0f85c47583149daab492eff8be4db403b7018c90b09a7
7
- data.tar.gz: 3e5e6374f3731ba02d763d515bf3877d35e635a0c5f1f87a465ac8fb891581aa288a42c83eed1fbf93a963dba681115269a497efe864d71c31116f0580940e45
6
+ metadata.gz: 327541c28d54c819b6827cf1c7024c11a45de98efa04e873a36d8f834c8b99f3012afb61885608a65be911ce50fcf3daef7757e5e3fd8a9a3a9774bd207a20db
7
+ data.tar.gz: a2f81f09ec656b48000906fb7a566cafcda0d617c2aa3890415485d418e18ead19e2662ac42a5b11f059fef119a50d992e9124b4e6755dcf199146fa4fbe2e17
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- bps-google-api (0.2.0)
4
+ bps-google-api (0.2.1)
5
5
  exp_retry (~> 0.0.11)
6
6
  fileutils (~> 1.2)
7
7
  google-api-client (~> 0.23.4)
@@ -2,7 +2,7 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = 'bps-google-api'
5
- s.version = '0.2.0'
5
+ s.version = '0.2.1'
6
6
  s.date = '2019-06-20'
7
7
  s.summary = 'Configured Google API'
8
8
  s.description = 'A configured Google API wrapper.'
data/lib/ext/hash.rb ADDED
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Import assert_valid_keys from Rails if not already defined
4
+ class Hash
5
+ def assert_valid_keys(*valid_keys)
6
+ valid_keys.flatten!
7
+
8
+ each_key do |k|
9
+ next if valid_keys.include?(k)
10
+
11
+ raise(
12
+ ArgumentError,
13
+ "Unknown key: #{k.inspect}. Valid keys are: #{valid_keys.map(&:inspect).join(', ')}"
14
+ )
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ProgressBar
4
+ class Silent
5
+ def self.tty?
6
+ true
7
+ end
8
+
9
+ def self.print(str)
10
+ str
11
+ end
12
+
13
+ def self.flush
14
+ self
15
+ end
16
+ end
17
+ end
data/lib/google_api.rb CHANGED
@@ -14,4 +14,8 @@ module GoogleAPI
14
14
  require 'google_api/base'
15
15
  require 'google_api/calendar'
16
16
  require 'google_api/group'
17
+ require 'google_api/configured'
18
+
19
+ require 'ext/hash' unless defined?(Rails)
20
+ require 'ext/silent_progress_bar'
17
21
  end
@@ -36,8 +36,7 @@ module GoogleAPI
36
36
  end
37
37
 
38
38
  def last_token_path
39
- path = %w[tmp run last_page_token]
40
- File.join(root_path, *path)
39
+ File.join(root_path, 'tmp', 'run', 'last_page_token')
41
40
  end
42
41
  end
43
42
  end
@@ -37,7 +37,7 @@ module GoogleAPI
37
37
  url = authorizer.get_authorization_url(base_url: OOB_URI)
38
38
  puts("Open this URL to authorize:\n", url)
39
39
  print("\nResponse code: ")
40
- gets
40
+ ENV.key?('GOOGLE_AUTHORIZATION_CODE') ? ENV['GOOGLE_AUTHORIZATION_CODE'] : gets
41
41
  end
42
42
 
43
43
  def authorizer
@@ -61,12 +61,12 @@ module GoogleAPI
61
61
  {
62
62
  GOOGLE_CLIENT_ID: auth.client_id, GOOGLE_CLIENT_SECRET: auth.client_secret,
63
63
  GOOGLE_ACCESS_TOKEN: auth.access_token, GOOGLE_REFRESH_TOKEN: auth.refresh_token,
64
- GOOGLE_AUTH_SCOPES: auth.scope, GOOGLE_AUTH_EXP: expires_milli(auth.as_json['expires_at'])
64
+ GOOGLE_AUTH_SCOPES: auth.scope, GOOGLE_AUTH_EXP: expires_milli(auth.expires_at.to_s)
65
65
  }
66
66
  end
67
67
 
68
68
  def expires_milli(time)
69
- DateTime.strptime(time, '%Y-%m-%dT%H:%M:%S.%L%:z').to_i * 1000
69
+ Time.strptime(time, '%Y-%m-%d %H:%M:%S %:z').to_i * 1000
70
70
  end
71
71
 
72
72
  def store_key(path, key)
@@ -56,7 +56,7 @@ module GoogleAPI
56
56
  private
57
57
 
58
58
  def event(event_options)
59
- validate_event_options(event_options)
59
+ event_options.assert_valid_keys(VALID_EVENT_KEYS)
60
60
  event_options[:start] = date(event_options[:start])
61
61
  event_options[:end] = date(event_options[:end])
62
62
 
@@ -67,16 +67,5 @@ module GoogleAPI
67
67
  key = date&.is_a?(String) ? :date : :date_time
68
68
  Google::Apis::CalendarV3::EventDateTime.new(key => date, time_zone: ENV['TZ'])
69
69
  end
70
-
71
- def last_token_path
72
- path = %w[tmp run last_page_token]
73
- defined?(Rails) ? Rails.root.join(*path) : File.join(*path)
74
- end
75
-
76
- def validate_event_options(event_options)
77
- return unless event_options.respond_to?(:assert_valid_keys)
78
-
79
- event_options.assert_valid_keys(VALID_EVENT_KEYS)
80
- end
81
70
  end
82
71
  end
@@ -3,14 +3,16 @@
3
3
  module GoogleAPI
4
4
  class Calendar < GoogleAPI::Base
5
5
  module ClearTestCalendar
6
- def clear_test_calendar(page_token: nil, page_limit: 50, verbose: false)
6
+ def clear_test_calendar(page_token: nil, page_limit: 50, verbose: false, error: false)
7
+ raise Google::Apis::RateLimitError, '(Rate Limit Exceeded)' if error
8
+
7
9
  @verbose = verbose
8
10
  Google::Apis.logger.level = Logger::WARN
9
11
  choose_page_token(page_token)
10
12
  loop_over_pages(ENV['GOOGLE_CALENDAR_ID_TEST'], page_limit: page_limit)
11
13
  puts '*** Cleared all events!' if @verbose
12
14
  rescue Google::Apis::RateLimitError
13
- puts "\n\n*** Google::Apis::RateLimitError (Rate Limit Exceeded)"
15
+ puts "\n\n*** Google::Apis::RateLimitError (Rate Limit Exceeded)" if @verbose
14
16
  ensure
15
17
  log_last_page_token if token?
16
18
  end
@@ -58,10 +60,14 @@ module GoogleAPI
58
60
  end
59
61
 
60
62
  def progress_bar(total)
61
- ProgressBar.create(
63
+ bar_config = {
62
64
  title: 'Page cleared', starting_at: 0, total: total, progress_mark: ' ',
63
65
  remainder_mark: "\u{FF65}", format: "%a [%R/sec] %E | %b\u{15E7}%i %c/%C (%P%%) %t"
64
- )
66
+ }
67
+
68
+ bar_config = bar_config.merge(output: ProgressBar::Silent) if ENV.key?('HIDE_PROGRESS_BARS')
69
+
70
+ ProgressBar.create(bar_config)
65
71
  end
66
72
  end
67
73
  end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GoogleAPI
4
+ module Configured
5
+ require 'google_api/configured/calendar'
6
+ require 'google_api/configured/group'
7
+ end
8
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GoogleAPI
4
+ module Configured
5
+ class Calendar
6
+ CALENDAR_API ||= GoogleAPI::Calendar.new
7
+
8
+ attr_reader :calendar_id
9
+
10
+ def initialize(calendar_id)
11
+ @calendar_id = calendar_id
12
+ end
13
+
14
+ def list(max_results: 2500, page_token: nil)
15
+ CALENDAR_API.list(calendar_id, max_results: max_results, page_token: page_token)
16
+ end
17
+
18
+ def create(event_options = {})
19
+ CALENDAR_API.create(calendar_id, event_options)
20
+ end
21
+
22
+ def get(event_id)
23
+ CALENDAR_API.get(calendar_id, event_id)
24
+ end
25
+
26
+ def update(event_id, event_options = {})
27
+ CALENDAR_API.update(calendar_id, event_id, event_options)
28
+ end
29
+
30
+ def delete(event_id)
31
+ CALENDAR_API.delete(calendar_id, event_id)
32
+ end
33
+
34
+ def permit(user = nil, email: nil)
35
+ CALENDAR_API.permit(calendar_id, user, email: email)
36
+ end
37
+
38
+ def unpermit(user = nil, calendar_rule_id: nil)
39
+ CALENDAR_API.unpermit(calendar_id, user, calendar_rule_id: calendar_rule_id)
40
+ end
41
+
42
+ def clear_test_calendar(page_token: nil, page_limit: 50, verbose: false, error: false)
43
+ CALENDAR_API.clear_test_calendar(
44
+ page_token: page_token, page_limit: page_limit, verbose: verbose, error: error
45
+ )
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GoogleAPI
4
+ module Configured
5
+ class Group
6
+ GROUP_API ||= GoogleAPI::Group.new
7
+
8
+ attr_reader :group_id
9
+
10
+ def initialize(group_id)
11
+ @group_id = group_id
12
+ end
13
+
14
+ def get
15
+ GROUP_API.get(group_id)
16
+ end
17
+
18
+ def members
19
+ GROUP_API.members(group_id)
20
+ end
21
+
22
+ def add(email)
23
+ GROUP_API.add(group_id, email)
24
+ end
25
+
26
+ def remove(email)
27
+ GROUP_API.remove(group_id, email)
28
+ end
29
+ end
30
+ end
31
+ end
@@ -4,27 +4,26 @@ module GoogleAPI
4
4
  class Group < GoogleAPI::Base
5
5
  SERVICE_CLASS = Google::Apis::AdminDirectoryV1::DirectoryService
6
6
 
7
- def initialize(id, auth: true)
8
- @group_id = id
7
+ def initialize(auth: true)
9
8
  super(auth: auth)
10
9
  end
11
10
 
12
- def get
13
- call(:get_group, @group_id)
11
+ def get(group_id)
12
+ call(:get_group, group_id)
14
13
  end
15
14
 
16
- def members
17
- call(:list_members, @group_id)
15
+ def members(group_id)
16
+ call(:list_members, group_id)
18
17
  end
19
18
 
20
- def add(email)
21
- call(:insert_member, @group_id, member(email))
19
+ def add(group_id, email)
20
+ call(:insert_member, group_id, member(email))
22
21
  rescue Google::Apis::ClientError
23
22
  :already_exists
24
23
  end
25
24
 
26
- def remove(email)
27
- call(:delete_member, @group_id, email)
25
+ def remove(group_id, email)
26
+ call(:delete_member, group_id, email)
28
27
  rescue Google::Apis::ClientError
29
28
  :not_found
30
29
  end
@@ -10,4 +10,24 @@ RSpec.describe GoogleAPI::Base do
10
10
  NameError, 'uninitialized constant GoogleAPI::Base::SERVICE_CLASS'
11
11
  )
12
12
  end
13
+
14
+ context 'with a valid subclass' do
15
+ subject { GoogleAPI::Calendar }
16
+
17
+ it 'returns the correct last token path' do
18
+ expect(subject.new.send(:last_token_path)).to eql('./tmp/run/last_page_token')
19
+ end
20
+
21
+ it 'returns an array from authorize with reveal' do
22
+ expect(subject.new(auth: false).authorize!(reveal: true)).to be_a(Array)
23
+ end
24
+
25
+ let(:reauth) { proc(subject.new(auth: false).authorize!(refresh: true)) }
26
+
27
+ it 'reauthorizes' do
28
+ silently do
29
+ expect { reauth.call }.to raise_error(Signet::AuthorizationError)
30
+ end
31
+ end
32
+ end
13
33
  end
@@ -3,73 +3,130 @@
3
3
  require 'spec_helper'
4
4
 
5
5
  RSpec.describe GoogleAPI::Calendar do
6
- subject { described_class.new }
7
-
8
- it 'has a service class' do
9
- expect { subject.send(:service_class) }.not_to raise_error
6
+ def test_event
7
+ {
8
+ summary: 'API Test Event',
9
+ description: 'This is a test event generated by the API gem.',
10
+ start: DateTime.strptime('201906051200', '%Y%m%d%H%M'),
11
+ end: DateTime.strptime('201906051400', '%Y%m%d%H%M')
12
+ }
10
13
  end
11
14
 
12
- it 'has the correct service class' do
13
- expect(subject.send(:service_class)).to eql(Google::Apis::CalendarV3::CalendarService)
15
+ describe 'service class' do
16
+ subject { described_class.new }
17
+
18
+ it 'does not raise an error' do
19
+ expect { subject.send(:service_class) }.not_to raise_error
20
+ end
21
+
22
+ it 'is the correct class' do
23
+ expect(subject.send(:service_class)).to eql(Google::Apis::CalendarV3::CalendarService)
24
+ end
14
25
  end
15
26
 
16
- describe 'list' do
17
- it 'returns not found for a nonexistent calendar' do
18
- expect { subject.list('not-a-calendar') }.to raise_error(
27
+ context 'with an invalid calendar' do
28
+ let(:test_cal_id) { 'not-a-calendar' }
29
+
30
+ subject { GoogleAPI::Configured::Calendar.new(test_cal_id) }
31
+
32
+ it 'returns not found from list' do
33
+ expect { subject.list }.to raise_error(
19
34
  Google::Apis::ClientError, 'notFound: Not Found'
20
35
  )
21
36
  end
22
- end
23
37
 
24
- describe 'create' do
25
- it 'returns not found for a nonexistent calendar' do
26
- expect { subject.create('not-a-calendar') }.to raise_error(
38
+ it 'returns not found from create' do
39
+ expect { subject.create }.to raise_error(
27
40
  Google::Apis::ClientError, 'notFound: Not Found'
28
41
  )
29
42
  end
30
- end
31
43
 
32
- describe 'get' do
33
- it 'returns not found for a nonexistent calendar' do
34
- expect { subject.get('not-a-calendar', 'not-an-event') }.to raise_error(
44
+ it 'returns not found from get' do
45
+ expect { subject.get('not-an-event') }.to raise_error(
35
46
  Google::Apis::ClientError, 'notFound: Not Found'
36
47
  )
37
48
  end
38
- end
39
49
 
40
- describe 'update' do
41
- it 'returns not found for a nonexistent calendar' do
42
- expect { subject.update('not-a-calendar', 'not-an-event') }.to raise_error(
50
+ it 'returns not found from update' do
51
+ expect { subject.update('not-an-event') }.to raise_error(
43
52
  Google::Apis::ClientError, 'notFound: Not Found'
44
53
  )
45
54
  end
46
- end
47
55
 
48
- describe 'delete' do
49
- it 'returns event not found for a nonexistent calendar' do
50
- expect(subject.delete('not-a-calendar', 'not-an-event')).to eql(
56
+ it 'returns event not found from delete' do
57
+ expect(subject.delete('not-an-event')).to eql(
51
58
  :event_not_found
52
59
  )
53
60
  end
54
- end
55
61
 
56
- describe 'permit' do
57
- it 'returns nil for a nonexistent calendar' do
58
- expect(subject.permit('not-a-calendar', email: 'not-a-user')).to be_nil
62
+ it 'returns nil from permit' do
63
+ expect(subject.permit(email: 'not-a-user')).to be_nil
59
64
  end
60
- end
61
65
 
62
- describe 'unpermit' do
63
- it 'returns permission not found for a nonexistent calendar' do
64
- expect(subject.unpermit('not-a-calendar', calendar_rule_id: 'not-a-user')).to eql(
65
- :permission_not_found
66
- )
66
+ it 'returns permission not found from unpermit' do
67
+ expect(subject.unpermit(calendar_rule_id: 'not-a-user')).to eql(:permission_not_found)
67
68
  end
68
69
  end
69
70
 
70
- describe 'clear test calendar' do
71
- it 'does not raise any errors' do
72
- expect { subject.clear_test_calendar }.not_to raise_error
71
+ context 'with a valid calendar' do
72
+ let(:test_cal_id) { ENV['GOOGLE_CALENDAR_ID_TEST'] }
73
+
74
+ subject { GoogleAPI::Configured::Calendar.new(test_cal_id) }
75
+
76
+ it 'returns a list' do
77
+ expect(subject.list).to be_a(Google::Apis::CalendarV3::Events)
78
+ end
79
+
80
+ it 'creates an event' do
81
+ expect(subject.create(test_event)).to be_a(Google::Apis::CalendarV3::Event)
82
+ end
83
+
84
+ it 'gets an event ' do
85
+ event = subject.create(test_event)
86
+ expect(subject.get(event.id)).to be_a(Google::Apis::CalendarV3::Event)
87
+ end
88
+
89
+ it 'updates an event' do
90
+ event = subject.create(test_event)
91
+ updated_test_event = test_event.merge(description: 'Updated.')
92
+ subject.update(event.id, updated_test_event)
93
+ updated_event = subject.get(event.id)
94
+
95
+ expect(updated_event.description).to eql('Updated.')
96
+ end
97
+
98
+ it 'deletes an event' do
99
+ event = subject.create(test_event)
100
+
101
+ expect(subject.delete(event.id)).to eql('')
102
+ end
103
+
104
+ it 'returns nil from permit' do
105
+ expect(subject.permit(email: 'not-a-user')).to be_nil
106
+ end
107
+
108
+ it 'returns permission not found from unpermit' do
109
+ expect(subject.unpermit(calendar_rule_id: 'not-a-user')).to eql(:permission_not_found)
110
+ end
111
+
112
+ describe 'clear test calendar' do
113
+ it 'does not raise any errors' do
114
+ expect { subject.clear_test_calendar }.not_to raise_error
115
+ end
116
+
117
+ it 'rescues from an unhandled rate limit exception' do
118
+ expect { subject.clear_test_calendar(error: true) }.not_to raise_error
119
+ end
120
+
121
+ it 'does not raise any errors with verbose' do
122
+ subject.create(test_event)
123
+
124
+ silently do
125
+ expect do
126
+ GoogleAPI::Configured::Calendar.new('test').clear_test_calendar(verbose: true)
127
+ end.not_to raise_error
128
+ end
129
+ end
73
130
  end
74
131
  end
75
132
  end
@@ -3,41 +3,47 @@
3
3
  require 'spec_helper'
4
4
 
5
5
  RSpec.describe GoogleAPI::Group do
6
- subject { described_class.new('test-group@example.com') }
6
+ describe 'service class' do
7
+ subject { described_class.new }
7
8
 
8
- it 'has a service class' do
9
- expect { subject.send(:service_class) }.not_to raise_error
10
- end
9
+ it 'has a service class' do
10
+ expect { subject.send(:service_class) }.not_to raise_error
11
+ end
11
12
 
12
- it 'has the correct service class' do
13
- expect(subject.send(:service_class)).to eql(Google::Apis::AdminDirectoryV1::DirectoryService)
13
+ it 'has the correct service class' do
14
+ expect(subject.send(:service_class)).to eql(Google::Apis::AdminDirectoryV1::DirectoryService)
15
+ end
14
16
  end
15
17
 
16
- describe 'get' do
17
- it 'returns not found for a nonexistent group' do
18
- expect { subject.get }.to raise_error(
19
- Google::Apis::ClientError, 'forbidden: Not Authorized to access this resource/api'
20
- )
18
+ context 'with an invalid group' do
19
+ subject { GoogleAPI::Configured::Group.new('test-group@example.com') }
20
+
21
+ describe 'get' do
22
+ it 'returns not found for a nonexistent group' do
23
+ expect { subject.get }.to raise_error(
24
+ Google::Apis::ClientError, 'forbidden: Not Authorized to access this resource/api'
25
+ )
26
+ end
21
27
  end
22
- end
23
28
 
24
- describe 'members' do
25
- it 'returns forbidden for a nonexistent group' do
26
- expect { subject.members }.to raise_error(
27
- Google::Apis::ClientError, 'forbidden: Not Authorized to access this resource/api'
28
- )
29
+ describe 'members' do
30
+ it 'returns forbidden for a nonexistent group' do
31
+ expect { subject.members }.to raise_error(
32
+ Google::Apis::ClientError, 'forbidden: Not Authorized to access this resource/api'
33
+ )
34
+ end
29
35
  end
30
- end
31
36
 
32
- describe 'add' do
33
- it 'returns not found for a nonexistent group' do
34
- expect(subject.add('test-member@example.com')).to eql(:already_exists)
37
+ describe 'add' do
38
+ it 'returns not found for a nonexistent group' do
39
+ expect(subject.add('test-member@example.com')).to eql(:already_exists)
40
+ end
35
41
  end
36
- end
37
42
 
38
- describe 'remove' do
39
- it 'returns not found for a nonexistent group' do
40
- expect(subject.remove('test-member@example.com')).to eql(:not_found)
43
+ describe 'remove' do
44
+ it 'returns not found for a nonexistent group' do
45
+ expect(subject.remove('test-member@example.com')).to eql(:not_found)
46
+ end
41
47
  end
42
48
  end
43
49
  end
data/spec/spec_helper.rb CHANGED
@@ -5,12 +5,25 @@ Bundler.setup
5
5
  require 'simplecov'
6
6
  SimpleCov.start do
7
7
  add_filter '/spec'
8
+ add_filter '/lib/ext'
8
9
  end
10
+ SimpleCov.minimum_coverage(100)
9
11
 
10
12
  require 'google_api'
11
13
 
14
+ def silently
15
+ original_stdout = $stdout
16
+ $stdout = File.new(File.join('tmp', 'null'), 'w')
17
+ yield
18
+ $stdout = original_stdout
19
+ end
20
+
12
21
  RSpec.configure do |config|
13
22
  config.before(:suite) do
14
- FileUtils.mkdir_p('tmp/run')
23
+ FileUtils.mkdir_p(File.join('tmp', 'run'))
24
+ FileUtils.rm(Dir.glob(File.join('config', 'keys', '*')))
25
+
26
+ ENV['GOOGLE_AUTHORIZATION_CODE'] = 'test-auth-code'
27
+ ENV['HIDE_PROGRESS_BARS'] = 'true'
15
28
  end
16
29
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bps-google-api
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Julian Fiander
@@ -120,11 +120,16 @@ files:
120
120
  - Gemfile.lock
121
121
  - Readme.md
122
122
  - bps-google-api.gemspec
123
+ - lib/ext/hash.rb
124
+ - lib/ext/silent_progress_bar.rb
123
125
  - lib/google_api.rb
124
126
  - lib/google_api/base.rb
125
127
  - lib/google_api/base/authorization.rb
126
128
  - lib/google_api/calendar.rb
127
129
  - lib/google_api/calendar/clear_test_calendar.rb
130
+ - lib/google_api/configured.rb
131
+ - lib/google_api/configured/calendar.rb
132
+ - lib/google_api/configured/group.rb
128
133
  - lib/google_api/group.rb
129
134
  - spec/.rubocop.yml
130
135
  - spec/lib/google_api/base_spec.rb