astronomer 2.0.14

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.
@@ -0,0 +1,191 @@
1
+ require 'spec_helper'
2
+
3
+ module Segment
4
+ class Analytics
5
+ describe Request do
6
+ before do
7
+ # Try and keep debug statements out of tests
8
+ allow(subject.logger).to receive(:error)
9
+ allow(subject.logger).to receive(:debug)
10
+ end
11
+
12
+ describe '#initialize' do
13
+ let!(:net_http) { Net::HTTP.new(anything, anything) }
14
+
15
+ before do
16
+ allow(Net::HTTP).to receive(:new) { net_http }
17
+ end
18
+
19
+ it 'sets an initalized Net::HTTP read_timeout' do
20
+ expect(net_http).to receive(:use_ssl=)
21
+ described_class.new
22
+ end
23
+
24
+ it 'sets an initalized Net::HTTP read_timeout' do
25
+ expect(net_http).to receive(:read_timeout=)
26
+ described_class.new
27
+ end
28
+
29
+ it 'sets an initalized Net::HTTP open_timeout' do
30
+ expect(net_http).to receive(:open_timeout=)
31
+ described_class.new
32
+ end
33
+
34
+ it 'sets the http client' do
35
+ expect(subject.instance_variable_get(:@http)).to_not be_nil
36
+ end
37
+
38
+ context 'no options are set' do
39
+ it 'sets a default path' do
40
+ expect(subject.instance_variable_get(:@path)).to eq(described_class::PATH)
41
+ end
42
+
43
+ it 'sets a default retries' do
44
+ expect(subject.instance_variable_get(:@retries)).to eq(described_class::RETRIES)
45
+ end
46
+
47
+ it 'sets a default backoff' do
48
+ expect(subject.instance_variable_get(:@backoff)).to eq(described_class::BACKOFF)
49
+ end
50
+
51
+ it 'initializes a new Net::HTTP with default host and port' do
52
+ expect(Net::HTTP).to receive(:new).with(described_class::HOST, described_class::PORT)
53
+ described_class.new
54
+ end
55
+ end
56
+
57
+ context 'options are given' do
58
+ let(:path) { 'my/cool/path' }
59
+ let(:retries) { 1234 }
60
+ let(:backoff) { 10 }
61
+ let(:host) { 'http://www.example.com' }
62
+ let(:port) { 8080 }
63
+ let(:options) do
64
+ {
65
+ path: path,
66
+ retries: retries,
67
+ backoff: backoff,
68
+ host: host,
69
+ port: port
70
+ }
71
+ end
72
+
73
+ subject { described_class.new(options) }
74
+
75
+ it 'sets passed in path' do
76
+ expect(subject.instance_variable_get(:@path)).to eq(path)
77
+ end
78
+
79
+ it 'sets passed in retries' do
80
+ expect(subject.instance_variable_get(:@retries)).to eq(retries)
81
+ end
82
+
83
+ it 'sets passed in backoff' do
84
+ expect(subject.instance_variable_get(:@backoff)).to eq(backoff)
85
+ end
86
+
87
+ it 'initializes a new Net::HTTP with passed in host and port' do
88
+ expect(Net::HTTP).to receive(:new).with(host, port)
89
+ described_class.new(options)
90
+ end
91
+ end
92
+ end
93
+
94
+ describe '#post' do
95
+ let(:response) { Net::HTTPResponse.new(http_version, status_code, response_body) }
96
+ let(:http_version) { 1.1 }
97
+ let(:status_code) { 200 }
98
+ let(:response_body) { {}.to_json }
99
+ let(:write_key) { 'abcdefg' }
100
+ let(:batch) { [] }
101
+
102
+ before do
103
+ allow(subject.instance_variable_get(:@http)).to receive(:request) { response }
104
+ allow(response).to receive(:body) { response_body }
105
+ end
106
+
107
+ it 'initalizes a new Net::HTTP::Post with path and default headers' do
108
+ path = subject.instance_variable_get(:@path)
109
+ default_headers = { 'Content-Type' => 'application/json', 'accept' => 'application/json' }
110
+ expect(Net::HTTP::Post).to receive(:new).with(path, default_headers).and_call_original
111
+ subject.post(write_key, batch)
112
+ end
113
+
114
+ it 'adds basic auth to the Net::HTTP::Post' do
115
+ expect_any_instance_of(Net::HTTP::Post).to receive(:basic_auth).with(write_key, nil)
116
+ subject.post(write_key, batch)
117
+ end
118
+
119
+ context 'with a stub' do
120
+ before do
121
+ allow(described_class).to receive(:stub) { true }
122
+ end
123
+
124
+ it 'returns a 200 response' do
125
+ expect(subject.post(write_key, batch).status).to eq(200)
126
+ end
127
+
128
+ it 'has a nil error' do
129
+ expect(subject.post(write_key, batch).error).to be_nil
130
+ end
131
+
132
+ it 'logs a debug statement' do
133
+ expect(subject.logger).to receive(:debug).with(/stubbed request to/)
134
+ subject.post(write_key, batch)
135
+ end
136
+ end
137
+
138
+ context 'a real request' do
139
+ context 'request is successful' do
140
+ let(:status_code) { 201 }
141
+ it 'returns a response code' do
142
+ expect(subject.post(write_key, batch).status).to eq(status_code)
143
+ end
144
+
145
+ it 'returns a nil error' do
146
+ expect(subject.post(write_key, batch).error).to be_nil
147
+ end
148
+ end
149
+
150
+ context 'request results in errorful response' do
151
+ let(:error) { 'this is an error' }
152
+ let(:response_body) { { error: error }.to_json }
153
+
154
+ it 'returns the parsed error' do
155
+ expect(subject.post(write_key, batch).error).to eq(error)
156
+ end
157
+ end
158
+
159
+ context 'request or parsing of response results in an exception' do
160
+ let(:response_body) { 'Malformed JSON ---' }
161
+
162
+ let(:backoff) { 0 }
163
+
164
+ subject { described_class.new(retries: retries, backoff: backoff) }
165
+
166
+ context 'remaining retries is > 1' do
167
+ let(:retries) { 2 }
168
+
169
+ it 'sleeps' do
170
+ expect(subject).to receive(:sleep).exactly(retries - 1).times
171
+ subject.post(write_key, batch)
172
+ end
173
+ end
174
+
175
+ context 'remaining retries is 1' do
176
+ let(:retries) { 1 }
177
+
178
+ it 'returns a -1 for status' do
179
+ expect(subject.post(write_key, batch).status).to eq(-1)
180
+ end
181
+
182
+ it 'has a connection error' do
183
+ expect(subject.post(write_key, batch).error).to match(/Connection error/)
184
+ end
185
+ end
186
+ end
187
+ end
188
+ end
189
+ end
190
+ end
191
+ end
@@ -0,0 +1,30 @@
1
+ require 'spec_helper'
2
+
3
+ module Segment
4
+ class Analytics
5
+ describe Response do
6
+ describe '#status' do
7
+ it { expect(subject).to respond_to(:status) }
8
+ end
9
+
10
+ describe '#error' do
11
+ it { expect(subject).to respond_to(:error) }
12
+ end
13
+
14
+ describe '#initialize' do
15
+ let(:status) { 404 }
16
+ let(:error) { 'Oh No' }
17
+
18
+ subject { described_class.new(status, error) }
19
+
20
+ it 'sets the instance variable status' do
21
+ expect(subject.instance_variable_get(:@status)).to eq(status)
22
+ end
23
+
24
+ it 'sets the instance variable error' do
25
+ expect(subject.instance_variable_get(:@error)).to eq(error)
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,96 @@
1
+ require 'spec_helper'
2
+
3
+ module Segment
4
+ class Analytics
5
+ describe Worker do
6
+ describe "#init" do
7
+ it 'accepts string keys' do
8
+ queue = Queue.new
9
+ worker = Segment::Analytics::Worker.new(queue, 'secret', 'batch_size' => 100)
10
+ expect(worker.instance_variable_get(:@batch_size)).to eq(100)
11
+ end
12
+ end
13
+
14
+ describe '#flush' do
15
+ before :all do
16
+ Segment::Analytics::Defaults::Request::BACKOFF = 0.1
17
+ end
18
+
19
+ after :all do
20
+ Segment::Analytics::Defaults::Request::BACKOFF = 30.0
21
+ end
22
+
23
+ it 'does not error if the endpoint is unreachable' do
24
+ expect do
25
+ Net::HTTP.any_instance.stub(:post).and_raise(Exception)
26
+
27
+ queue = Queue.new
28
+ queue << {}
29
+ worker = Segment::Analytics::Worker.new(queue, 'secret')
30
+ worker.run
31
+
32
+ expect(queue).to be_empty
33
+
34
+ Net::HTTP.any_instance.unstub(:post)
35
+ end.to_not raise_error
36
+ end
37
+
38
+ it 'executes the error handler if the request is invalid' do
39
+ Segment::Analytics::Request.any_instance.stub(:post).and_return(Segment::Analytics::Response.new(400, "Some error"))
40
+
41
+ on_error = Proc.new do |status, error|
42
+ puts "#{status}, #{error}"
43
+ end
44
+
45
+ expect(on_error).to receive(:call).once
46
+
47
+ queue = Queue.new
48
+ queue << {}
49
+ worker = Segment::Analytics::Worker.new queue, 'secret', :on_error => on_error
50
+ worker.run
51
+
52
+ Segment::Analytics::Request::any_instance.unstub(:post)
53
+
54
+ expect(queue).to be_empty
55
+ end
56
+
57
+ it 'does not call on_error if the request is good' do
58
+ on_error = Proc.new do |status, error|
59
+ puts "#{status}, #{error}"
60
+ end
61
+
62
+ expect(on_error).to_not receive(:call)
63
+
64
+ queue = Queue.new
65
+ queue << Requested::TRACK
66
+ worker = Segment::Analytics::Worker.new queue, 'testsecret', :on_error => on_error
67
+ worker.run
68
+
69
+ expect(queue).to be_empty
70
+ end
71
+ end
72
+
73
+ describe '#is_requesting?' do
74
+ it 'does not return true if there isn\'t a current batch' do
75
+ queue = Queue.new
76
+ worker = Segment::Analytics::Worker.new(queue, 'testsecret')
77
+
78
+ expect(worker.is_requesting?).to eq(false)
79
+ end
80
+
81
+ it 'returns true if there is a current batch' do
82
+ queue = Queue.new
83
+ queue << Requested::TRACK
84
+ worker = Segment::Analytics::Worker.new(queue, 'testsecret')
85
+
86
+ Thread.new do
87
+ worker.run
88
+ expect(worker.is_requesting?).to eq(false)
89
+ end
90
+
91
+ eventually { expect(worker.is_requesting?).to eq(true) }
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,106 @@
1
+ require 'spec_helper'
2
+
3
+ module Segment
4
+ class Analytics
5
+ describe Analytics do
6
+ let(:analytics) { Segment::Analytics.new :write_key => WRITE_KEY, :stub => true }
7
+
8
+ describe '#track' do
9
+ it 'errors without an event' do
10
+ expect { analytics.track(:user_id => 'user') }.to raise_error(ArgumentError)
11
+ end
12
+
13
+ it 'errors without a user_id' do
14
+ expect { analytics.track(:event => 'Event') }.to raise_error(ArgumentError)
15
+ end
16
+
17
+ it 'does not error with the required options' do
18
+ expect do
19
+ analytics.track Queued::TRACK
20
+ sleep(1)
21
+ end.to_not raise_error
22
+ end
23
+ end
24
+
25
+ describe '#identify' do
26
+ it 'errors without a user_id' do
27
+ expect { analytics.identify :traits => {} }.to raise_error(ArgumentError)
28
+ end
29
+
30
+ it 'does not error with the required options' do
31
+ analytics.identify Queued::IDENTIFY
32
+ sleep(1)
33
+ end
34
+ end
35
+
36
+ describe '#alias' do
37
+ it 'errors without from' do
38
+ expect { analytics.alias :user_id => 1234 }.to raise_error(ArgumentError)
39
+ end
40
+
41
+ it 'errors without to' do
42
+ expect { analytics.alias :previous_id => 1234 }.to raise_error(ArgumentError)
43
+ end
44
+
45
+ it 'does not error with the required options' do
46
+ expect do
47
+ analytics.alias ALIAS
48
+ sleep(1)
49
+ end.to_not raise_error
50
+ end
51
+ end
52
+
53
+ describe '#group' do
54
+ it 'errors without group_id' do
55
+ expect { analytics.group :user_id => 'foo' }.to raise_error(ArgumentError)
56
+ end
57
+
58
+ it 'errors without user_id or anonymous_id' do
59
+ expect { analytics.group :group_id => 'foo' }.to raise_error(ArgumentError)
60
+ end
61
+
62
+ it 'does not error with the required options' do
63
+ expect do
64
+ analytics.group Queued::GROUP
65
+ sleep(1)
66
+ end.to_not raise_error
67
+ end
68
+ end
69
+
70
+ describe '#page' do
71
+ it 'errors without user_id or anonymous_id' do
72
+ expect { analytics.page :name => 'foo' }.to raise_error(ArgumentError)
73
+ end
74
+
75
+ it 'does not error with the required options' do
76
+ expect do
77
+ analytics.page Queued::PAGE
78
+ sleep(1)
79
+ end.to_not raise_error
80
+ end
81
+ end
82
+
83
+ describe '#screen' do
84
+ it 'errors without user_id or anonymous_id' do
85
+ expect { analytics.screen :name => 'foo' }.to raise_error(ArgumentError)
86
+ end
87
+
88
+ it 'does not error with the required options' do
89
+ expect do
90
+ analytics.screen Queued::SCREEN
91
+ sleep(1)
92
+ end.to_not raise_error
93
+ end
94
+ end
95
+
96
+ describe '#flush' do
97
+ it 'flushes without error' do
98
+ expect do
99
+ analytics.identify Queued::IDENTIFY
100
+ analytics.flush
101
+ end.to_not raise_error
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,81 @@
1
+ require 'segment/analytics'
2
+ require 'wrong'
3
+ require 'active_support/time'
4
+
5
+ include Wrong
6
+
7
+ # Setting timezone for ActiveSupport::TimeWithZone to UTC
8
+ Time.zone = 'UTC'
9
+
10
+ module Segment
11
+ class Analytics
12
+ WRITE_KEY = 'testsecret'
13
+
14
+ TRACK = {
15
+ :event => 'Ruby Library test event',
16
+ :properties => {
17
+ :type => 'Chocolate',
18
+ :is_a_lie => true,
19
+ :layers => 20,
20
+ :created => Time.new
21
+ }
22
+ }
23
+
24
+ IDENTIFY = {
25
+ :traits => {
26
+ :likes_animals => true,
27
+ :instrument => 'Guitar',
28
+ :age => 25
29
+ }
30
+ }
31
+
32
+ ALIAS = {
33
+ :previous_id => 1234,
34
+ :user_id => 'abcd'
35
+ }
36
+
37
+ GROUP = {}
38
+
39
+ PAGE = {
40
+ :name => 'home'
41
+ }
42
+
43
+ SCREEN = {
44
+ :name => 'main'
45
+ }
46
+
47
+ USER_ID = 1234
48
+ GROUP_ID = 1234
49
+
50
+ # Hashes sent to the client, snake_case
51
+ module Queued
52
+ TRACK = TRACK.merge :user_id => USER_ID
53
+ IDENTIFY = IDENTIFY.merge :user_id => USER_ID
54
+ GROUP = GROUP.merge :group_id => GROUP_ID, :user_id => USER_ID
55
+ PAGE = PAGE.merge :user_id => USER_ID
56
+ SCREEN = SCREEN.merge :user_id => USER_ID
57
+ end
58
+
59
+ # Hashes which are sent from the worker, camel_cased
60
+ module Requested
61
+ TRACK = TRACK.merge({
62
+ :userId => USER_ID,
63
+ :type => 'track'
64
+ })
65
+
66
+ IDENTIFY = IDENTIFY.merge({
67
+ :userId => USER_ID,
68
+ :type => 'identify'
69
+ })
70
+
71
+ GROUP = GROUP.merge({
72
+ :groupId => GROUP_ID,
73
+ :userId => USER_ID,
74
+ :type => 'group'
75
+ })
76
+
77
+ PAGE = PAGE.merge :userId => USER_ID
78
+ SCREEN = SCREEN.merge :userId => USER_ID
79
+ end
80
+ end
81
+ end