analytics-ruby 2.2.5 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,16 +2,43 @@ require 'logger'
2
2
 
3
3
  module Segment
4
4
  class Analytics
5
+ # Wraps an existing logger and adds a prefix to all messages
6
+ class PrefixedLogger
7
+ def initialize(logger, prefix)
8
+ @logger = logger
9
+ @prefix = prefix
10
+ end
11
+
12
+ def debug(msg)
13
+ @logger.debug("#{@prefix} #{msg}")
14
+ end
15
+
16
+ def info(msg)
17
+ @logger.info("#{@prefix} #{msg}")
18
+ end
19
+
20
+ def warn(msg)
21
+ @logger.warn("#{@prefix} #{msg}")
22
+ end
23
+
24
+ def error(msg)
25
+ @logger.error("#{@prefix} #{msg}")
26
+ end
27
+ end
28
+
5
29
  module Logging
6
30
  class << self
7
31
  def logger
8
- @logger ||= if defined?(Rails)
9
- Rails.logger
10
- else
11
- logger = Logger.new STDOUT
12
- logger.progname = 'Segment::Analytics'
13
- logger
14
- end
32
+ return @logger if @logger
33
+
34
+ base_logger = if defined?(Rails)
35
+ Rails.logger
36
+ else
37
+ logger = Logger.new STDOUT
38
+ logger.progname = 'Segment::Analytics'
39
+ logger
40
+ end
41
+ @logger = PrefixedLogger.new(base_logger, '[analytics-ruby]')
15
42
  end
16
43
 
17
44
  attr_writer :logger
@@ -1,9 +1,12 @@
1
+ require 'forwardable'
1
2
  require 'segment/analytics/logging'
2
3
 
3
4
  module Segment
4
5
  class Analytics
5
6
  # A batch of `Message`s to be sent to the API
6
7
  class MessageBatch
8
+ class JSONGenerationError < StandardError; end
9
+
7
10
  extend Forwardable
8
11
  include Segment::Analytics::Logging
9
12
  include Segment::Analytics::Defaults::MessageBatch
@@ -15,11 +18,18 @@ module Segment
15
18
  end
16
19
 
17
20
  def <<(message)
18
- if message.too_big?
21
+ begin
22
+ message_json = message.to_json
23
+ rescue StandardError => e
24
+ raise JSONGenerationError, "Serialization error: #{e}"
25
+ end
26
+
27
+ message_json_size = message_json.bytesize
28
+ if message_too_big?(message_json_size)
19
29
  logger.error('a message exceeded the maximum allowed size')
20
30
  else
21
31
  @messages << message
22
- @json_size += message.json_size + 1 # One byte for the comma
32
+ @json_size += message_json_size + 1 # One byte for the comma
23
33
  end
24
34
  end
25
35
 
@@ -42,6 +52,10 @@ module Segment
42
52
  @messages.length >= @max_message_count
43
53
  end
44
54
 
55
+ def message_too_big?(message_json_size)
56
+ message_json_size > Defaults::Message::MAX_BYTES
57
+ end
58
+
45
59
  # We consider the max size here as just enough to leave room for one more
46
60
  # message of the largest size possible. This is a shortcut that allows us
47
61
  # to use a native Ruby `Queue` that doesn't allow peeking. The tradeoff
@@ -0,0 +1,56 @@
1
+ module Segment
2
+ class Analytics
3
+ class TestQueue
4
+ attr_reader :messages
5
+
6
+ def initialize
7
+ reset!
8
+ end
9
+
10
+ def [](key)
11
+ all[key]
12
+ end
13
+
14
+ def count
15
+ all.count
16
+ end
17
+
18
+ def <<(message)
19
+ all << message
20
+ send(message[:type]) << message
21
+ end
22
+
23
+ def alias
24
+ messages[:alias] ||= []
25
+ end
26
+
27
+ def all
28
+ messages[:all] ||= []
29
+ end
30
+
31
+ def group
32
+ messages[:group] ||= []
33
+ end
34
+
35
+ def identify
36
+ messages[:identify] ||= []
37
+ end
38
+
39
+ def page
40
+ messages[:page] ||= []
41
+ end
42
+
43
+ def screen
44
+ messages[:screen] ||= []
45
+ end
46
+
47
+ def track
48
+ messages[:track] ||= []
49
+ end
50
+
51
+ def reset!
52
+ @messages = {}
53
+ end
54
+ end
55
+ end
56
+ end
@@ -9,13 +9,11 @@ require 'json'
9
9
 
10
10
  module Segment
11
11
  class Analytics
12
- class Request
12
+ class Transport
13
13
  include Segment::Analytics::Defaults::Request
14
14
  include Segment::Analytics::Utils
15
15
  include Segment::Analytics::Logging
16
16
 
17
- # public: Creates a new request object to send analytics batch
18
- #
19
17
  def initialize(options = {})
20
18
  options[:host] ||= HOST
21
19
  options[:port] ||= PORT
@@ -34,14 +32,18 @@ module Segment
34
32
  @http = http
35
33
  end
36
34
 
37
- # public: Posts the write key and batch of messages to the API.
35
+ # Sends a batch of messages to the API
38
36
  #
39
- # returns - Response of the status and error if it exists
40
- def post(write_key, batch)
37
+ # @return [Response] API response
38
+ def send(write_key, batch)
39
+ logger.debug("Sending request for #{batch.length} items")
40
+
41
41
  last_response, exception = retry_with_backoff(@retries) do
42
42
  status_code, body = send_request(write_key, batch)
43
43
  error = JSON.parse(body)['error']
44
44
  should_retry = should_retry_request?(status_code, body)
45
+ logger.debug("Response status code: #{status_code}")
46
+ logger.debug("Response error: #{error}") if error
45
47
 
46
48
  [Response.new(status_code, error), should_retry]
47
49
  end
@@ -49,12 +51,17 @@ module Segment
49
51
  if exception
50
52
  logger.error(exception.message)
51
53
  exception.backtrace.each { |line| logger.error(line) }
52
- Response.new(-1, "Connection error: #{exception}")
54
+ Response.new(-1, exception.to_s)
53
55
  else
54
56
  last_response
55
57
  end
56
58
  end
57
59
 
60
+ # Closes a persistent connection if it exists
61
+ def shutdown
62
+ @http.finish if @http.started?
63
+ end
64
+
58
65
  private
59
66
 
60
67
  def should_retry_request?(status_code, body)
@@ -90,6 +97,7 @@ module Segment
90
97
  end
91
98
 
92
99
  if should_retry && (retries_remaining > 1)
100
+ logger.debug("Retrying request, #{retries_remaining} retries left")
93
101
  sleep(@backoff_policy.next_interval.to_f / 1000)
94
102
  retry_with_backoff(retries_remaining - 1, &block)
95
103
  else
@@ -108,15 +116,11 @@ module Segment
108
116
 
109
117
  if self.class.stub
110
118
  logger.debug "stubbed request to #{@path}: " \
111
- "write key = #{write_key}, batch = JSON.generate(#{batch})"
119
+ "write key = #{write_key}, batch = #{JSON.generate(batch)}"
112
120
 
113
121
  [200, '{}']
114
122
  else
115
- # If `start` is not called, Ruby adds a 'Connection: close' header to
116
- # all requests, preventing us from reusing a connection for multiple
117
- # HTTP requests
118
- @http.start unless @http.started?
119
-
123
+ @http.start unless @http.started? # Maintain a persistent connection
120
124
  response = @http.request(request, payload)
121
125
  [response.code.to_i, response.body]
122
126
  end
@@ -64,12 +64,8 @@ module Segment
64
64
  end
65
65
  end
66
66
 
67
- def time_in_iso8601(time, fraction_digits = 3)
68
- fraction = if fraction_digits > 0
69
- ('.%06i' % time.usec)[0, fraction_digits + 1]
70
- end
71
-
72
- "#{time.strftime('%Y-%m-%dT%H:%M:%S')}#{fraction}#{formatted_offset(time, true, 'Z')}"
67
+ def time_in_iso8601(time)
68
+ "#{time.strftime('%Y-%m-%dT%H:%M:%S.%6N')}#{formatted_offset(time, true, 'Z')}"
73
69
  end
74
70
 
75
71
  def date_in_iso8601(date)
@@ -1,5 +1,5 @@
1
1
  module Segment
2
2
  class Analytics
3
- VERSION = '2.2.5'
3
+ VERSION = '2.4.0'
4
4
  end
5
5
  end
@@ -1,7 +1,6 @@
1
1
  require 'segment/analytics/defaults'
2
- require 'segment/analytics/message'
3
2
  require 'segment/analytics/message_batch'
4
- require 'segment/analytics/request'
3
+ require 'segment/analytics/transport'
5
4
  require 'segment/analytics/utils'
6
5
 
7
6
  module Segment
@@ -9,6 +8,7 @@ module Segment
9
8
  class Worker
10
9
  include Segment::Analytics::Utils
11
10
  include Segment::Analytics::Defaults
11
+ include Segment::Analytics::Logging
12
12
 
13
13
  # public: Creates a new worker
14
14
  #
@@ -29,7 +29,7 @@ module Segment
29
29
  batch_size = options[:batch_size] || Defaults::MessageBatch::MAX_SIZE
30
30
  @batch = MessageBatch.new(batch_size)
31
31
  @lock = Mutex.new
32
- @request = Request.new
32
+ @transport = Transport.new(options)
33
33
  end
34
34
 
35
35
  # public: Continuously runs the loop to check for new events
@@ -39,16 +39,16 @@ module Segment
39
39
  return if @queue.empty?
40
40
 
41
41
  @lock.synchronize do
42
- until @batch.full? || @queue.empty?
43
- @batch << Message.new(@queue.pop)
44
- end
42
+ consume_message_from_queue! until @batch.full? || @queue.empty?
45
43
  end
46
44
 
47
- res = @request.post(@write_key, @batch)
45
+ res = @transport.send @write_key, @batch
48
46
  @on_error.call(res.status, res.error) unless res.status == 200
49
47
 
50
48
  @lock.synchronize { @batch.clear }
51
49
  end
50
+ ensure
51
+ @transport.shutdown
52
52
  end
53
53
 
54
54
  # public: Check whether we have outstanding requests.
@@ -56,6 +56,14 @@ module Segment
56
56
  def is_requesting?
57
57
  @lock.synchronize { !@batch.empty? }
58
58
  end
59
+
60
+ private
61
+
62
+ def consume_message_from_queue!
63
+ @batch << @queue.pop
64
+ rescue MessageBatch::JSONGenerationError => e
65
+ @on_error.call(-1, e.to_s)
66
+ end
59
67
  end
60
68
  end
61
69
  end
@@ -1,11 +1,13 @@
1
1
  require 'segment/analytics/version'
2
2
  require 'segment/analytics/defaults'
3
3
  require 'segment/analytics/utils'
4
+ require 'segment/analytics/field_parser'
4
5
  require 'segment/analytics/client'
5
6
  require 'segment/analytics/worker'
6
- require 'segment/analytics/request'
7
+ require 'segment/analytics/transport'
7
8
  require 'segment/analytics/response'
8
9
  require 'segment/analytics/logging'
10
+ require 'segment/analytics/test_queue'
9
11
 
10
12
  module Segment
11
13
  class Analytics
@@ -17,7 +19,7 @@ module Segment
17
19
  # @option options [Boolean] :stub (false) If true, requests don't hit the
18
20
  # server and are stubbed to be successful.
19
21
  def initialize(options = {})
20
- Request.stub = options[:stub] if options.has_key?(:stub)
22
+ Transport.stub = options[:stub] if options.has_key?(:stub)
21
23
  @client = Segment::Analytics::Client.new options
22
24
  end
23
25
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: analytics-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.2.5
4
+ version: 2.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Segment.io
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-05-01 00:00:00.000000000 Z
11
+ date: 2021-05-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: commander
@@ -17,7 +17,7 @@ dependencies:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
19
  version: '4.4'
20
- type: :runtime
20
+ type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
@@ -81,33 +81,19 @@ dependencies:
81
81
  - !ruby/object:Gem::Version
82
82
  version: 4.1.11
83
83
  - !ruby/object:Gem::Dependency
84
- name: faraday
84
+ name: oj
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
87
  - - "~>"
88
88
  - !ruby/object:Gem::Version
89
- version: '0.13'
89
+ version: 3.6.2
90
90
  type: :development
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
94
  - - "~>"
95
95
  - !ruby/object:Gem::Version
96
- version: '0.13'
97
- - !ruby/object:Gem::Dependency
98
- name: pmap
99
- requirement: !ruby/object:Gem::Requirement
100
- requirements:
101
- - - "~>"
102
- - !ruby/object:Gem::Version
103
- version: '1.1'
104
- type: :development
105
- prerelease: false
106
- version_requirements: !ruby/object:Gem::Requirement
107
- requirements:
108
- - - "~>"
109
- - !ruby/object:Gem::Version
110
- version: '1.1'
96
+ version: 3.6.2
111
97
  - !ruby/object:Gem::Dependency
112
98
  name: rubocop
113
99
  requirement: !ruby/object:Gem::Requirement
@@ -143,40 +129,22 @@ executables:
143
129
  extensions: []
144
130
  extra_rdoc_files: []
145
131
  files:
146
- - Gemfile
147
- - History.md
148
- - Makefile
149
- - README.md
150
- - RELEASING.md
151
- - Rakefile
152
- - analytics-ruby.gemspec
153
132
  - bin/analytics
154
- - codecov.yml
155
133
  - lib/analytics-ruby.rb
156
134
  - lib/segment.rb
157
135
  - lib/segment/analytics.rb
158
136
  - lib/segment/analytics/backoff_policy.rb
159
137
  - lib/segment/analytics/client.rb
160
138
  - lib/segment/analytics/defaults.rb
139
+ - lib/segment/analytics/field_parser.rb
161
140
  - lib/segment/analytics/logging.rb
162
- - lib/segment/analytics/message.rb
163
141
  - lib/segment/analytics/message_batch.rb
164
- - lib/segment/analytics/request.rb
165
142
  - lib/segment/analytics/response.rb
143
+ - lib/segment/analytics/test_queue.rb
144
+ - lib/segment/analytics/transport.rb
166
145
  - lib/segment/analytics/utils.rb
167
146
  - lib/segment/analytics/version.rb
168
147
  - lib/segment/analytics/worker.rb
169
- - spec/helpers/runscope_client.rb
170
- - spec/segment/analytics/backoff_policy_spec.rb
171
- - spec/segment/analytics/client_spec.rb
172
- - spec/segment/analytics/e2e_spec.rb
173
- - spec/segment/analytics/message_batch_spec.rb
174
- - spec/segment/analytics/message_spec.rb
175
- - spec/segment/analytics/request_spec.rb
176
- - spec/segment/analytics/response_spec.rb
177
- - spec/segment/analytics/worker_spec.rb
178
- - spec/segment/analytics_spec.rb
179
- - spec/spec_helper.rb
180
148
  homepage: https://github.com/segmentio/analytics-ruby
181
149
  licenses:
182
150
  - MIT
@@ -189,15 +157,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
189
157
  requirements:
190
158
  - - ">="
191
159
  - !ruby/object:Gem::Version
192
- version: '0'
160
+ version: '2.0'
193
161
  required_rubygems_version: !ruby/object:Gem::Requirement
194
162
  requirements:
195
163
  - - ">="
196
164
  - !ruby/object:Gem::Version
197
165
  version: '0'
198
166
  requirements: []
199
- rubyforge_project:
200
- rubygems_version: 2.7.6
167
+ rubygems_version: 3.0.8
201
168
  signing_key:
202
169
  specification_version: 4
203
170
  summary: Segment.io analytics library
data/Gemfile DELETED
@@ -1,2 +0,0 @@
1
- source 'http://rubygems.org'
2
- gemspec
data/History.md DELETED
@@ -1,222 +0,0 @@
1
- 2.2.5 / 2018-05-01
2
- ==================
3
-
4
- * [Fix](https://github.com/segmentio/analytics-ruby/pull/158): Require `version` module first.
5
-
6
- 2.2.4 / 2018-04-30
7
- ==================
8
-
9
- * Promote pre-release version to stable.
10
-
11
- 2.2.4.pre / 2018-02-04
12
- ======================
13
-
14
- * [Fix](https://github.com/segmentio/analytics-ruby/pull/147): Prevent 'batch
15
- size exceeded' errors by automatically batching
16
- items according to size
17
- * [Performance](https://github.com/segmentio/analytics-ruby/pull/149): Reuse
18
- TCP connections
19
- * [Improvement](https://github.com/segmentio/analytics-ruby/pull/145): Emit logs
20
- when in-memory queue is full
21
- * [Improvement](https://github.com/segmentio/analytics-ruby/pull/143): Emit logs
22
- when messages exceed maximum allowed size
23
- * [Improvement](https://github.com/segmentio/analytics-ruby/pull/134): Add
24
- exponential backoff to retries
25
- * [Improvement](https://github.com/segmentio/analytics-ruby/pull/132): Handle
26
- HTTP status code failure appropriately
27
-
28
- 2.2.3.pre / 2017-09-14
29
- ==================
30
-
31
- * [Fix](https://github.com/segmentio/analytics-ruby/pull/120): Override `respond_to_missing` instead of `respond_to?` to facilitate mock the library in tests.
32
-
33
-
34
- 2.2.2 / 2016-08-03
35
- ==================
36
-
37
- * adding commander as dep (for CLI)
38
-
39
- 2.2.1 / 2016-08-03
40
- ==================
41
-
42
- * add executables to spec
43
-
44
- 2.2.0 / 2016-08-03
45
- ==================
46
-
47
- * Adding an (experimental) CLI
48
-
49
- 2.1.0 / 2016-06-17
50
- ==================
51
-
52
- * Fix: Ensure error handler is called before Client#flush finishes.
53
- * Feature: Support setting a custom message ID.
54
-
55
- 2.0.13 / 2015-09-15
56
- ==================
57
-
58
- * readme: updated install docs
59
- * fix: page/screen to allow no name
60
- * git: ignore ruby version
61
- * travis-ci: remove old rubys
62
-
63
- 2.0.12 / 2015-01-10
64
- ==================
65
-
66
- * Fix batch being cleared and causing duplicates
67
-
68
- 2.0.11 / 2014-09-22
69
- ==================
70
-
71
- * fix: don't clear batch if request failed
72
-
73
- 2.0.10 / 2014-09-22
74
- ==================
75
-
76
- * Move timeout retry above output
77
-
78
- 2.0.9 / 2014-09-22
79
- ==================
80
-
81
- * Fix rescuing timeouts
82
-
83
- 2.0.8 / 2014-09-11
84
- ==================
85
- * fix: add 3 ms to timestamp
86
-
87
- 2.0.7 / 2014-08-27
88
- ==================
89
- * fix: include optional options hash in calls
90
-
91
- 2.0.6 / 2014-08-12
92
- ==================
93
- * fix: category param on #page and #screen
94
-
95
- 2.0.5 / 2014-05-26
96
- ==================
97
- * fix: datetime conversions
98
-
99
- 2.0.4 / 2014-06-11
100
- ==================
101
- * fix: isofying trait dates in #group
102
-
103
- 2.0.3 / 2014-06-04
104
- ==================
105
- * fix: undefined method `is_requesting?' for nil:NilClass when calling flush (#51)
106
-
107
- 2.0.2 / 2014-05-30
108
- ==================
109
- * fix: formatting ios dates
110
- * fix: respond_to? implementation
111
- * add: able to stub requests by setting STUB env var
112
-
113
- 2.0.1 / 2014-05-15
114
- ==================
115
- * add: namespace under Segment::Analytics
116
- * add: can create multiple instances with creator method (rather than
117
- having a singleton)
118
- * add: logging with Logger instance or Rails.logger if available
119
- * add: able to stub requests so they just log and don't hit the server
120
- * fix: worker continues running across forked processes
121
- * fix: removed usage of ActiveSupport methods since its not a dependency
122
- * fix: sending data that matches segment's new api spec
123
-
124
- (there is no v2.0.0)
125
-
126
- 1.1.0 / 2014-04-17
127
- ==================
128
- * adding .initialized? by [@lumberj](https://github.com/lumberj)
129
-
130
- 1.0.0 / 2014-03-12
131
- ==================
132
- * removing faraday dependency
133
-
134
- 0.6.0 / 2014-02-19
135
- ==================
136
- * adding .group(), .page(), and .screen() calls
137
- * relaxing faraday dependency, fixes #31
138
-
139
- 0.5.4 / 2013-12-31
140
- ==================
141
- * Add `requestId` fields to all requests for tracing.
142
-
143
- 0.5.3 / 2013-12-31
144
- ==================
145
- * Allow the consumer thread to shut down so it won't remain live in hot deploy scenarios. This fixes the jruby memory leak by [@nirvdrum](https://github.com/nirvdrum)
146
-
147
- 0.5.2 / 2013-12-02
148
- ==================
149
- * adding `sleep` backoff between connection retries
150
-
151
- 0.5.1 / 2013-11-22
152
- ==================
153
- * adding retries for connection hangups
154
-
155
- 0.5.0 / 2013-10-03
156
- ==================
157
- * Removing global Analytics constant in favor of adding it to our config. NOTE: If you are upgrading from a previous version and want to continue using the `Analytics` namespace, you'll have to add `Analytics = AnalyticsRuby` to your config. Otherwise you WILL NOT be sending analytics data. See the [setup docs for more info](https://segment.io/libraries/ruby)
158
-
159
- 0.4.0 / 2013-08-30
160
- ==================
161
- * Adding support and tests for 1.8.7
162
-
163
- 0.3.4 / 2013-08-26
164
- ==================
165
- * Pass `Time` values as iso8601 timestamp strings
166
-
167
- 0.3.3 / 2013-08-02
168
- ==================
169
- * Allow init/track/identify/alias to accept strings as keys. by [@shipstar](https://github.com/shipstar)
170
-
171
- 0.3.2 / 2013-05-28
172
- ==================
173
- * Adding faraday timeout by [@yanchenyun](https://github.com/yangchenyun)
174
-
175
- 0.3.1 / 2013-04-29
176
- ==================
177
- * Adding check for properties to be a Hash
178
-
179
- 0.3.0 / 2013-04-05
180
- ==================
181
- * Adding alias call
182
-
183
- 0.2.0 / 2013-03-21
184
- ==================
185
- * Adding flush method
186
-
187
- 0.1.4 / 2013-03-19
188
- ==================
189
- * Adding ClassMethods for more extensibility by [arronmabrey](https://github.com/arronmabrey)
190
-
191
- 0.1.3 / 2013-03-19
192
- ==================
193
- * Fixing user_id.to_s semantics, reported by [arronmabrey](https://github.com/arronmabrey)
194
- * Reduced faraday requirements by [arronmabrey](https://github.com/arronmabrey)
195
-
196
- 0.1.2 / 2013-03-11
197
- ==================
198
- * Fixing thrown exception on non-initialized tracks thanks to [sbellity](https://github.com/sbellity)
199
-
200
- 0.1.1 / 2013-02-11
201
- ==================
202
- * Updating dependencies
203
- * Adding actual support for MultiJson 1.0
204
-
205
- 0.1.0 / 2013-01-22
206
- ==================
207
- * Updated docs to point at segment.io
208
-
209
- 0.0.5 / 2013-01-21
210
- ==================
211
- * Renaming of all the files for proper bundling usage
212
-
213
- 0.0.4 / 2013-01-17
214
- ==================
215
- * Updated readme and install instruction courtesy of [@zeke](https://github.com/zeke)
216
- * Removed typhoeus and reverted to default adapter
217
- * Removing session_id in favor of a single user_id
218
-
219
- 0.0.3 / 2013-01-16
220
- ==================
221
- * Rakefile and renaming courtesy of [@kiennt](https://github.com/kiennt)
222
- * Updated tests with mocks