prefab-cloud-ruby 1.7.2 → 1.8.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 90c54906938e69580ab5b2aad4407560268e01d90ed4f2d3f595c51a1a308b9a
4
- data.tar.gz: 632d29a45418a731cf6bb536dacc32aadcc2f8559326f8b195069250a71ac4ff
3
+ metadata.gz: 9cc77b24b0cabb61c5ffb820116e8cf1a5122ceb4fae6e0d56245be4ab165eb9
4
+ data.tar.gz: 11e541dfe23555733292de53b1d3193291b81dd412cea70e6126b0c042f52f12
5
5
  SHA512:
6
- metadata.gz: 1921b09367845ee76bd84604f002bffc735139c3765417053ab8e5b73f059a1e909dec105249e38933f0dcf1f1d2bab9374c33c7703c79952da3617a22108817
7
- data.tar.gz: acfdbd82dbef635e9e3703ed3d3b9111d5a47e5382465406cd688b80c1104e6479fcb80ef2dc243be0fc9f6c8d3b2ab265a1541345ca7017e92dbbff6b7511ca
6
+ metadata.gz: 795e04559f5dab867e30ba1ce58a622c4c651b7214a1dcfadde0d8fcac1825c88699b019fc36a1a88cd56a94dc89fb9564d773262b4a0e4d07247c5bc60a1cd4
7
+ data.tar.gz: 2e4718e509231db718a65e18d766a7d481b72e344c9d73d766a17fbe91619a30694799b622a0fdbc419f440c93a449abb1fdc00dfb58ea75c0c5387f190df23e
data/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.8.0 - 2024-08-22
4
+
5
+ - Load config from belt and failover to suspenders (#195)
6
+
3
7
  ## 1.7.2 - 2024-06-24
4
8
 
5
9
  - Support JSON config values (#194)
data/Gemfile.lock CHANGED
@@ -101,7 +101,7 @@ GEM
101
101
  rake (~> 13.0)
102
102
  macaddr (1.7.2)
103
103
  systemu (~> 2.6.5)
104
- mini_portile2 (2.8.5)
104
+ mini_portile2 (2.8.7)
105
105
  minitest (5.22.3)
106
106
  minitest-focus (1.4.0)
107
107
  minitest (>= 4, < 6)
@@ -114,7 +114,7 @@ GEM
114
114
  multi_xml (0.6.0)
115
115
  multipart-post (2.4.0)
116
116
  mutex_m (0.2.0)
117
- nokogiri (1.16.3)
117
+ nokogiri (1.16.7)
118
118
  mini_portile2 (~> 2.8.2)
119
119
  racc (~> 1.4)
120
120
  oauth2 (1.4.11)
@@ -126,7 +126,7 @@ GEM
126
126
  psych (5.1.2)
127
127
  stringio
128
128
  public_suffix (5.0.4)
129
- racc (1.7.3)
129
+ racc (1.8.1)
130
130
  rack (3.0.10)
131
131
  rake (13.1.0)
132
132
  rchardet (1.8.0)
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.7.2
1
+ 1.8.0
data/lib/prefab/client.rb CHANGED
@@ -8,7 +8,7 @@ module Prefab
8
8
  MAX_SLEEP_SEC = 10
9
9
  BASE_SLEEP_SEC = 0.5
10
10
 
11
- attr_reader :namespace, :interceptor, :api_key, :prefab_api_url, :options, :instance_hash
11
+ attr_reader :namespace, :interceptor, :api_key, :options, :instance_hash
12
12
 
13
13
  def initialize(options = Prefab::Options.new)
14
14
  @options = options.is_a?(Prefab::Options) ? options : Prefab::Options.new(options)
@@ -23,9 +23,6 @@ module Prefab
23
23
  else
24
24
  @api_key = @options.api_key
25
25
  raise Prefab::Errors::InvalidApiKeyError, @api_key if @api_key.nil? || @api_key.empty? || api_key.count('-') < 1
26
-
27
- @prefab_api_url = @options.prefab_api_url
28
- LOG.debug "Prefab Connecting to: #{@prefab_api_url}"
29
26
  end
30
27
 
31
28
  context.clear
@@ -48,6 +45,10 @@ module Prefab
48
45
  @config_client ||= Prefab::ConfigClient.new(self, timeout)
49
46
  end
50
47
 
48
+ def stop
49
+ @config_client.stop
50
+ end
51
+
51
52
  def feature_flag_client
52
53
  @feature_flag_client ||= Prefab::FeatureFlagClient.new(self)
53
54
  end
@@ -111,7 +112,7 @@ module Prefab
111
112
  end
112
113
 
113
114
  def post(path, body)
114
- Prefab::HttpConnection.new(@options.prefab_api_url, @api_key).post(path, body)
115
+ Prefab::HttpConnection.new(@options.telemetry_destination, @api_key).post(path, body)
115
116
  end
116
117
 
117
118
  def inspect
@@ -3,19 +3,15 @@
3
3
  module Prefab
4
4
  class ConfigClient
5
5
  LOG = Prefab::InternalLogger.new(self)
6
- RECONNECT_WAIT = 5
7
6
  DEFAULT_CHECKPOINT_FREQ_SEC = 60
8
- SSE_READ_TIMEOUT = 300
9
7
  STALE_CACHE_WARN_HOURS = 5
10
- AUTH_USER = 'authuser'
8
+
11
9
  def initialize(base_client, timeout)
12
10
  @base_client = base_client
13
11
  @options = base_client.options
14
12
  LOG.debug 'Initialize ConfigClient'
15
13
  @timeout = timeout
16
14
 
17
- @stream_lock = Concurrent::ReadWriteLock.new
18
-
19
15
  @checkpoint_freq_secs = DEFAULT_CHECKPOINT_FREQ_SEC
20
16
 
21
17
  @config_loader = Prefab::ConfigLoader.new(@base_client)
@@ -34,9 +30,27 @@ module Prefab
34
30
  end
35
31
  end
36
32
 
33
+ def stop
34
+ @sse_config_client&.close
35
+ end
36
+
37
37
  def start_streaming
38
- @stream_lock.with_write_lock do
39
- start_sse_streaming_connection_thread(@config_loader.highwater_mark) if @streaming_thread.nil?
38
+ Thread.new do
39
+ # wait for the config loader to have a highwater mark before we connect the SSE
40
+ loop do
41
+ break if @config_loader.highwater_mark > 0
42
+
43
+ sleep 0.1
44
+ end
45
+
46
+ stream_lock = Concurrent::ReadWriteLock.new
47
+ @sse_config_client = Prefab::SSEConfigClient.new(@options, @config_loader)
48
+
49
+ @sse_config_client.start do |configs|
50
+ stream_lock.with_write_lock do
51
+ load_configs(configs, :sse)
52
+ end
53
+ end
40
54
  end
41
55
  end
42
56
 
@@ -104,29 +118,24 @@ module Prefab
104
118
  end
105
119
 
106
120
  def load_checkpoint
107
- success = load_checkpoint_api_cdn
108
-
109
- return if success
110
-
111
- success = load_checkpoint_api
112
-
121
+ success = load_source_checkpoint
113
122
  return if success
114
123
 
115
124
  success = load_cache
116
-
117
125
  return if success
118
126
 
119
127
  LOG.warn 'No success loading checkpoints'
120
128
  end
121
129
 
122
- def load_checkpoint_api_cdn
123
- conn = Prefab::HttpConnection.new("#{@options.url_for_api_cdn}/api/v1/configs/0", @base_client.api_key)
124
- load_url(conn, :remote_cdn_api)
125
- end
130
+ def load_source_checkpoint
131
+ @options.config_sources.each do |source|
132
+ conn = Prefab::HttpConnection.new("#{source}/api/v1/configs/0", @base_client.api_key)
133
+ result = load_url(conn, :remote_api)
126
134
 
127
- def load_checkpoint_api
128
- conn = Prefab::HttpConnection.new("#{@options.prefab_api_url}/api/v1/configs/0", @base_client.api_key)
129
- load_url(conn, :remote_api)
135
+ return true if result
136
+ end
137
+
138
+ return false
130
139
  end
131
140
 
132
141
  def load_url(conn, source)
@@ -263,26 +272,5 @@ module Prefab
263
272
  LOG.info presenter.to_s
264
273
  LOG.debug to_s
265
274
  end
266
-
267
- def start_sse_streaming_connection_thread(start_at_id)
268
- auth = "#{AUTH_USER}:#{@base_client.api_key}"
269
- auth_string = Base64.strict_encode64(auth)
270
- headers = {
271
- 'x-prefab-start-at-id' => start_at_id,
272
- 'Authorization' => "Basic #{auth_string}",
273
- 'X-PrefabCloud-Client-Version' => "prefab-cloud-ruby-#{Prefab::VERSION}"
274
- }
275
- url = "#{@base_client.prefab_api_url}/api/v1/sse/config"
276
- LOG.debug "SSE Streaming Connect to #{url} start_at #{start_at_id}"
277
- @streaming_thread = SSE::Client.new(url,
278
- headers: headers,
279
- read_timeout: SSE_READ_TIMEOUT,
280
- logger: Prefab::InternalLogger.new(SSE::Client)) do |client|
281
- client.on_event do |event|
282
- configs = PrefabProto::Configs.decode(Base64.decode64(event.data))
283
- load_configs(configs, :sse)
284
- end
285
- end
286
- end
287
275
  end
288
276
  end
@@ -46,16 +46,22 @@ module Prefab
46
46
  pool.post do
47
47
  LOG.debug "Uploading context shapes for #{to_ship.values.size}"
48
48
 
49
- shapes = PrefabProto::ContextShapes.new(
50
- shapes: to_ship.map do |name, shape|
51
- PrefabProto::ContextShape.new(
52
- name: name,
53
- field_types: shape
54
- )
55
- end
49
+ events = PrefabProto::TelemetryEvents.new(
50
+ instance_hash: instance_hash,
51
+ events: [
52
+ PrefabProto::TelemetryEvent.new(context_shapes:
53
+ PrefabProto::ContextShapes.new(
54
+ shapes: to_ship.map do |name, shape|
55
+ PrefabProto::ContextShape.new(
56
+ name: name,
57
+ field_types: shape
58
+ )
59
+ end
60
+ ))
61
+ ]
56
62
  )
57
63
 
58
- result = post('/api/v1/context-shapes', shapes)
64
+ result = post('/api/v1/telemetry', events)
59
65
 
60
66
  LOG.debug "Uploaded #{to_ship.values.size} shapes: #{result.status}"
61
67
  end
@@ -45,11 +45,13 @@ module Prefab
45
45
  end
46
46
 
47
47
  def PROP_IS_ONE_OF(criterion, properties)
48
- matches?(criterion, value_from_properties(criterion, properties), properties)
48
+ Array(value_from_properties(criterion, properties)).any? do |prop|
49
+ matches?(criterion, prop, properties)
50
+ end
49
51
  end
50
52
 
51
53
  def PROP_IS_NOT_ONE_OF(criterion, properties)
52
- !matches?(criterion, value_from_properties(criterion, properties), properties)
54
+ !PROP_IS_ONE_OF(criterion, properties)
53
55
  end
54
56
 
55
57
  def PROP_ENDS_WITH_ONE_OF(criterion, properties)
@@ -57,7 +59,7 @@ module Prefab
57
59
  end
58
60
 
59
61
  def PROP_DOES_NOT_END_WITH_ONE_OF(criterion, properties)
60
- !prop_ends_with_one_of?(criterion, value_from_properties(criterion, properties))
62
+ !PROP_ENDS_WITH_ONE_OF(criterion, properties)
61
63
  end
62
64
 
63
65
  def HIERARCHICAL_MATCH(criterion, properties)
@@ -51,15 +51,16 @@ module Prefab
51
51
  aggregate[path]['logger_name'] = path
52
52
  end
53
53
 
54
- loggers = PrefabProto::Loggers.new(
55
- loggers: aggregate.values,
56
- start_at: start_at_was,
57
- end_at: Prefab::TimeHelpers.now_in_ms,
58
- instance_hash: @client.instance_hash,
59
- namespace: @client.namespace
54
+ loggers = PrefabProto::LoggersTelemetryEvent.new(loggers: aggregate.values,
55
+ start_at: start_at_was,
56
+ end_at: Prefab::TimeHelpers.now_in_ms)
57
+
58
+ events = PrefabProto::TelemetryEvents.new(
59
+ instance_hash: instance_hash,
60
+ events: [PrefabProto::TelemetryEvent.new(loggers: loggers)]
60
61
  )
61
62
 
62
- result = post('/api/v1/known-loggers', loggers)
63
+ result = post('/api/v1/telemetry', events)
63
64
 
64
65
  LOG.debug "Uploaded #{to_ship.size} paths: #{result.status}"
65
66
  end
@@ -5,7 +5,10 @@ module Prefab
5
5
  class Options
6
6
  attr_reader :api_key
7
7
  attr_reader :namespace
8
- attr_reader :prefab_api_url
8
+ attr_reader :sources
9
+ attr_reader :sse_sources
10
+ attr_reader :telemetry_destination
11
+ attr_reader :config_sources
9
12
  attr_reader :on_no_default
10
13
  attr_reader :initialization_timeout_sec
11
14
  attr_reader :on_init_failure
@@ -38,10 +41,16 @@ module Prefab
38
41
  DEFAULT_MAX_EXAMPLE_CONTEXTS = 100_000
39
42
  DEFAULT_MAX_EVAL_SUMMARIES = 100_000
40
43
 
44
+ DEFAULT_SOURCES = [
45
+ "https://belt.prefab.cloud",
46
+ "https://suspenders.prefab.cloud",
47
+ ].freeze
48
+
41
49
  private def init(
50
+ sources: nil,
42
51
  api_key: ENV['PREFAB_API_KEY'],
43
52
  namespace: '',
44
- prefab_api_url: ENV['PREFAB_API_URL'] || 'https://api.prefab.cloud',
53
+ prefab_api_url: nil,
45
54
  on_no_default: ON_NO_DEFAULT::RAISE, # options :raise, :warn_and_return_nil,
46
55
  initialization_timeout_sec: 10, # how long to wait before on_init_failure
47
56
  on_init_failure: ON_INITIALIZATION_FAILURE::RAISE,
@@ -63,7 +72,6 @@ module Prefab
63
72
  )
64
73
  @api_key = api_key
65
74
  @namespace = namespace
66
- @prefab_api_url = remove_trailing_slash(prefab_api_url)
67
75
  @on_no_default = on_no_default
68
76
  @initialization_timeout_sec = initialization_timeout_sec
69
77
  @on_init_failure = on_init_failure
@@ -88,6 +96,25 @@ module Prefab
88
96
  @collect_example_contexts = false
89
97
  @collect_max_example_contexts = 0
90
98
 
99
+ if ENV['PREFAB_API_URL_OVERRIDE'] && ENV['PREFAB_API_URL_OVERRIDE'].length > 0
100
+ sources = ENV['PREFAB_API_URL_OVERRIDE']
101
+ end
102
+
103
+ @sources = Array(sources || DEFAULT_SOURCES).map {|source| remove_trailing_slash(source) }
104
+
105
+ @sse_sources = @sources
106
+ @config_sources = @sources
107
+
108
+ @telemetry_destination = @sources.select do |source|
109
+ source.start_with?('https://') && (source.include?("belt") || source.include?("suspenders"))
110
+ end.map do |source|
111
+ source.sub(/(belt|suspenders)\./, 'telemetry.')
112
+ end[0]
113
+
114
+ if prefab_api_url
115
+ warn '[DEPRECATION] prefab_api_url is deprecated. Please provide `sources` if you need to override the default sources'
116
+ end
117
+
91
118
  case context_upload_mode
92
119
  when :none
93
120
  # do nothing
@@ -140,11 +167,6 @@ module Prefab
140
167
  @collect_max_evaluation_summaries
141
168
  end
142
169
 
143
- # https://api.prefab.cloud -> https://api-prefab-cloud.global.ssl.fastly.net
144
- def url_for_api_cdn
145
- ENV['PREFAB_CDN_URL'] || "#{@prefab_api_url.gsub(/\./, '-')}.global.ssl.fastly.net"
146
- end
147
-
148
170
  def api_key_id
149
171
  @api_key&.split("-")&.first
150
172
  end
@@ -32,6 +32,10 @@ module Prefab
32
32
  @client.post(url, data)
33
33
  end
34
34
 
35
+ def instance_hash
36
+ @client.instance_hash
37
+ end
38
+
35
39
  def start_periodic_sync(sync_interval)
36
40
  @start_at = Prefab::TimeHelpers.now_in_ms
37
41
 
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Prefab
4
+ class SSEConfigClient
5
+ SSE_READ_TIMEOUT = 300
6
+ SECONDS_BETWEEN_RECONNECT = 5
7
+ AUTH_USER = 'authuser'
8
+ LOG = Prefab::InternalLogger.new(self)
9
+
10
+ def initialize(options, config_loader)
11
+ @options = options
12
+ @config_loader = config_loader
13
+ @connected = false
14
+ end
15
+
16
+ def close
17
+ @retry_thread&.kill
18
+ @client&.close
19
+ end
20
+
21
+ def start(&load_configs)
22
+ if @options.sse_sources.empty?
23
+ LOG.debug 'No SSE sources configured'
24
+ return
25
+ end
26
+
27
+ @client = connect(&load_configs)
28
+
29
+ closed_count = 0
30
+
31
+ @retry_thread = Thread.new do
32
+ loop do
33
+ sleep 1
34
+
35
+ if @client.closed?
36
+ closed_count += 1
37
+
38
+ if closed_count > SECONDS_BETWEEN_RECONNECT
39
+ closed_count = 0
40
+ connect(&load_configs)
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+
47
+ def connect(&load_configs)
48
+ url = "#{source}/api/v1/sse/config"
49
+ LOG.debug "SSE Streaming Connect to #{url} start_at #{@config_loader.highwater_mark}"
50
+
51
+ SSE::Client.new(url,
52
+ headers: headers,
53
+ read_timeout: SSE_READ_TIMEOUT,
54
+ logger: Prefab::InternalLogger.new(SSE::Client)) do |client|
55
+ client.on_event do |event|
56
+ configs = PrefabProto::Configs.decode(Base64.decode64(event.data))
57
+ load_configs.call(configs, :sse)
58
+ end
59
+
60
+ client.on_error do |error|
61
+ LOG.error "SSE Streaming Error: #{error} for url #{url}"
62
+
63
+ if error.is_a?(HTTP::ConnectionError)
64
+ client.close
65
+ end
66
+ end
67
+ end
68
+ end
69
+
70
+ def headers
71
+ auth = "#{AUTH_USER}:#{@options.api_key}"
72
+ auth_string = Base64.strict_encode64(auth)
73
+ return {
74
+ 'x-prefab-start-at-id' => @config_loader.highwater_mark,
75
+ 'Authorization' => "Basic #{auth_string}",
76
+ 'Accept' => 'text/event-stream',
77
+ 'X-PrefabCloud-Client-Version' => "prefab-cloud-ruby-#{Prefab::VERSION}"
78
+ }
79
+ end
80
+
81
+ def source
82
+ @source_index = @source_index.nil? ? 0 : @source_index + 1
83
+
84
+ if @source_index >= @options.sse_sources.size
85
+ @source_index = 0
86
+ end
87
+
88
+ return @options.sse_sources[@source_index]
89
+ end
90
+ end
91
+ end
@@ -46,6 +46,7 @@ require 'prefab/context'
46
46
  require 'prefab/logger_client'
47
47
  require 'active_support/deprecation'
48
48
  require 'active_support'
49
+ require 'prefab/sse_config_client'
49
50
  require 'prefab/client'
50
51
  require 'prefab/config_client_presenter'
51
52
  require 'prefab/config_client'
@@ -2,16 +2,16 @@
2
2
  # DO NOT EDIT THIS FILE DIRECTLY
3
3
  # Instead, edit Juwelier::Tasks in Rakefile, and run 'rake gemspec'
4
4
  # -*- encoding: utf-8 -*-
5
- # stub: prefab-cloud-ruby 1.7.2 ruby lib
5
+ # stub: prefab-cloud-ruby 1.8.0 ruby lib
6
6
 
7
7
  Gem::Specification.new do |s|
8
8
  s.name = "prefab-cloud-ruby".freeze
9
- s.version = "1.7.2"
9
+ s.version = "1.8.0"
10
10
 
11
11
  s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
12
12
  s.require_paths = ["lib".freeze]
13
13
  s.authors = ["Jeff Dwyer".freeze]
14
- s.date = "2024-06-24"
14
+ s.date = "2024-08-22"
15
15
  s.description = "Feature Flags, Live Config, and Dynamic Log Levels as a service".freeze
16
16
  s.email = "jdwyer@prefab.cloud".freeze
17
17
  s.extra_rdoc_files = [
@@ -74,6 +74,7 @@ Gem::Specification.new do |s|
74
74
  "lib/prefab/prefab.rb",
75
75
  "lib/prefab/rate_limit_cache.rb",
76
76
  "lib/prefab/resolved_config_presenter.rb",
77
+ "lib/prefab/sse_config_client.rb",
77
78
  "lib/prefab/time_helpers.rb",
78
79
  "lib/prefab/weighted_value_resolver.rb",
79
80
  "lib/prefab/yaml_config_parser.rb",
@@ -113,6 +114,7 @@ Gem::Specification.new do |s|
113
114
  "test/test_options.rb",
114
115
  "test/test_prefab.rb",
115
116
  "test/test_rate_limit_cache.rb",
117
+ "test/test_sse_config_client.rb",
116
118
  "test/test_weighted_value_resolver.rb"
117
119
  ]
118
120
  s.homepage = "http://github.com/prefab-cloud/prefab-cloud-ruby".freeze
@@ -20,6 +20,10 @@ class IntegrationTest
20
20
  @test_client = capture_telemetry(base_client)
21
21
  end
22
22
 
23
+ def teardown
24
+ test_client.stop
25
+ end
26
+
23
27
  def test_type
24
28
  if @data
25
29
  :telemetry
@@ -53,7 +57,11 @@ class IntegrationTest
53
57
  def parse_client_overrides(overrides)
54
58
  Hash[
55
59
  (overrides || {}).map do |(k, v)|
56
- [k.to_sym, v]
60
+ if k.to_s == "prefab_api_url"
61
+ [:sources, [v]]
62
+ else
63
+ [k.to_sym, v]
64
+ end
57
65
  end
58
66
  ]
59
67
  end
@@ -127,17 +135,22 @@ class IntegrationTest
127
135
  prefab_envs: ['unit_tests'],
128
136
  prefab_datasources: Prefab::Options::DATASOURCES::ALL,
129
137
  api_key: ENV['PREFAB_INTEGRATION_TEST_API_KEY'],
130
- prefab_api_url: 'https://api.staging-prefab.cloud',
138
+ sources: [
139
+ 'https://belt.staging-prefab.cloud',
140
+ 'https://suspenders.staging-prefab.cloud',
141
+ ],
131
142
  global_context: @global_context || {},
132
143
  }.merge(@client_overrides))
133
144
  end
134
145
 
135
146
  def capture_telemetry(client)
147
+ super_method = client.method(:post)
148
+
136
149
  client.define_singleton_method(:post) do |url, data|
137
150
  client.instance_variable_set(:@last_data_sent, data)
138
151
  client.instance_variable_set(:@last_post_endpoint, url)
139
152
 
140
- result = super(url, data)
153
+ result = super_method.call(url, data)
141
154
 
142
155
  client.instance_variable_set(:@last_post_result, result)
143
156
 
@@ -2,16 +2,12 @@
2
2
 
3
3
  module IntegrationTestHelpers
4
4
  SUBMODULE_PATH = 'test/prefab-cloud-integration-test-data'
5
- RAISE_IF_NO_TESTS_FOUND = ENV['PREFAB_INTEGRATION_TEST_RAISE'] == 'true'
6
5
 
7
6
  def self.find_integration_tests
8
7
  files = find_test_files
9
8
 
10
9
  if files.none?
11
- message = "No integration tests found"
12
- raise message if RAISE_IF_NO_TESTS_FOUND
13
-
14
- puts message
10
+ raise "No integration tests found"
15
11
  end
16
12
 
17
13
  files
@@ -44,7 +40,7 @@ module IntegrationTestHelpers
44
40
  end
45
41
  end
46
42
 
47
- [aggregator, ->(data) { data.loggers }, expected_loggers.values]
43
+ [aggregator, ->(data) { data.events[0].loggers.loggers }, expected_loggers.values]
48
44
  when "context_shape"
49
45
  aggregator = it.test_client.context_shape_aggregator
50
46
 
@@ -59,7 +55,7 @@ module IntegrationTestHelpers
59
55
  )
60
56
  end
61
57
 
62
- [aggregator, ->(data) { data.shapes }, expected]
58
+ [aggregator, ->(data) { data.events[0].context_shapes.shapes }, expected]
63
59
  when "evaluation_summary"
64
60
  aggregator = it.test_client.evaluation_summary_aggregator
65
61
 
@@ -47,11 +47,14 @@ module CommonHelpers
47
47
  ENV[key] = old_value
48
48
  end
49
49
 
50
+ EFFECTIVELY_NEVER = 99_999 # we sync manually
51
+
50
52
  DEFAULT_NEW_CLIENT_OPTIONS = {
51
53
  prefab_config_override_dir: 'none',
52
54
  prefab_config_classpath_dir: 'test',
53
55
  prefab_envs: ['unit_tests'],
54
- prefab_datasources: Prefab::Options::DATASOURCES::LOCAL_ONLY
56
+ prefab_datasources: Prefab::Options::DATASOURCES::LOCAL_ONLY,
57
+ collect_sync_interval: EFFECTIVELY_NEVER,
55
58
  }.freeze
56
59
 
57
60
  def new_client(overrides = {})
@@ -98,30 +98,36 @@ class TestContextShapeAggregator < Minitest::Test
98
98
 
99
99
  assert_equal [
100
100
  [
101
- '/api/v1/context-shapes',
102
- PrefabProto::ContextShapes.new(shapes: [
103
- PrefabProto::ContextShape.new(
104
- name: 'user', field_types: {
105
- 'age' => 4, 'dob' => 2, 'email' => 2, 'name' => 2
106
- }
107
- ),
108
- PrefabProto::ContextShape.new(
109
- name: 'subscription', field_types: {
110
- 'plan' => 2, 'free' => 5, 'trial' => 5
111
- }
112
- ),
113
- PrefabProto::ContextShape.new(
114
- name: 'device', field_types: {
115
- 'version' => 1, 'os' => 2, 'name' => 2
116
- }
117
- )
118
- ])
101
+ '/api/v1/telemetry',
102
+ PrefabProto::TelemetryEvents.new(
103
+ instance_hash: client.instance_hash,
104
+ events: [
105
+ PrefabProto::TelemetryEvent.new(context_shapes:
106
+
107
+ PrefabProto::ContextShapes.new(shapes: [
108
+ PrefabProto::ContextShape.new(
109
+ name: 'user', field_types: {
110
+ 'age' => 4, 'dob' => 2, 'email' => 2, 'name' => 2
111
+ }
112
+ ),
113
+ PrefabProto::ContextShape.new(
114
+ name: 'subscription', field_types: {
115
+ 'plan' => 2, 'free' => 5, 'trial' => 5
116
+ }
117
+ ),
118
+ PrefabProto::ContextShape.new(
119
+ name: 'device', field_types: {
120
+ 'version' => 1, 'os' => 2, 'name' => 2
121
+ }
122
+ )
123
+ ]))
124
+ ]
125
+ )
119
126
  ]
120
127
  ], requests
121
128
 
122
-
123
129
  assert_logged [
124
- "No success loading checkpoints",
130
+ 'No success loading checkpoints',
125
131
  "Couldn't Initialize In 0. Key some.key. Returning what we have"
126
132
  ]
127
133
  end
@@ -4,8 +4,6 @@ require 'test_helper'
4
4
  require 'timecop'
5
5
 
6
6
  class TestExampleContextsAggregator < Minitest::Test
7
- EFFECTIVELY_NEVER = 99_999 # we sync manually
8
-
9
7
  def test_record
10
8
  aggregator = Prefab::ExampleContextsAggregator.new(client: MockBaseClient.new, max_contexts: 2,
11
9
  sync_interval: EFFECTIVELY_NEVER)
@@ -8,10 +8,19 @@ require 'yaml'
8
8
  class TestIntegration < Minitest::Test
9
9
  IntegrationTestHelpers.find_integration_tests.map do |test_file|
10
10
  tests = YAML.load(File.read(test_file))['tests']
11
+ test_names = []
11
12
 
12
13
  tests.each do |test|
13
14
  test['cases'].each do |test_case|
14
- define_method(:"test_#{test['name']}_#{test_case['name']}") do
15
+ new_name = "test_#{test['name']}_#{test_case['name']}"
16
+
17
+ if test_names.include?(new_name)
18
+ raise "Duplicate test name: #{new_name}"
19
+ end
20
+
21
+ test_names << new_name
22
+
23
+ define_method(:"#{new_name}") do
15
24
  it = IntegrationTest.new(test_case)
16
25
 
17
26
  IntegrationTestHelpers.with_block_context_maybe(it.block_context) do
@@ -38,7 +47,7 @@ class TestIntegration < Minitest::Test
38
47
 
39
48
  wait_for -> { it.last_post_result&.status == 200 }
40
49
 
41
- assert it.endpoint == it.last_post_endpoint
50
+ assert_equal "/api/v1/telemetry", it.last_post_endpoint
42
51
 
43
52
  actual = get_actual_data[it.last_data_sent]
44
53
 
@@ -56,6 +65,8 @@ class TestIntegration < Minitest::Test
56
65
  "Prefab::ConfigClient -- Couldn't Initialize In 0.01. Key any-key. Returning what we have"
57
66
  ]
58
67
  end
68
+ ensure
69
+ it.teardown
59
70
  end
60
71
  end
61
72
  end
@@ -32,15 +32,27 @@ class TestLogPathAggregator < Minitest::Test
32
32
  client.log_path_aggregator.send(:sync)
33
33
  end
34
34
 
35
- assert_equal '/api/v1/known-loggers', requests[0][0]
36
- sent_logger = requests[0][1]
37
- assert_equal 'this.is.a.namespace', sent_logger.namespace
38
- assert_equal Prefab::TimeHelpers.now_in_ms, sent_logger.start_at
39
- assert_equal Prefab::TimeHelpers.now_in_ms, sent_logger.end_at
40
- assert_equal client.instance_hash, sent_logger.instance_hash
41
- assert_includes sent_logger.loggers,
42
- PrefabProto::Logger.new(logger_name: 'test.test_log_path_aggregator.test_sync', infos: 2,
43
- errors: 3)
35
+ assert_equal [
36
+ [
37
+ '/api/v1/telemetry',
38
+ PrefabProto::TelemetryEvents.new(
39
+ instance_hash: client.instance_hash,
40
+ events: [
41
+ PrefabProto::TelemetryEvent.new(loggers:
42
+
43
+ PrefabProto::LoggersTelemetryEvent.new(loggers: [
44
+ PrefabProto::Logger.new(
45
+ logger_name: 'test.test_log_path_aggregator.test_sync',
46
+ infos: 2,
47
+ errors: 3
48
+ )
49
+ ],
50
+ start_at: Prefab::TimeHelpers.now_in_ms,
51
+ end_at: Prefab::TimeHelpers.now_in_ms,))
52
+ ]
53
+ )
54
+ ]
55
+ ], requests
44
56
  end
45
57
  end
46
58
 
data/test/test_options.rb CHANGED
@@ -5,18 +5,31 @@ require 'test_helper'
5
5
  class TestOptions < Minitest::Test
6
6
  API_KEY = 'abcdefg'
7
7
 
8
- def test_prefab_api_url
9
- assert_equal 'https://api.prefab.cloud', Prefab::Options.new.prefab_api_url
8
+ def test_api_override_env_var
9
+ assert_equal Prefab::Options::DEFAULT_SOURCES, Prefab::Options.new.sources
10
10
 
11
- with_env 'PREFAB_API_URL', 'https://api.prefab.cloud' do
12
- assert_equal 'https://api.prefab.cloud', Prefab::Options.new.prefab_api_url
11
+ # blank doesn't take effect
12
+ with_env('PREFAB_API_URL_OVERRIDE', '') do
13
+ assert_equal Prefab::Options::DEFAULT_SOURCES, Prefab::Options.new.sources
13
14
  end
14
15
 
15
- with_env 'PREFAB_API_URL', 'https://api.prefab.cloud/' do
16
- assert_equal 'https://api.prefab.cloud', Prefab::Options.new.prefab_api_url
16
+ # non-blank does take effect
17
+ with_env('PREFAB_API_URL_OVERRIDE', 'https://override.example.com') do
18
+ assert_equal ["https://override.example.com"], Prefab::Options.new.sources
17
19
  end
18
20
  end
19
21
 
22
+ def test_overriding_sources
23
+ assert_equal Prefab::Options::DEFAULT_SOURCES, Prefab::Options.new.sources
24
+
25
+ # a plain string ends up wrapped in an array
26
+ source = 'https://example.com'
27
+ assert_equal [source], Prefab::Options.new(sources: source).sources
28
+
29
+ sources = ['https://example.com', 'https://example2.com']
30
+ assert_equal sources, Prefab::Options.new(sources: sources).sources
31
+ end
32
+
20
33
  def test_works_with_named_arguments
21
34
  assert_equal API_KEY, Prefab::Options.new(api_key: API_KEY).api_key
22
35
  end
@@ -0,0 +1,65 @@
1
+ require 'test_helper'
2
+
3
+ class TestSSEConfigClient < Minitest::Test
4
+ def test_client
5
+ sources = [
6
+ "https://api.staging-prefab.cloud/",
7
+ ]
8
+
9
+ options = Prefab::Options.new(sources: sources, api_key: ENV['PREFAB_INTEGRATION_TEST_API_KEY'])
10
+
11
+ config_loader = OpenStruct.new(highwater_mark: 4)
12
+
13
+ client = Prefab::SSEConfigClient.new(options, config_loader)
14
+
15
+ assert_equal 4, client.headers['x-prefab-start-at-id']
16
+
17
+ result = nil
18
+
19
+ # fake our load_configs block
20
+ client.start do |c, source|
21
+ result = c
22
+ assert_equal :sse, source
23
+ end
24
+
25
+ wait_for -> { !result.nil? }
26
+
27
+ assert result.configs.size > 30
28
+ ensure
29
+ client.close
30
+ end
31
+
32
+ def test_failing_over
33
+ sources = [
34
+ "https://does.not.exist.staging-prefab.cloud/",
35
+ "https://api.staging-prefab.cloud/",
36
+ ]
37
+
38
+ options = Prefab::Options.new(sources: sources, api_key: ENV['PREFAB_INTEGRATION_TEST_API_KEY'])
39
+
40
+ config_loader = OpenStruct.new(highwater_mark: 4)
41
+
42
+ client = Prefab::SSEConfigClient.new(options, config_loader)
43
+
44
+ assert_equal 4, client.headers['x-prefab-start-at-id']
45
+
46
+ result = nil
47
+
48
+ # fake our load_configs block
49
+ client.start do |c, source|
50
+ result = c
51
+ assert_equal :sse, source
52
+ end
53
+
54
+ wait_for -> { !result.nil? }, max_wait: 10
55
+
56
+ assert result.configs.size > 30
57
+ ensure
58
+ client.close
59
+
60
+ assert_logged [
61
+ /failed to connect: .*https:\/\/does.not.exist/,
62
+ /HTTP::ConnectionError/,
63
+ ]
64
+ end
65
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: prefab-cloud-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.7.2
4
+ version: 1.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeff Dwyer
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-06-24 00:00:00.000000000 Z
11
+ date: 2024-08-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby
@@ -261,6 +261,7 @@ files:
261
261
  - lib/prefab/prefab.rb
262
262
  - lib/prefab/rate_limit_cache.rb
263
263
  - lib/prefab/resolved_config_presenter.rb
264
+ - lib/prefab/sse_config_client.rb
264
265
  - lib/prefab/time_helpers.rb
265
266
  - lib/prefab/weighted_value_resolver.rb
266
267
  - lib/prefab/yaml_config_parser.rb
@@ -300,6 +301,7 @@ files:
300
301
  - test/test_options.rb
301
302
  - test/test_prefab.rb
302
303
  - test/test_rate_limit_cache.rb
304
+ - test/test_sse_config_client.rb
303
305
  - test/test_weighted_value_resolver.rb
304
306
  homepage: http://github.com/prefab-cloud/prefab-cloud-ruby
305
307
  licenses: