Skip to content

Commit 4184c8c

Browse files
authored
feat: write backup files when streaming (#254)
1 parent 9874615 commit 4184c8c

File tree

4 files changed

+107
-20
lines changed

4 files changed

+107
-20
lines changed

lib/unleash/backup_file_writer.rb

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
require 'unleash/configuration'
2+
3+
module Unleash
4+
class BackupFileWriter
5+
def self.save!(toggle_data)
6+
Unleash.logger.debug "Will save toggles to disk now"
7+
8+
backup_file = Unleash.configuration.backup_file
9+
backup_file_tmp = "#{backup_file}.tmp"
10+
11+
File.open(backup_file_tmp, "w") do |file|
12+
file.write(toggle_data)
13+
end
14+
File.rename(backup_file_tmp, backup_file)
15+
rescue StandardError => e
16+
# This is not really the end of the world. Swallowing the exception.
17+
Unleash.logger.error "Unable to save backup file. Exception thrown #{e.class}:'#{e}'"
18+
Unleash.logger.error "stacktrace: #{e.backtrace}"
19+
end
20+
end
21+
end

lib/unleash/streaming_event_processor.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
require 'json'
2+
require 'unleash/backup_file_writer'
23

34
module Unleash
45
class StreamingEventProcessor
@@ -41,7 +42,8 @@ def handle_connected_event(event)
4142
def handle_updated_event(event)
4243
handle_delta_event(event.data)
4344

44-
# TODO: update backup file
45+
full_state = @toggle_engine.get_state
46+
Unleash::BackupFileWriter.save!(full_state)
4547
rescue JSON::ParserError => e
4648
Unleash.logger.error "Unable to parse JSON from streaming event data. Exception thrown #{e.class}: '#{e}'"
4749
Unleash.logger.debug "stacktrace: #{e.backtrace}"

lib/unleash/toggle_fetcher.rb

Lines changed: 2 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
require 'unleash/configuration'
22
require 'unleash/bootstrap/handler'
3+
require 'unleash/backup_file_writer'
34
require 'net/http'
45
require 'json'
56
require 'yggdrasil_engine'
@@ -54,25 +55,7 @@ def fetch
5455
# always synchronize with the local cache when fetching:
5556
update_engine_state!(response.body)
5657

57-
save! response.body
58-
end
59-
60-
def save!(toggle_data)
61-
Unleash.logger.debug "Will save toggles to disk now"
62-
63-
backup_file = Unleash.configuration.backup_file
64-
backup_file_tmp = "#{backup_file}.tmp"
65-
66-
self.toggle_lock.synchronize do
67-
File.open(backup_file_tmp, "w") do |file|
68-
file.write(toggle_data)
69-
end
70-
File.rename(backup_file_tmp, backup_file)
71-
end
72-
rescue StandardError => e
73-
# This is not really the end of the world. Swallowing the exception.
74-
Unleash.logger.error "Unable to save backup file. Exception thrown #{e.class}:'#{e}'"
75-
Unleash.logger.error "stacktrace: #{e.backtrace}"
58+
Unleash::BackupFileWriter.save! response.body
7659
end
7760

7861
private
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
RSpec.describe Unleash::StreamingEventProcessor do
2+
let(:engine) { YggdrasilEngine.new }
3+
let(:processor) { Unleash::StreamingEventProcessor.new(engine) }
4+
let(:backup_file) { Unleash.configuration.backup_file }
5+
6+
before do
7+
Unleash.configure do |config|
8+
config.url = 'http://test-url/'
9+
config.app_name = 'test-app'
10+
end
11+
Unleash.logger = Unleash.configuration.logger
12+
end
13+
14+
after do
15+
File.delete(backup_file) if File.exist?(backup_file)
16+
end
17+
18+
class TestEvent
19+
attr_reader :type, :data
20+
21+
def initialize(type, data)
22+
@type = type
23+
@data = data
24+
end
25+
end
26+
27+
def feature_event(name, enabled = true)
28+
{
29+
"events": [{
30+
"type": "feature-updated",
31+
"eventId": 1,
32+
"feature": {
33+
"name": name,
34+
"enabled": enabled,
35+
"strategies": [{ "name": "default" }]
36+
}
37+
}]
38+
}.to_json
39+
end
40+
41+
def backup_contains_feature?(name)
42+
return false unless File.exist?(backup_file)
43+
44+
parsed = JSON.parse(File.read(backup_file))
45+
feature_names = parsed['features'].map { |f| f['name'] }
46+
feature_names.include?(name)
47+
end
48+
49+
describe '#process_event' do
50+
it 'processes valid events and saves full engine state' do
51+
event = TestEvent.new('unleash-updated', feature_event('test-feature'))
52+
processor.process_event(event)
53+
54+
expect(engine.enabled?('test-feature', {})).to eq(true)
55+
expect(backup_contains_feature?('test-feature')).to eq(true)
56+
end
57+
58+
it 'ignores unknown event types' do
59+
event = TestEvent.new('unknown-event', feature_event('test-feature'))
60+
processor.process_event(event)
61+
62+
expect(File.exist?(backup_file)).to eq(false)
63+
expect(engine.enabled?('test-feature', {})).to be_falsy
64+
end
65+
66+
it 'saves full engine state, not partial event data' do
67+
processor.process_event(TestEvent.new('unleash-updated', feature_event('first-feature', true)))
68+
processor.process_event(TestEvent.new('unleash-updated', feature_event('second-feature', false)))
69+
70+
expect(backup_contains_feature?('first-feature')).to eq(true)
71+
expect(backup_contains_feature?('second-feature')).to eq(true)
72+
end
73+
74+
it 'handles invalid JSON gracefully without creating backup' do
75+
event = TestEvent.new('unleash-updated', 'invalid json')
76+
77+
expect { processor.process_event(event) }.not_to raise_error
78+
expect(File.exist?(backup_file)).to eq(false)
79+
end
80+
end
81+
end

0 commit comments

Comments
 (0)