blueprint_config 1.3.1 → 1.5.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.
@@ -0,0 +1,145 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+ require 'blueprint_config'
5
+
6
+ describe BlueprintConfig::Configuration do
7
+ let(:config) { BlueprintConfig::Configuration.instance }
8
+
9
+ before do
10
+ # Mock Rails test environment
11
+ unless defined?(Rails)
12
+ module Rails
13
+ def self.env
14
+ ActiveSupport::StringInquirer.new('test')
15
+ end
16
+
17
+ def self.application
18
+ app = Object.new
19
+ credentials = {}
20
+ app.define_singleton_method(:credentials) { credentials }
21
+ app
22
+ end
23
+ end
24
+ end
25
+
26
+ # Initialize with memory backend
27
+ config.instance_variable_set(:@backends, nil)
28
+ config.instance_variable_set(:@config, nil)
29
+ BlueprintConfig.before_initialize.call
30
+ BlueprintConfig.after_initialize.call
31
+ end
32
+
33
+ after do
34
+ Object.send(:remove_const, :Rails) if defined?(Rails)
35
+ config.clear_memory! if config.backends[:memory]
36
+ end
37
+
38
+ describe '#set' do
39
+ context 'in test environment' do
40
+ it 'sets a simple value' do
41
+ config.set('foo', 'bar')
42
+ expect(config[:foo]).to eq('bar')
43
+ end
44
+
45
+ it 'sets a value with dotted key' do
46
+ config.set('smtp.server', 'test.example.com')
47
+ expect(config.dig(:smtp, :server)).to eq('test.example.com')
48
+ end
49
+
50
+ it 'accepts hash argument to set multiple values' do
51
+ config.set(
52
+ foo: 'bar',
53
+ smtp: { server: 'localhost', port: 1025 }
54
+ )
55
+
56
+ expect(config[:foo]).to eq('bar')
57
+ expect(config.dig(:smtp, :server)).to eq('localhost')
58
+ expect(config.dig(:smtp, :port)).to eq(1025)
59
+ end
60
+
61
+ it 'converts keys to strings' do
62
+ config.set(:symbol_key, 'value')
63
+ expect(config[:symbol_key]).to eq('value')
64
+ end
65
+
66
+ it 'triggers reload after setting' do
67
+ expect(config).to receive(:reload!).at_least(:once)
68
+ config.set('test', 'value')
69
+ end
70
+ end
71
+
72
+ context 'without memory backend' do
73
+ before do
74
+ # Remove memory backend
75
+ backends = config.backends
76
+ memory = backends[:memory]
77
+ backends.delete(:memory) if memory
78
+ end
79
+
80
+ it 'raises error when memory backend is not configured' do
81
+ expect { config.set('foo', 'bar') }.to raise_error(/Memory backend not configured/)
82
+ end
83
+ end
84
+ end
85
+
86
+ describe '#clear_memory!' do
87
+ context 'with memory backend' do
88
+ before do
89
+ config.set('foo', 'bar')
90
+ config.set('nested.key', 'value')
91
+ end
92
+
93
+ it 'clears all memory-stored values' do
94
+ expect(config[:foo]).to eq('bar')
95
+ config.clear_memory!
96
+ expect { config.fetch(:foo) }.to raise_error(KeyError)
97
+ end
98
+
99
+ it 'triggers reload after clearing' do
100
+ expect(config).to receive(:reload!).at_least(:once)
101
+ config.clear_memory!
102
+ end
103
+
104
+ it 'allows setting new values after clear' do
105
+ config.clear_memory!
106
+ config.set('new', 'value')
107
+ expect(config[:new]).to eq('value')
108
+ end
109
+ end
110
+
111
+ context 'without memory backend' do
112
+ before do
113
+ # Remove memory backend
114
+ backends = config.backends
115
+ memory = backends[:memory]
116
+ backends.delete(:memory) if memory
117
+ end
118
+
119
+ it 'does nothing when memory backend is not present' do
120
+ expect { config.clear_memory! }.not_to raise_error
121
+ end
122
+ end
123
+ end
124
+
125
+ describe 'non-test environment behavior' do
126
+ before do
127
+ # Mock production environment
128
+ allow(Rails).to receive(:env).and_return(ActiveSupport::StringInquirer.new('production'))
129
+
130
+ # Reinitialize without memory backend
131
+ config.instance_variable_set(:@backends, nil)
132
+ config.instance_variable_set(:@config, nil)
133
+ BlueprintConfig.before_initialize.call
134
+ BlueprintConfig.after_initialize.call
135
+ end
136
+
137
+ it 'does not include memory backend in production' do
138
+ expect(config.backends[:memory]).to be_nil
139
+ end
140
+
141
+ it 'raises error when trying to set in production' do
142
+ expect { config.set('foo', 'bar') }.to raise_error(/Memory backend not configured/)
143
+ end
144
+ end
145
+ end
@@ -0,0 +1,191 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+ require 'blueprint_config'
5
+
6
+ describe 'Memory backend integration' do
7
+ # Simulate Rails test environment
8
+ before(:all) do
9
+ unless defined?(Rails)
10
+ module Rails
11
+ def self.env
12
+ ActiveSupport::StringInquirer.new('test')
13
+ end
14
+
15
+ def self.application
16
+ # Mock credentials
17
+ app = Object.new
18
+ credentials = {}
19
+ app.define_singleton_method(:credentials) { credentials }
20
+ app
21
+ end
22
+ end
23
+ end
24
+ end
25
+
26
+ before do
27
+ # Reset configuration
28
+ BlueprintConfig.instance.instance_variable_set(:@backends, nil)
29
+ BlueprintConfig.instance.instance_variable_set(:@config, nil)
30
+
31
+ # Initialize with memory backend
32
+ BlueprintConfig.before_initialize.call
33
+ BlueprintConfig.after_initialize.call
34
+ end
35
+
36
+ after do
37
+ # Clean up memory after each test
38
+ BlueprintConfig.instance.clear_memory! if BlueprintConfig.instance.backends[:memory]
39
+ end
40
+
41
+ describe 'AppConfig.set' do
42
+ before do
43
+ BlueprintConfig.define_shortcut
44
+ end
45
+
46
+ context 'with simple values' do
47
+ it 'sets and retrieves string value' do
48
+ AppConfig.set('some.setting', 'value')
49
+ expect(AppConfig.some.setting).to eq('value')
50
+ end
51
+
52
+ it 'sets and retrieves numeric value' do
53
+ AppConfig.set('limit', 100)
54
+ expect(AppConfig.limit).to eq(100)
55
+ end
56
+
57
+ it 'sets and retrieves boolean value' do
58
+ AppConfig.set('enabled', true)
59
+ expect(AppConfig.enabled).to be true
60
+ end
61
+
62
+ it 'overwrites existing values' do
63
+ AppConfig.set('foo', 'bar')
64
+ AppConfig.set('foo', 'baz')
65
+ expect(AppConfig.foo).to eq('baz')
66
+ end
67
+ end
68
+
69
+ context 'with hash argument' do
70
+ it 'sets multiple values at once' do
71
+ AppConfig.set(
72
+ foo: 'bar',
73
+ baz: 'qux',
74
+ num: 42
75
+ )
76
+ expect(AppConfig.foo).to eq('bar')
77
+ expect(AppConfig.baz).to eq('qux')
78
+ expect(AppConfig.num).to eq(42)
79
+ end
80
+
81
+ it 'sets nested values' do
82
+ AppConfig.set(
83
+ smtp: {
84
+ server: 'localhost',
85
+ port: 1025
86
+ }
87
+ )
88
+ expect(AppConfig.smtp.server).to eq('localhost')
89
+ expect(AppConfig.smtp.port).to eq(1025)
90
+ end
91
+
92
+ it 'sets deeply nested values' do
93
+ AppConfig.set(
94
+ app: {
95
+ mail: {
96
+ smtp: {
97
+ settings: {
98
+ address: '127.0.0.1',
99
+ port: 587
100
+ }
101
+ }
102
+ }
103
+ }
104
+ )
105
+ expect(AppConfig.app.mail.smtp.settings.address).to eq('127.0.0.1')
106
+ expect(AppConfig.app.mail.smtp.settings.port).to eq(587)
107
+ end
108
+ end
109
+
110
+ context 'with dotted key and hash value' do
111
+ it 'combines key prefix with hash keys' do
112
+ AppConfig.set('mail.smtp', { server: 'localhost', port: 1025 })
113
+ expect(AppConfig.mail.smtp.server).to eq('localhost')
114
+ expect(AppConfig.mail.smtp.port).to eq(1025)
115
+ end
116
+ end
117
+
118
+ context 'memory backend priority' do
119
+ it 'overrides values from other backends' do
120
+ # Assuming there might be some default config
121
+ AppConfig.set('host', 'test.example.com')
122
+ expect(AppConfig.host).to eq('test.example.com')
123
+ end
124
+ end
125
+ end
126
+
127
+ describe 'AppConfig.clear_memory!' do
128
+ before do
129
+ BlueprintConfig.define_shortcut
130
+ end
131
+
132
+ it 'clears all memory-stored values' do
133
+ AppConfig.set('foo', 'bar')
134
+ AppConfig.set('baz', 'qux')
135
+
136
+ AppConfig.clear_memory!
137
+
138
+ # Values should revert to what's in other backends or nil
139
+ expect { AppConfig.foo! }.to raise_error(KeyError)
140
+ expect { AppConfig.baz! }.to raise_error(KeyError)
141
+ end
142
+
143
+ it 'can be used between test examples' do
144
+ AppConfig.set('test.value', 'first')
145
+ expect(AppConfig.test.value).to eq('first')
146
+
147
+ AppConfig.clear_memory!
148
+
149
+ AppConfig.set('test.value', 'second')
150
+ expect(AppConfig.test.value).to eq('second')
151
+ end
152
+ end
153
+
154
+ describe 'RSpec usage pattern' do
155
+ before do
156
+ BlueprintConfig.define_shortcut
157
+ end
158
+
159
+ context 'with some setting' do
160
+ before do
161
+ AppConfig.set('some.setting', 'value')
162
+ end
163
+
164
+ after do
165
+ AppConfig.clear_memory!
166
+ end
167
+
168
+ it 'works as expected' do
169
+ expect(AppConfig.some.setting).to eq('value')
170
+ end
171
+
172
+ it 'is isolated from other tests' do
173
+ expect(AppConfig.some.setting).to eq('value')
174
+ end
175
+ end
176
+
177
+ context 'with different setting' do
178
+ before do
179
+ AppConfig.set('some.setting', 'different')
180
+ end
181
+
182
+ after do
183
+ AppConfig.clear_memory!
184
+ end
185
+
186
+ it 'has its own value' do
187
+ expect(AppConfig.some.setting).to eq('different')
188
+ end
189
+ end
190
+ end
191
+ end
@@ -0,0 +1,132 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+ require 'blueprint_config/options_hash'
5
+ require 'blueprint_config/options_array'
6
+
7
+ describe BlueprintConfig::OptionsHash do
8
+ describe '#with_sources' do
9
+ describe 'simple values' do
10
+ it 'returns hash with value and source for each key' do
11
+ hash = BlueprintConfig::OptionsHash.new(
12
+ { foo: 'bar', num: 42 },
13
+ source: 'TestBackend'
14
+ )
15
+
16
+ result = hash.with_sources
17
+ expect(result).to eq({
18
+ foo: { value: 'bar', source: 'TestBackend' },
19
+ num: { value: 42, source: 'TestBackend' }
20
+ })
21
+ end
22
+ end
23
+
24
+ describe 'nested hashes' do
25
+ it 'recursively includes sources for nested values' do
26
+ hash = BlueprintConfig::OptionsHash.new(
27
+ {
28
+ smtp: {
29
+ server: 'localhost',
30
+ port: 1025
31
+ },
32
+ features: {
33
+ enabled: true
34
+ }
35
+ },
36
+ source: 'ConfigFile'
37
+ )
38
+
39
+ result = hash.with_sources
40
+ expect(result[:smtp][:server]).to eq({ value: 'localhost', source: 'ConfigFile' })
41
+ expect(result[:smtp][:port]).to eq({ value: 1025, source: 'ConfigFile' })
42
+ expect(result[:features][:enabled]).to eq({ value: true, source: 'ConfigFile' })
43
+ end
44
+ end
45
+
46
+ describe 'arrays' do
47
+ it 'handles arrays properly' do
48
+ hash = BlueprintConfig::OptionsHash.new(
49
+ {
50
+ servers: ['web1', 'web2', 'web3'],
51
+ ports: [80, 443]
52
+ },
53
+ source: 'YAMLBackend'
54
+ )
55
+
56
+ result = hash.with_sources
57
+ expect(result[:servers]).to eq({
58
+ value: ['web1', 'web2', 'web3'],
59
+ source: 'YAMLBackend'
60
+ })
61
+ expect(result[:ports]).to eq({
62
+ value: [80, 443],
63
+ source: 'YAMLBackend'
64
+ })
65
+ end
66
+ end
67
+
68
+ describe 'mixed sources after merge' do
69
+ it 'preserves individual sources after deep merge' do
70
+ # First configuration from YAML
71
+ yaml_config = BlueprintConfig::OptionsHash.new(
72
+ {
73
+ smtp: { server: 'smtp.example.com', port: 587 },
74
+ app: { name: 'MyApp' }
75
+ },
76
+ source: 'YAML'
77
+ )
78
+
79
+ # Override some values from credentials
80
+ creds_config = BlueprintConfig::OptionsHash.new(
81
+ {
82
+ smtp: { username: 'user@example.com' },
83
+ database: { password: 'secret' }
84
+ },
85
+ source: 'Credentials'
86
+ )
87
+
88
+ # Merge configurations
89
+ yaml_config.deep_merge!(creds_config)
90
+
91
+ result = yaml_config.with_sources
92
+
93
+ # Original YAML values
94
+ expect(result[:smtp][:server]).to eq({ value: 'smtp.example.com', source: 'YAML' })
95
+ expect(result[:smtp][:port]).to eq({ value: 587, source: 'YAML' })
96
+ expect(result[:app][:name]).to eq({ value: 'MyApp', source: 'YAML' })
97
+
98
+ # Values from credentials
99
+ expect(result[:smtp][:username]).to eq({ value: 'user@example.com', source: 'Credentials' })
100
+ expect(result[:database][:password]).to eq({ value: 'secret', source: 'Credentials' })
101
+ end
102
+ end
103
+
104
+ describe 'deeply nested structures' do
105
+ it 'handles deeply nested structures' do
106
+ hash = BlueprintConfig::OptionsHash.new(
107
+ {
108
+ level1: {
109
+ level2: {
110
+ level3: {
111
+ value: 'deep',
112
+ array: [1, 2, 3]
113
+ }
114
+ }
115
+ }
116
+ },
117
+ source: 'DeepSource'
118
+ )
119
+
120
+ result = hash.with_sources
121
+ expect(result[:level1][:level2][:level3][:value]).to eq({
122
+ value: 'deep',
123
+ source: 'DeepSource'
124
+ })
125
+ expect(result[:level1][:level2][:level3][:array]).to eq({
126
+ value: [1, 2, 3],
127
+ source: 'DeepSource'
128
+ })
129
+ end
130
+ end
131
+ end
132
+ end
data/spec/spec_helper.rb CHANGED
@@ -1,5 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'active_support'
4
+ require 'active_support/core_ext'
5
+ require 'active_support/string_inquirer'
3
6
  require 'blueprint_config'
4
7
 
5
8
  BlueprintConfig.root = File.dirname(__FILE__)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: blueprint_config
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.1
4
+ version: 1.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vladimir Elchinov
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2024-01-09 00:00:00.000000000 Z
12
+ date: 2025-07-17 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activerecord
@@ -108,6 +108,7 @@ files:
108
108
  - lib/blueprint_config/backend/base.rb
109
109
  - lib/blueprint_config/backend/credentials.rb
110
110
  - lib/blueprint_config/backend/env.rb
111
+ - lib/blueprint_config/backend/memory.rb
111
112
  - lib/blueprint_config/backend/yaml.rb
112
113
  - lib/blueprint_config/backend_collection.rb
113
114
  - lib/blueprint_config/configuration.rb
@@ -120,13 +121,19 @@ files:
120
121
  - lib/generators/blueprint_config/install/install_generator.rb
121
122
  - lib/generators/blueprint_config/install/templates/migration.rb.erb
122
123
  - spec/backend_collection_spec.rb
124
+ - spec/backend_source_tracking_spec.rb
123
125
  - spec/blueprint_config/backend/active_record_spec.rb
124
126
  - spec/blueprint_config/backend/env_spec.rb
127
+ - spec/blueprint_config/backend/memory_spec.rb
125
128
  - spec/blueprint_config/backend/yaml_spec.rb
126
129
  - spec/blueprint_config/options_array_spec.rb
127
130
  - spec/blueprint_config/options_hash_spec.rb
128
131
  - spec/config/app.yml
132
+ - spec/configuration_integration_spec.rb
133
+ - spec/configuration_memory_methods_spec.rb
129
134
  - spec/configuration_spec.rb
135
+ - spec/memory_integration_spec.rb
136
+ - spec/options_hash_with_sources_spec.rb
130
137
  - spec/spec_helper.rb
131
138
  homepage: https://github.com/railsblueprint/blueprint_config
132
139
  licenses:
@@ -153,11 +160,17 @@ specification_version: 4
153
160
  summary: Congifure Ruby apps
154
161
  test_files:
155
162
  - spec/backend_collection_spec.rb
163
+ - spec/backend_source_tracking_spec.rb
156
164
  - spec/blueprint_config/backend/active_record_spec.rb
157
165
  - spec/blueprint_config/backend/env_spec.rb
166
+ - spec/blueprint_config/backend/memory_spec.rb
158
167
  - spec/blueprint_config/backend/yaml_spec.rb
159
168
  - spec/blueprint_config/options_array_spec.rb
160
169
  - spec/blueprint_config/options_hash_spec.rb
161
170
  - spec/config/app.yml
171
+ - spec/configuration_integration_spec.rb
172
+ - spec/configuration_memory_methods_spec.rb
162
173
  - spec/configuration_spec.rb
174
+ - spec/memory_integration_spec.rb
175
+ - spec/options_hash_with_sources_spec.rb
163
176
  - spec/spec_helper.rb