WARNING: THIS SITE IS A MIRROR OF GITHUB.COM / IT CANNOT LOGIN OR REGISTER ACCOUNTS / THE CONTENTS ARE PROVIDED AS-IS / THIS SITE ASSUMES NO RESPONSIBILITY FOR ANY DISPLAYED CONTENT OR LINKS / IF YOU FOUND SOMETHING MAY NOT GOOD FOR EVERYONE, CONTACT ADMIN AT ilovescratch@foxmail.com
Skip to content

Commit d7cb457

Browse files
committed
feat: write backup files
1 parent 9874615 commit d7cb457

File tree

4 files changed

+179
-21
lines changed

4 files changed

+179
-21
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: 4 additions & 2 deletions
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
@@ -39,9 +40,10 @@ def handle_connected_event(event)
3940
end
4041

4142
def handle_updated_event(event)
42-
handle_delta_event(event.data)
43+
# Save the updated state to backup file first, even if engine update fails
44+
Unleash::BackupFileWriter.save!(event.data)
4345

44-
# TODO: update backup file
46+
handle_delta_event(event.data)
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: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
RSpec.describe Unleash::StreamingEventProcessor do
2+
let(:engine) { YggdrasilEngine.new }
3+
let(:processor) { Unleash::StreamingEventProcessor.new(engine) }
4+
5+
before do
6+
Unleash.configure do |config|
7+
config.url = 'http://streaming-test-url/'
8+
config.app_name = 'streaming-my-test-app'
9+
end
10+
11+
Unleash.logger = Unleash.configuration.logger
12+
end
13+
14+
after do
15+
WebMock.reset!
16+
File.delete(Unleash.configuration.backup_file) if File.exist?(Unleash.configuration.backup_file)
17+
end
18+
19+
describe '#process_event' do
20+
let(:updated_event_data) do
21+
{
22+
"events": [
23+
{
24+
"type": "feature-updated",
25+
"eventId": 2,
26+
"feature": {
27+
"name": "test-feature",
28+
"enabled": true,
29+
"strategies": [{"name": "default"}]
30+
}
31+
}
32+
]
33+
}.to_json
34+
end
35+
36+
let(:connected_event_data) do
37+
{
38+
"events": [
39+
{
40+
"type": "hydration",
41+
"eventId": 1,
42+
"features": [
43+
{
44+
"name": "test-feature",
45+
"enabled": true,
46+
"strategies": [{"name": "default"}]
47+
}
48+
],
49+
"segments": []
50+
}
51+
]
52+
}.to_json
53+
end
54+
55+
class TestEvent
56+
attr_reader :type, :data
57+
58+
def initialize(type, data)
59+
@type = type
60+
@data = data
61+
end
62+
end
63+
64+
context 'when processing unleash-updated event' do
65+
let(:event) { TestEvent.new('unleash-updated', updated_event_data) }
66+
67+
it 'creates a backup file with toggle data' do
68+
processor.process_event(event)
69+
70+
backup_file = Unleash.configuration.backup_file
71+
expect(File.exist?(backup_file)).to eq(true)
72+
73+
content = File.read(backup_file)
74+
expect(content).to eq(updated_event_data)
75+
76+
parsed = JSON.parse(content)
77+
expect(parsed).to include('events')
78+
expect(parsed['events'].first).to include('feature')
79+
expect(parsed['events'].first['feature']['name']).to eq('test-feature')
80+
end
81+
82+
it 'updates the engine state' do
83+
processor.process_event(event)
84+
85+
expect(engine.enabled?('test-feature', {})).to eq(true)
86+
end
87+
end
88+
89+
context 'when processing unleash-connected event' do
90+
let(:event) { TestEvent.new('unleash-connected', connected_event_data) }
91+
92+
it 'creates a backup file with toggle data' do
93+
processor.process_event(event)
94+
95+
backup_file = Unleash.configuration.backup_file
96+
expect(File.exist?(backup_file)).to eq(true)
97+
98+
content = File.read(backup_file)
99+
expect(content).to eq(connected_event_data)
100+
101+
parsed = JSON.parse(content)
102+
expect(parsed).to include('events')
103+
expect(parsed['events'].first).to include('features')
104+
expect(parsed['events'].first['features'].first['name']).to eq('test-feature')
105+
end
106+
107+
it 'updates the engine state' do
108+
processor.process_event(event)
109+
110+
expect(engine.enabled?('test-feature', {})).to eq(true)
111+
end
112+
end
113+
114+
context 'when processing unknown event type' do
115+
let(:event) { TestEvent.new('unknown-event', updated_event_data) }
116+
117+
it 'does not create a backup file' do
118+
processor.process_event(event)
119+
120+
backup_file = Unleash.configuration.backup_file
121+
expect(File.exist?(backup_file)).to eq(false)
122+
end
123+
124+
it 'does not update the engine state' do
125+
initial_enabled = engine.enabled?('test-feature', {})
126+
127+
processor.process_event(event)
128+
129+
expect(engine.enabled?('test-feature', {})).to eq(initial_enabled)
130+
end
131+
end
132+
133+
context 'when processing event with invalid JSON' do
134+
let(:invalid_data) { 'invalid json data that looks like real streaming data but is malformed' }
135+
let(:event) { TestEvent.new('unleash-updated', invalid_data) }
136+
137+
it 'handles JSON parse errors gracefully' do
138+
expect { processor.process_event(event) }.not_to raise_error
139+
end
140+
141+
it 'still creates a backup file with the raw data' do
142+
processor.process_event(event)
143+
144+
backup_file = Unleash.configuration.backup_file
145+
expect(File.exist?(backup_file)).to eq(true)
146+
147+
content = File.read(backup_file)
148+
expect(content).to eq(invalid_data)
149+
end
150+
end
151+
end
152+
end

0 commit comments

Comments
 (0)