spec/events_spec.rb in launchdarkly-server-sdk-5.6.2 vs spec/events_spec.rb in launchdarkly-server-sdk-5.7.0
- old
+ new
@@ -3,559 +3,521 @@
require "time"
describe LaunchDarkly::EventProcessor do
subject { LaunchDarkly::EventProcessor }
- let(:default_config) { LaunchDarkly::Config.new }
- let(:hc) { FakeHttpClient.new }
+ let(:default_config_opts) { { diagnostic_opt_out: true, logger: $null_log } }
+ let(:default_config) { LaunchDarkly::Config.new(default_config_opts) }
let(:user) { { key: "userkey", name: "Red" } }
let(:filtered_user) { { key: "userkey", privateAttrs: [ "name" ] } }
let(:numeric_user) { { key: 1, secondary: 2, ip: 3, country: 4, email: 5, firstName: 6, lastName: 7,
avatar: 8, name: 9, anonymous: false, custom: { age: 99 } } }
let(:stringified_numeric_user) { { key: '1', secondary: '2', ip: '3', country: '4', email: '5', firstName: '6',
lastName: '7', avatar: '8', name: '9', anonymous: false, custom: { age: 99 } } }
- after(:each) do
- if !@ep.nil?
- @ep.stop
+ def with_processor_and_sender(config)
+ sender = FakeEventSender.new
+ ep = subject.new("sdk_key", config, nil, nil, { event_sender: sender })
+ begin
+ yield ep, sender
+ ensure
+ ep.stop
end
end
it "queues identify event" do
- @ep = subject.new("sdk_key", default_config, hc)
- e = { kind: "identify", key: user[:key], user: user }
- @ep.add_event(e)
+ with_processor_and_sender(default_config) do |ep, sender|
+ e = { kind: "identify", key: user[:key], user: user }
+ ep.add_event(e)
- output = flush_and_get_events
- expect(output).to contain_exactly(e)
+ output = flush_and_get_events(ep, sender)
+ expect(output).to contain_exactly(e)
+ end
end
it "filters user in identify event" do
- config = LaunchDarkly::Config.new(all_attributes_private: true)
- @ep = subject.new("sdk_key", config, hc)
- e = { kind: "identify", key: user[:key], user: user }
- @ep.add_event(e)
+ config = LaunchDarkly::Config.new(default_config_opts.merge(all_attributes_private: true))
+ with_processor_and_sender(config) do |ep, sender|
+ e = { kind: "identify", key: user[:key], user: user }
+ ep.add_event(e)
- output = flush_and_get_events
- expect(output).to contain_exactly({
- kind: "identify",
- key: user[:key],
- creationDate: e[:creationDate],
- user: filtered_user
- })
+ output = flush_and_get_events(ep, sender)
+ expect(output).to contain_exactly({
+ kind: "identify",
+ key: user[:key],
+ creationDate: e[:creationDate],
+ user: filtered_user
+ })
+ end
end
it "stringifies built-in user attributes in identify event" do
- @ep = subject.new("sdk_key", default_config, hc)
- flag = { key: "flagkey", version: 11 }
- e = { kind: "identify", key: numeric_user[:key], user: numeric_user }
- @ep.add_event(e)
+ with_processor_and_sender(default_config) do |ep, sender|
+ flag = { key: "flagkey", version: 11 }
+ e = { kind: "identify", key: numeric_user[:key], user: numeric_user }
+ ep.add_event(e)
- output = flush_and_get_events
- expect(output).to contain_exactly(
- kind: "identify",
- key: numeric_user[:key].to_s,
- creationDate: e[:creationDate],
- user: stringified_numeric_user
- )
+ output = flush_and_get_events(ep, sender)
+ expect(output).to contain_exactly(
+ kind: "identify",
+ key: numeric_user[:key].to_s,
+ creationDate: e[:creationDate],
+ user: stringified_numeric_user
+ )
+ end
end
it "queues individual feature event with index event" do
- @ep = subject.new("sdk_key", default_config, hc)
- flag = { key: "flagkey", version: 11 }
- fe = {
- kind: "feature", key: "flagkey", version: 11, user: user,
- variation: 1, value: "value", trackEvents: true
- }
- @ep.add_event(fe)
+ with_processor_and_sender(default_config) do |ep, sender|
+ flag = { key: "flagkey", version: 11 }
+ fe = {
+ kind: "feature", key: "flagkey", version: 11, user: user,
+ variation: 1, value: "value", trackEvents: true
+ }
+ ep.add_event(fe)
- output = flush_and_get_events
- expect(output).to contain_exactly(
- eq(index_event(fe, user)),
- eq(feature_event(fe, flag, false, nil)),
- include(:kind => "summary")
- )
+ output = flush_and_get_events(ep, sender)
+ expect(output).to contain_exactly(
+ eq(index_event(fe, user)),
+ eq(feature_event(fe, flag, false, nil)),
+ include(:kind => "summary")
+ )
+ end
end
it "filters user in index event" do
- config = LaunchDarkly::Config.new(all_attributes_private: true)
- @ep = subject.new("sdk_key", config, hc)
- flag = { key: "flagkey", version: 11 }
- fe = {
- kind: "feature", key: "flagkey", version: 11, user: user,
- variation: 1, value: "value", trackEvents: true
- }
- @ep.add_event(fe)
+ config = LaunchDarkly::Config.new(default_config_opts.merge(all_attributes_private: true))
+ with_processor_and_sender(config) do |ep, sender|
+ flag = { key: "flagkey", version: 11 }
+ fe = {
+ kind: "feature", key: "flagkey", version: 11, user: user,
+ variation: 1, value: "value", trackEvents: true
+ }
+ ep.add_event(fe)
- output = flush_and_get_events
- expect(output).to contain_exactly(
- eq(index_event(fe, filtered_user)),
- eq(feature_event(fe, flag, false, nil)),
- include(:kind => "summary")
- )
+ output = flush_and_get_events(ep, sender)
+ expect(output).to contain_exactly(
+ eq(index_event(fe, filtered_user)),
+ eq(feature_event(fe, flag, false, nil)),
+ include(:kind => "summary")
+ )
+ end
end
it "stringifies built-in user attributes in index event" do
- @ep = subject.new("sdk_key", default_config, hc)
- flag = { key: "flagkey", version: 11 }
- fe = {
- kind: "feature", key: "flagkey", version: 11, user: numeric_user,
- variation: 1, value: "value", trackEvents: true
- }
- @ep.add_event(fe)
+ with_processor_and_sender(default_config) do |ep, sender|
+ flag = { key: "flagkey", version: 11 }
+ fe = {
+ kind: "feature", key: "flagkey", version: 11, user: numeric_user,
+ variation: 1, value: "value", trackEvents: true
+ }
+ ep.add_event(fe)
- output = flush_and_get_events
- expect(output).to contain_exactly(
- eq(index_event(fe, stringified_numeric_user)),
- eq(feature_event(fe, flag, false, nil)),
- include(:kind => "summary")
- )
+ output = flush_and_get_events(ep, sender)
+ expect(output).to contain_exactly(
+ eq(index_event(fe, stringified_numeric_user)),
+ eq(feature_event(fe, flag, false, nil)),
+ include(:kind => "summary")
+ )
+ end
end
it "can include inline user in feature event" do
- config = LaunchDarkly::Config.new(inline_users_in_events: true)
- @ep = subject.new("sdk_key", config, hc)
- flag = { key: "flagkey", version: 11 }
- fe = {
- kind: "feature", key: "flagkey", version: 11, user: user,
- variation: 1, value: "value", trackEvents: true
- }
- @ep.add_event(fe)
+ config = LaunchDarkly::Config.new(default_config_opts.merge(inline_users_in_events: true))
+ with_processor_and_sender(config) do |ep, sender|
+ flag = { key: "flagkey", version: 11 }
+ fe = {
+ kind: "feature", key: "flagkey", version: 11, user: user,
+ variation: 1, value: "value", trackEvents: true
+ }
+ ep.add_event(fe)
- output = flush_and_get_events
- expect(output).to contain_exactly(
- eq(feature_event(fe, flag, false, user)),
- include(:kind => "summary")
- )
+ output = flush_and_get_events(ep, sender)
+ expect(output).to contain_exactly(
+ eq(feature_event(fe, flag, false, user)),
+ include(:kind => "summary")
+ )
+ end
end
it "stringifies built-in user attributes in feature event" do
- config = LaunchDarkly::Config.new(inline_users_in_events: true)
- @ep = subject.new("sdk_key", config, hc)
- flag = { key: "flagkey", version: 11 }
- fe = {
- kind: "feature", key: "flagkey", version: 11, user: numeric_user,
- variation: 1, value: "value", trackEvents: true
- }
- @ep.add_event(fe)
+ config = LaunchDarkly::Config.new(default_config_opts.merge(inline_users_in_events: true))
+ with_processor_and_sender(config) do |ep, sender|
+ flag = { key: "flagkey", version: 11 }
+ fe = {
+ kind: "feature", key: "flagkey", version: 11, user: numeric_user,
+ variation: 1, value: "value", trackEvents: true
+ }
+ ep.add_event(fe)
- output = flush_and_get_events
- expect(output).to contain_exactly(
- eq(feature_event(fe, flag, false, stringified_numeric_user)),
- include(:kind => "summary")
- )
+ output = flush_and_get_events(ep, sender)
+ expect(output).to contain_exactly(
+ eq(feature_event(fe, flag, false, stringified_numeric_user)),
+ include(:kind => "summary")
+ )
+ end
end
it "filters user in feature event" do
- config = LaunchDarkly::Config.new(all_attributes_private: true, inline_users_in_events: true)
- @ep = subject.new("sdk_key", config, hc)
- flag = { key: "flagkey", version: 11 }
- fe = {
- kind: "feature", key: "flagkey", version: 11, user: user,
- variation: 1, value: "value", trackEvents: true
- }
- @ep.add_event(fe)
+ config = LaunchDarkly::Config.new(default_config_opts.merge(all_attributes_private: true, inline_users_in_events: true))
+ with_processor_and_sender(config) do |ep, sender|
+ flag = { key: "flagkey", version: 11 }
+ fe = {
+ kind: "feature", key: "flagkey", version: 11, user: user,
+ variation: 1, value: "value", trackEvents: true
+ }
+ ep.add_event(fe)
- output = flush_and_get_events
- expect(output).to contain_exactly(
- eq(feature_event(fe, flag, false, filtered_user)),
- include(:kind => "summary")
- )
+ output = flush_and_get_events(ep, sender)
+ expect(output).to contain_exactly(
+ eq(feature_event(fe, flag, false, filtered_user)),
+ include(:kind => "summary")
+ )
+ end
end
it "still generates index event if inline_users is true but feature event was not tracked" do
- config = LaunchDarkly::Config.new(inline_users_in_events: true)
- @ep = subject.new("sdk_key", config, hc)
- flag = { key: "flagkey", version: 11 }
- fe = {
- kind: "feature", key: "flagkey", version: 11, user: user,
- variation: 1, value: "value", trackEvents: false
- }
- @ep.add_event(fe)
+ config = LaunchDarkly::Config.new(default_config_opts.merge(inline_users_in_events: true))
+ with_processor_and_sender(config) do |ep, sender|
+ flag = { key: "flagkey", version: 11 }
+ fe = {
+ kind: "feature", key: "flagkey", version: 11, user: user,
+ variation: 1, value: "value", trackEvents: false
+ }
+ ep.add_event(fe)
- output = flush_and_get_events
- expect(output).to contain_exactly(
- eq(index_event(fe, user)),
- include(:kind => "summary")
- )
+ output = flush_and_get_events(ep, sender)
+ expect(output).to contain_exactly(
+ eq(index_event(fe, user)),
+ include(:kind => "summary")
+ )
+ end
end
it "sets event kind to debug if flag is temporarily in debug mode" do
- @ep = subject.new("sdk_key", default_config, hc)
- flag = { key: "flagkey", version: 11 }
- future_time = (Time.now.to_f * 1000).to_i + 1000000
- fe = {
- kind: "feature", key: "flagkey", version: 11, user: user,
- variation: 1, value: "value", trackEvents: false, debugEventsUntilDate: future_time
- }
- @ep.add_event(fe)
+ with_processor_and_sender(default_config) do |ep, sender|
+ flag = { key: "flagkey", version: 11 }
+ future_time = (Time.now.to_f * 1000).to_i + 1000000
+ fe = {
+ kind: "feature", key: "flagkey", version: 11, user: user,
+ variation: 1, value: "value", trackEvents: false, debugEventsUntilDate: future_time
+ }
+ ep.add_event(fe)
- output = flush_and_get_events
- expect(output).to contain_exactly(
- eq(index_event(fe, user)),
- eq(feature_event(fe, flag, true, user)),
- include(:kind => "summary")
- )
+ output = flush_and_get_events(ep, sender)
+ expect(output).to contain_exactly(
+ eq(index_event(fe, user)),
+ eq(feature_event(fe, flag, true, user)),
+ include(:kind => "summary")
+ )
+ end
end
it "can be both debugging and tracking an event" do
- @ep = subject.new("sdk_key", default_config, hc)
- flag = { key: "flagkey", version: 11 }
- future_time = (Time.now.to_f * 1000).to_i + 1000000
- fe = {
- kind: "feature", key: "flagkey", version: 11, user: user,
- variation: 1, value: "value", trackEvents: true, debugEventsUntilDate: future_time
- }
- @ep.add_event(fe)
+ with_processor_and_sender(default_config) do |ep, sender|
+ flag = { key: "flagkey", version: 11 }
+ future_time = (Time.now.to_f * 1000).to_i + 1000000
+ fe = {
+ kind: "feature", key: "flagkey", version: 11, user: user,
+ variation: 1, value: "value", trackEvents: true, debugEventsUntilDate: future_time
+ }
+ ep.add_event(fe)
- output = flush_and_get_events
- expect(output).to contain_exactly(
- eq(index_event(fe, user)),
- eq(feature_event(fe, flag, false, nil)),
- eq(feature_event(fe, flag, true, user)),
- include(:kind => "summary")
- )
+ output = flush_and_get_events(ep, sender)
+ expect(output).to contain_exactly(
+ eq(index_event(fe, user)),
+ eq(feature_event(fe, flag, false, nil)),
+ eq(feature_event(fe, flag, true, user)),
+ include(:kind => "summary")
+ )
+ end
end
it "ends debug mode based on client time if client time is later than server time" do
- @ep = subject.new("sdk_key", default_config, hc)
+ with_processor_and_sender(default_config) do |ep, sender|
+ # Pick a server time that is somewhat behind the client time
+ server_time = Time.now - 20
- # Pick a server time that is somewhat behind the client time
- server_time = (Time.now.to_f * 1000).to_i - 20000
+ # Send and flush an event we don't care about, just to set the last server time
+ sender.result = LaunchDarkly::Impl::EventSenderResult.new(true, false, server_time)
+ ep.add_event({ kind: "identify", user: user })
+ flush_and_get_events(ep, sender)
- # Send and flush an event we don't care about, just to set the last server time
- hc.set_server_time(server_time)
- @ep.add_event({ kind: "identify", user: { key: "otherUser" }})
- flush_and_get_events
+ # Now send an event with debug mode on, with a "debug until" time that is further in
+ # the future than the server time, but in the past compared to the client.
+ flag = { key: "flagkey", version: 11 }
+ debug_until = (server_time.to_f * 1000).to_i + 1000
+ fe = {
+ kind: "feature", key: "flagkey", version: 11, user: user,
+ variation: 1, value: "value", trackEvents: false, debugEventsUntilDate: debug_until
+ }
+ ep.add_event(fe)
- # Now send an event with debug mode on, with a "debug until" time that is further in
- # the future than the server time, but in the past compared to the client.
- flag = { key: "flagkey", version: 11 }
- debug_until = server_time + 1000
- fe = {
- kind: "feature", key: "flagkey", version: 11, user: user,
- variation: 1, value: "value", trackEvents: false, debugEventsUntilDate: debug_until
- }
- @ep.add_event(fe)
-
- # Should get a summary event only, not a full feature event
- output = flush_and_get_events
- expect(output).to contain_exactly(
- eq(index_event(fe, user)),
- include(:kind => "summary")
- )
+ # Should get a summary event only, not a full feature event
+ output = flush_and_get_events(ep, sender)
+ expect(output).to contain_exactly(
+ include(:kind => "summary")
+ )
+ end
end
it "ends debug mode based on server time if server time is later than client time" do
- @ep = subject.new("sdk_key", default_config, hc)
+ with_processor_and_sender(default_config) do |ep, sender|
+ # Pick a server time that is somewhat ahead of the client time
+ server_time = Time.now + 20
- # Pick a server time that is somewhat ahead of the client time
- server_time = (Time.now.to_f * 1000).to_i + 20000
+ # Send and flush an event we don't care about, just to set the last server time
+ sender.result = LaunchDarkly::Impl::EventSenderResult.new(true, false, server_time)
+ ep.add_event({ kind: "identify", user: user })
+ flush_and_get_events(ep, sender)
- # Send and flush an event we don't care about, just to set the last server time
- hc.set_server_time(server_time)
- @ep.add_event({ kind: "identify", user: { key: "otherUser" }})
- flush_and_get_events
+ # Now send an event with debug mode on, with a "debug until" time that is further in
+ # the future than the server time, but in the past compared to the client.
+ flag = { key: "flagkey", version: 11 }
+ debug_until = (server_time.to_f * 1000).to_i - 1000
+ fe = {
+ kind: "feature", key: "flagkey", version: 11, user: user,
+ variation: 1, value: "value", trackEvents: false, debugEventsUntilDate: debug_until
+ }
+ ep.add_event(fe)
- # Now send an event with debug mode on, with a "debug until" time that is further in
- # the future than the server time, but in the past compared to the client.
- flag = { key: "flagkey", version: 11 }
- debug_until = server_time - 1000
- fe = {
- kind: "feature", key: "flagkey", version: 11, user: user,
- variation: 1, value: "value", trackEvents: false, debugEventsUntilDate: debug_until
- }
- @ep.add_event(fe)
-
- # Should get a summary event only, not a full feature event
- output = flush_and_get_events
- expect(output).to contain_exactly(
- eq(index_event(fe, user)),
- include(:kind => "summary")
- )
+ # Should get a summary event only, not a full feature event
+ output = flush_and_get_events(ep, sender)
+ expect(output).to contain_exactly(
+ include(:kind => "summary")
+ )
+ end
end
it "generates only one index event for multiple events with same user" do
- @ep = subject.new("sdk_key", default_config, hc)
- flag1 = { key: "flagkey1", version: 11 }
- flag2 = { key: "flagkey2", version: 22 }
- future_time = (Time.now.to_f * 1000).to_i + 1000000
- fe1 = {
- kind: "feature", key: "flagkey1", version: 11, user: user,
- variation: 1, value: "value", trackEvents: true
- }
- fe2 = {
- kind: "feature", key: "flagkey2", version: 22, user: user,
- variation: 1, value: "value", trackEvents: true
- }
- @ep.add_event(fe1)
- @ep.add_event(fe2)
+ with_processor_and_sender(default_config) do |ep, sender|
+ flag1 = { key: "flagkey1", version: 11 }
+ flag2 = { key: "flagkey2", version: 22 }
+ future_time = (Time.now.to_f * 1000).to_i + 1000000
+ fe1 = {
+ kind: "feature", key: "flagkey1", version: 11, user: user,
+ variation: 1, value: "value", trackEvents: true
+ }
+ fe2 = {
+ kind: "feature", key: "flagkey2", version: 22, user: user,
+ variation: 1, value: "value", trackEvents: true
+ }
+ ep.add_event(fe1)
+ ep.add_event(fe2)
- output = flush_and_get_events
- expect(output).to contain_exactly(
- eq(index_event(fe1, user)),
- eq(feature_event(fe1, flag1, false, nil)),
- eq(feature_event(fe2, flag2, false, nil)),
- include(:kind => "summary")
- )
+ output = flush_and_get_events(ep, sender)
+ expect(output).to contain_exactly(
+ eq(index_event(fe1, user)),
+ eq(feature_event(fe1, flag1, false, nil)),
+ eq(feature_event(fe2, flag2, false, nil)),
+ include(:kind => "summary")
+ )
+ end
end
it "summarizes non-tracked events" do
- @ep = subject.new("sdk_key", default_config, hc)
- flag1 = { key: "flagkey1", version: 11 }
- flag2 = { key: "flagkey2", version: 22 }
- future_time = (Time.now.to_f * 1000).to_i + 1000000
- fe1 = {
- kind: "feature", key: "flagkey1", version: 11, user: user,
- variation: 1, value: "value1", default: "default1"
- }
- fe2 = {
- kind: "feature", key: "flagkey2", version: 22, user: user,
- variation: 2, value: "value2", default: "default2"
- }
- @ep.add_event(fe1)
- @ep.add_event(fe2)
+ with_processor_and_sender(default_config) do |ep, sender|
+ flag1 = { key: "flagkey1", version: 11 }
+ flag2 = { key: "flagkey2", version: 22 }
+ future_time = (Time.now.to_f * 1000).to_i + 1000000
+ fe1 = {
+ kind: "feature", key: "flagkey1", version: 11, user: user,
+ variation: 1, value: "value1", default: "default1"
+ }
+ fe2 = {
+ kind: "feature", key: "flagkey2", version: 22, user: user,
+ variation: 2, value: "value2", default: "default2"
+ }
+ ep.add_event(fe1)
+ ep.add_event(fe2)
- output = flush_and_get_events
- expect(output).to contain_exactly(
- eq(index_event(fe1, user)),
- eq({
- kind: "summary",
- startDate: fe1[:creationDate],
- endDate: fe2[:creationDate],
- features: {
- flagkey1: {
- default: "default1",
- counters: [
- { version: 11, variation: 1, value: "value1", count: 1 }
- ]
- },
- flagkey2: {
- default: "default2",
- counters: [
- { version: 22, variation: 2, value: "value2", count: 1 }
- ]
+ output = flush_and_get_events(ep, sender)
+ expect(output).to contain_exactly(
+ eq(index_event(fe1, user)),
+ eq({
+ kind: "summary",
+ startDate: fe1[:creationDate],
+ endDate: fe2[:creationDate],
+ features: {
+ flagkey1: {
+ default: "default1",
+ counters: [
+ { version: 11, variation: 1, value: "value1", count: 1 }
+ ]
+ },
+ flagkey2: {
+ default: "default2",
+ counters: [
+ { version: 22, variation: 2, value: "value2", count: 1 }
+ ]
+ }
}
- }
- })
- )
+ })
+ )
+ end
end
it "queues custom event with user" do
- @ep = subject.new("sdk_key", default_config, hc)
- e = { kind: "custom", key: "eventkey", user: user, data: { thing: "stuff" }, metricValue: 1.5 }
- @ep.add_event(e)
+ with_processor_and_sender(default_config) do |ep, sender|
+ e = { kind: "custom", key: "eventkey", user: user, data: { thing: "stuff" }, metricValue: 1.5 }
+ ep.add_event(e)
- output = flush_and_get_events
- expect(output).to contain_exactly(
- eq(index_event(e, user)),
- eq(custom_event(e, nil))
- )
+ output = flush_and_get_events(ep, sender)
+ expect(output).to contain_exactly(
+ eq(index_event(e, user)),
+ eq(custom_event(e, nil))
+ )
+ end
end
it "can include inline user in custom event" do
- config = LaunchDarkly::Config.new(inline_users_in_events: true)
- @ep = subject.new("sdk_key", config, hc)
- e = { kind: "custom", key: "eventkey", user: user, data: { thing: "stuff" } }
- @ep.add_event(e)
+ config = LaunchDarkly::Config.new(default_config_opts.merge(inline_users_in_events: true))
+ with_processor_and_sender(config) do |ep, sender|
+ e = { kind: "custom", key: "eventkey", user: user, data: { thing: "stuff" } }
+ ep.add_event(e)
- output = flush_and_get_events
- expect(output).to contain_exactly(
- eq(custom_event(e, user))
- )
+ output = flush_and_get_events(ep, sender)
+ expect(output).to contain_exactly(
+ eq(custom_event(e, user))
+ )
+ end
end
it "filters user in custom event" do
- config = LaunchDarkly::Config.new(all_attributes_private: true, inline_users_in_events: true)
- @ep = subject.new("sdk_key", config, hc)
- e = { kind: "custom", key: "eventkey", user: user, data: { thing: "stuff" } }
- @ep.add_event(e)
+ config = LaunchDarkly::Config.new(default_config_opts.merge(all_attributes_private: true, inline_users_in_events: true))
+ with_processor_and_sender(config) do |ep, sender|
+ e = { kind: "custom", key: "eventkey", user: user, data: { thing: "stuff" } }
+ ep.add_event(e)
- output = flush_and_get_events
- expect(output).to contain_exactly(
- eq(custom_event(e, filtered_user))
- )
+ output = flush_and_get_events(ep, sender)
+ expect(output).to contain_exactly(
+ eq(custom_event(e, filtered_user))
+ )
+ end
end
it "stringifies built-in user attributes in custom event" do
- config = LaunchDarkly::Config.new(inline_users_in_events: true)
- @ep = subject.new("sdk_key", config, hc)
- e = { kind: "custom", key: "eventkey", user: numeric_user }
- @ep.add_event(e)
+ config = LaunchDarkly::Config.new(default_config_opts.merge(inline_users_in_events: true))
+ with_processor_and_sender(config) do |ep, sender|
+ e = { kind: "custom", key: "eventkey", user: numeric_user }
+ ep.add_event(e)
- output = flush_and_get_events
- expect(output).to contain_exactly(
- eq(custom_event(e, stringified_numeric_user))
- )
+ output = flush_and_get_events(ep, sender)
+ expect(output).to contain_exactly(
+ eq(custom_event(e, stringified_numeric_user))
+ )
+ end
end
it "does a final flush when shutting down" do
- @ep = subject.new("sdk_key", default_config, hc)
- e = { kind: "identify", key: user[:key], user: user }
- @ep.add_event(e)
-
- @ep.stop
+ with_processor_and_sender(default_config) do |ep, sender|
+ e = { kind: "identify", key: user[:key], user: user }
+ ep.add_event(e)
+
+ ep.stop
- output = get_events_from_last_request
- expect(output).to contain_exactly(e)
+ output = sender.analytics_payloads.pop
+ expect(output).to contain_exactly(e)
+ end
end
it "sends nothing if there are no events" do
- @ep = subject.new("sdk_key", default_config, hc)
- @ep.flush
- expect(hc.get_request).to be nil
+ with_processor_and_sender(default_config) do |ep, sender|
+ ep.flush
+ ep.wait_until_inactive
+ expect(sender.analytics_payloads.empty?).to be true
+ end
end
- it "sends SDK key" do
- @ep = subject.new("sdk_key", default_config, hc)
- e = { kind: "identify", user: user }
- @ep.add_event(e)
+ it "stops posting events after unrecoverable error" do
+ with_processor_and_sender(default_config) do |ep, sender|
+ sender.result = LaunchDarkly::Impl::EventSenderResult.new(false, true, nil)
+ e = { kind: "identify", key: user[:key], user: user }
+ ep.add_event(e)
+ flush_and_get_events(ep, sender)
- @ep.flush
- @ep.wait_until_inactive
-
- expect(hc.get_request["authorization"]).to eq "sdk_key"
+ e = { kind: "identify", key: user[:key], user: user }
+ ep.add_event(e)
+ ep.flush
+ ep.wait_until_inactive
+ expect(sender.analytics_payloads.empty?).to be true
+ end
end
- it "sends unique payload IDs" do
- @ep = subject.new("sdk_key", default_config, hc)
- e = { kind: "identify", user: user }
-
- @ep.add_event(e)
- @ep.flush
- @ep.wait_until_inactive
- req0 = hc.get_request
+ describe "diagnostic events" do
+ let(:default_id) { LaunchDarkly::Impl::DiagnosticAccumulator.create_diagnostic_id('sdk_key') }
+ let(:diagnostic_config) { LaunchDarkly::Config.new(diagnostic_opt_out: false, logger: $null_log) }
- @ep.add_event(e)
- @ep.flush
- @ep.wait_until_inactive
- req1 = hc.get_request
+ def with_diagnostic_processor_and_sender(config)
+ sender = FakeEventSender.new
+ acc = LaunchDarkly::Impl::DiagnosticAccumulator.new(default_id)
+ ep = subject.new("sdk_key", config, nil, acc,
+ { diagnostic_recording_interval: 0.2, event_sender: sender })
+ begin
+ yield ep, sender
+ ensure
+ ep.stop
+ end
+ end
- id0 = req0["x-launchdarkly-payload-id"]
- id1 = req1["x-launchdarkly-payload-id"]
- expect(id0).not_to be_nil
- expect(id0).not_to eq ""
- expect(id1).not_to be nil
- expect(id1).not_to eq ""
- expect(id1).not_to eq id0
- end
+ it "sends init event" do
+ with_diagnostic_processor_and_sender(diagnostic_config) do |ep, sender|
+ event = sender.diagnostic_payloads.pop
+ expect(event).to include({
+ kind: 'diagnostic-init',
+ id: default_id
+ })
+ end
+ end
- def verify_unrecoverable_http_error(status)
- @ep = subject.new("sdk_key", default_config, hc)
- e = { kind: "identify", user: user }
- @ep.add_event(e)
+ it "sends periodic event" do
+ with_diagnostic_processor_and_sender(diagnostic_config) do |ep, sender|
+ init_event = sender.diagnostic_payloads.pop
+ periodic_event = sender.diagnostic_payloads.pop
+ expect(periodic_event).to include({
+ kind: 'diagnostic',
+ id: default_id,
+ droppedEvents: 0,
+ deduplicatedUsers: 0,
+ eventsInLastBatch: 0,
+ streamInits: []
+ })
+ end
+ end
- hc.set_response_status(status)
- @ep.flush
- @ep.wait_until_inactive
- expect(hc.get_request).not_to be_nil
- hc.reset
+ it "counts events in queue from last flush and dropped events" do
+ config = LaunchDarkly::Config.new(diagnostic_opt_out: false, capacity: 2, logger: $null_log)
+ with_diagnostic_processor_and_sender(config) do |ep, sender|
+ init_event = sender.diagnostic_payloads.pop
- @ep.add_event(e)
- @ep.flush
- @ep.wait_until_inactive
- expect(hc.get_request).to be_nil
- end
+ ep.add_event({ kind: 'identify', user: user })
+ ep.add_event({ kind: 'identify', user: user })
+ ep.add_event({ kind: 'identify', user: user })
+ flush_and_get_events(ep, sender)
- def verify_recoverable_http_error(status)
- @ep = subject.new("sdk_key", default_config, hc)
- e = { kind: "identify", user: user }
- @ep.add_event(e)
-
- hc.set_response_status(503)
- @ep.flush
- @ep.wait_until_inactive
-
- req0 = hc.get_request
- expect(req0).not_to be_nil
- req1 = hc.get_request
- expect(req1).not_to be_nil
- id0 = req0["x-launchdarkly-payload-id"]
- expect(id0).not_to be_nil
- expect(id0).not_to eq ""
- expect(req1["x-launchdarkly-payload-id"]).to eq id0
-
- expect(hc.get_request).to be_nil # no 3rd request
-
- # now verify that a subsequent flush still generates a request
- hc.reset
- @ep.add_event(e)
- @ep.flush
- @ep.wait_until_inactive
- expect(hc.get_request).not_to be_nil
- end
-
- it "stops posting events after getting a 401 error" do
- verify_unrecoverable_http_error(401)
- end
-
- it "stops posting events after getting a 403 error" do
- verify_unrecoverable_http_error(403)
- end
-
- it "retries after 408 error" do
- verify_recoverable_http_error(408)
- end
-
- it "retries after 429 error" do
- verify_recoverable_http_error(429)
- end
-
- it "retries after 503 error" do
- verify_recoverable_http_error(503)
- end
-
- it "retries flush once after connection error" do
- @ep = subject.new("sdk_key", default_config, hc)
- e = { kind: "identify", user: user }
- @ep.add_event(e)
-
- hc.set_exception(IOError.new("deliberate error"))
- @ep.flush
- @ep.wait_until_inactive
-
- expect(hc.get_request).not_to be_nil
- expect(hc.get_request).not_to be_nil
- expect(hc.get_request).to be_nil # no 3rd request
- end
-
- it "makes actual HTTP request with correct headers" do
- e = { kind: "identify", key: user[:key], user: user }
- with_server do |server|
- server.setup_ok_response("/bulk", "")
-
- @ep = subject.new("sdk_key", LaunchDarkly::Config.new(events_uri: server.base_uri.to_s))
- @ep.add_event(e)
- @ep.flush
-
- req = server.await_request
- expect(req.header).to include({
- "authorization" => [ "sdk_key" ],
- "content-type" => [ "application/json" ],
- "user-agent" => [ "RubyClient/" + LaunchDarkly::VERSION ],
- "x-launchdarkly-event-schema" => [ "3" ]
- })
+ periodic_event = sender.diagnostic_payloads.pop
+ expect(periodic_event).to include({
+ kind: 'diagnostic',
+ droppedEvents: 1,
+ eventsInLastBatch: 2
+ })
+ end
end
- end
- it "can use a proxy server" do
- e = { kind: "identify", key: user[:key], user: user }
- with_server do |server|
- server.setup_ok_response("/bulk", "")
+ it "counts deduplicated users" do
+ with_diagnostic_processor_and_sender(diagnostic_config) do |ep, sender|
+ init_event = sender.diagnostic_payloads.pop
- with_server(StubProxyServer.new) do |proxy|
- begin
- ENV["http_proxy"] = proxy.base_uri.to_s
- @ep = subject.new("sdk_key", LaunchDarkly::Config.new(events_uri: server.base_uri.to_s))
- @ep.add_event(e)
- @ep.flush
+ ep.add_event({ kind: 'custom', key: 'event1', user: user })
+ ep.add_event({ kind: 'custom', key: 'event2', user: user })
+ events = flush_and_get_events(ep, sender)
- req = server.await_request
- expect(req["content-type"]).to eq("application/json")
- ensure
- ENV["http_proxy"] = nil
- end
+ periodic_event = sender.diagnostic_payloads.pop
+ expect(periodic_event).to include({
+ kind: 'diagnostic',
+ deduplicatedUsers: 1
+ })
end
end
end
def index_event(e, user)
@@ -597,77 +559,28 @@
end
out[:metricValue] = e[:metricValue] if e.has_key?(:metricValue)
out
end
- def flush_and_get_events
- @ep.flush
- @ep.wait_until_inactive
- get_events_from_last_request
+ def flush_and_get_events(ep, sender)
+ ep.flush
+ ep.wait_until_inactive
+ sender.analytics_payloads.pop
end
- def get_events_from_last_request
- req = hc.get_request
- JSON.parse(req.body, symbolize_names: true)
- end
+ class FakeEventSender
+ attr_accessor :result
+ attr_reader :analytics_payloads
+ attr_reader :diagnostic_payloads
- class FakeHttpClient
def initialize
- reset
+ @result = LaunchDarkly::Impl::EventSenderResult.new(true, false, nil)
+ @analytics_payloads = Queue.new
+ @diagnostic_payloads = Queue.new
end
- def set_response_status(status)
- @status = status
- end
-
- def set_server_time(time_millis)
- @server_time = Time.at(time_millis.to_f / 1000)
- end
-
- def set_exception(e)
- @exception = e
- end
-
- def reset
- @requests = []
- @status = 200
- end
-
- def request(req)
- @requests.push(req)
- if @exception
- raise @exception
- else
- headers = {}
- if @server_time
- headers["Date"] = @server_time.httpdate
- end
- FakeResponse.new(@status ? @status : 200, headers)
- end
- end
-
- def start
- end
-
- def started?
- false
- end
-
- def finish
- end
-
- def get_request
- @requests.shift
- end
- end
-
- class FakeResponse
- include Net::HTTPHeader
-
- attr_reader :code
-
- def initialize(status, headers)
- @code = status.to_s
- initialize_http_header(headers)
+ def send_event_data(data, is_diagnostic)
+ (is_diagnostic ? @diagnostic_payloads : @analytics_payloads).push(JSON.parse(data, symbolize_names: true))
+ @result
end
end
end