1+ //
2+ // EnvironmentConfigurationTests.swift
3+ // BushelKit
4+ //
5+ // Created by Leo Dion.
6+ // Copyright © 2025 BrightDigit.
7+ //
8+ // Permission is hereby granted, free of charge, to any person
9+ // obtaining a copy of this software and associated documentation
10+ // files (the "Software"), to deal in the Software without
11+ // restriction, including without limitation the rights to use,
12+ // copy, modify, merge, publish, distribute, sublicense, and/or
13+ // sell copies of the Software, and to permit persons to whom the
14+ // Software is furnished to do so, subject to the following
15+ // conditions:
16+ //
17+ // The above copyright notice and this permission notice shall be
18+ // included in all copies or substantial portions of the Software.
19+ //
20+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21+ // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
22+ // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23+ // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
24+ // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
25+ // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26+ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
27+ // OTHER DEALINGS IN THE SOFTWARE.
28+ //
29+
30+ import Testing
31+ import Foundation
32+ @testable import BushelFoundation
33+ import BushelUtilities
34+
35+ struct EnvironmentConfigurationTests {
36+
37+ @Test ( " EnvironmentConfiguration properties read from environment " )
38+ func testEnvironmentProperties( ) {
39+ // Create mock environment
40+ let mockEnvironment : [ String : String ] = [
41+ " DISABLE_ASSERTION_FAILURE_FOR_ERROR " : " true " ,
42+ " DISALLOW_DATABASE_REBUILD " : " false " ,
43+ " ONBOARDING_OVERRIDE " : " skip " ,
44+ " RESET_APPLICATION " : " true " ,
45+ " RELEASE_VERSION " : " false " ,
46+ " REVIEW_ENGAGEMENT_THRESHOLD " : " 42 " ,
47+ " TRIGGER_TRACKING_PERMISSIONS_REQUEST " : " true "
48+ ]
49+
50+ // Create EnvironmentConfiguration with mock environment
51+ // Note: Since EnvironmentConfiguration.shared is static and properties are initialized
52+ // with ProcessInfo.processInfo.environment, we'll test the property wrapper separately
53+
54+ // Test individual property wrappers
55+ @EnvironmentProperty ( " DISABLE_ASSERTION_FAILURE_FOR_ERROR " , source: mockEnvironment)
56+ var disableAssertionFailure : Bool
57+
58+ @EnvironmentProperty ( " DISALLOW_DATABASE_REBUILD " , source: mockEnvironment)
59+ var disallowDatabaseRebuild : Bool
60+
61+ @EnvironmentProperty ( " ONBOARDING_OVERRIDE " , source: mockEnvironment)
62+ var onboardingOverride : OnboardingOverrideOption
63+
64+ @EnvironmentProperty ( " RESET_APPLICATION " , source: mockEnvironment)
65+ var resetApplication : Bool
66+
67+ @EnvironmentProperty ( " RELEASE_VERSION " , source: mockEnvironment)
68+ var releaseVersion : Bool
69+
70+ @EnvironmentProperty ( " REVIEW_ENGAGEMENT_THRESHOLD " , source: mockEnvironment)
71+ var reviewEngagementThreshold : Int
72+
73+ @EnvironmentProperty ( " TRIGGER_TRACKING_PERMISSIONS_REQUEST " , source: mockEnvironment)
74+ var triggerTrackingPermissionsRequest : Bool
75+
76+ #expect( disableAssertionFailure == true )
77+ #expect( disallowDatabaseRebuild == false )
78+ #expect( onboardingOverride == . skip)
79+ #expect( resetApplication == true )
80+ #expect( releaseVersion == false )
81+ #expect( reviewEngagementThreshold == 42 )
82+ #expect( triggerTrackingPermissionsRequest == true )
83+ }
84+
85+ @Test ( " EnvironmentConfiguration uses defaults when environment empty " )
86+ func testDefaultValues( ) {
87+ let emptyEnvironment : [ String : String ] = [ : ]
88+
89+ @EnvironmentProperty ( " DISABLE_ASSERTION_FAILURE_FOR_ERROR " , source: emptyEnvironment)
90+ var disableAssertionFailure : Bool
91+
92+ @EnvironmentProperty ( " DISALLOW_DATABASE_REBUILD " , source: emptyEnvironment)
93+ var disallowDatabaseRebuild : Bool
94+
95+ @EnvironmentProperty ( " ONBOARDING_OVERRIDE " , source: emptyEnvironment)
96+ var onboardingOverride : OnboardingOverrideOption
97+
98+ @EnvironmentProperty ( " RESET_APPLICATION " , source: emptyEnvironment)
99+ var resetApplication : Bool
100+
101+ @EnvironmentProperty ( " RELEASE_VERSION " , source: emptyEnvironment)
102+ var releaseVersion : Bool
103+
104+ @EnvironmentProperty ( " REVIEW_ENGAGEMENT_THRESHOLD " , source: emptyEnvironment)
105+ var reviewEngagementThreshold : Int
106+
107+ @EnvironmentProperty ( " TRIGGER_TRACKING_PERMISSIONS_REQUEST " , source: emptyEnvironment)
108+ var triggerTrackingPermissionsRequest : Bool
109+
110+ #expect( disableAssertionFailure == false ) // Bool default
111+ #expect( disallowDatabaseRebuild == false ) // Bool default
112+ #expect( onboardingOverride == . none) // OnboardingOverrideOption default
113+ #expect( resetApplication == false ) // Bool default
114+ #expect( releaseVersion == false ) // Bool default
115+ #expect( reviewEngagementThreshold == 0 ) // Int default
116+ #expect( triggerTrackingPermissionsRequest == false ) // Bool default
117+ }
118+
119+ @Test ( " EnvironmentConfiguration.Key raw values are correct " )
120+ func testKeyRawValues( ) {
121+ #expect( EnvironmentConfiguration . Key. disableAssertionFailureForError. rawValue == " DISABLE_ASSERTION_FAILURE_FOR_ERROR " )
122+ #expect( EnvironmentConfiguration . Key. disallowDatabaseRebuild. rawValue == " DISALLOW_DATABASE_REBUILD " )
123+ #expect( EnvironmentConfiguration . Key. onboardingOveride. rawValue == " ONBOARDING_OVERRIDE " )
124+ #expect( EnvironmentConfiguration . Key. resetApplication. rawValue == " RESET_APPLICATION " )
125+ #expect( EnvironmentConfiguration . Key. releaseVersion. rawValue == " RELEASE_VERSION " )
126+ #expect( EnvironmentConfiguration . Key. reviewEngagementThreshold. rawValue == " REVIEW_ENGAGEMENT_THRESHOLD " )
127+ #expect( EnvironmentConfiguration . Key. triggerTrackingPermissionsRequest. rawValue == " TRIGGER_TRACKING_PERMISSIONS_REQUEST " )
128+ }
129+
130+ @Test ( " EnvironmentConfiguration customMirror includes all properties " )
131+ func testCustomMirror( ) {
132+ let config = EnvironmentConfiguration . shared
133+ let mirror = config. customMirror
134+
135+ // Get all child labels from the mirror
136+ let childLabels = mirror. children. compactMap { $0. label }
137+
138+ // Verify all expected properties are in the mirror
139+ let expectedProperties = [
140+ " disableAssertionFailureForError " ,
141+ " disallowDatabaseRebuild " ,
142+ " onboardingOveride " ,
143+ " resetApplication " ,
144+ " releaseVersion " ,
145+ " reviewEngagementThreshold " ,
146+ " triggerTrackingPermissionsRequest "
147+ ]
148+
149+ for property in expectedProperties {
150+ #expect( childLabels. contains ( property) , " customMirror should include \( property) " )
151+ }
152+
153+ // Verify the mirror has exactly the expected number of properties
154+ #expect( mirror. children. count == expectedProperties. count)
155+ }
156+
157+ @Test ( " EnvironmentConfiguration.shared is a singleton " )
158+ func testSharedSingleton( ) {
159+ let instance1 = EnvironmentConfiguration . shared
160+ let instance2 = EnvironmentConfiguration . shared
161+
162+ // Since EnvironmentConfiguration is a struct, we can't compare identity,
163+ // but we can verify that .shared always returns a value
164+ #expect( EnvironmentConfiguration . shared != nil )
165+ }
166+
167+ @Test ( " EnvironmentProperty with Key enum " )
168+ func testEnvironmentPropertyWithKeyEnum( ) {
169+ let mockEnvironment : [ String : String ] = [
170+ " TRIGGER_TRACKING_PERMISSIONS_REQUEST " : " true "
171+ ]
172+
173+ @EnvironmentProperty (
174+ EnvironmentConfiguration . Key. triggerTrackingPermissionsRequest,
175+ source: mockEnvironment
176+ )
177+ var triggerTrackingPermissionsRequest : Bool
178+
179+ #expect( triggerTrackingPermissionsRequest == true )
180+ }
181+
182+ @Test ( " triggerTrackingPermissionsRequest property specifically " )
183+ func testTriggerTrackingPermissionsRequestProperty( ) {
184+ // Test with "true"
185+ let envTrue : [ String : String ] = [ " TRIGGER_TRACKING_PERMISSIONS_REQUEST " : " true " ]
186+ @EnvironmentProperty ( EnvironmentConfiguration . Key. triggerTrackingPermissionsRequest, source: envTrue)
187+ var trackingTrue : Bool
188+ #expect( trackingTrue == true )
189+
190+ // Test with "false"
191+ let envFalse : [ String : String ] = [ " TRIGGER_TRACKING_PERMISSIONS_REQUEST " : " false " ]
192+ @EnvironmentProperty ( EnvironmentConfiguration . Key. triggerTrackingPermissionsRequest, source: envFalse)
193+ var trackingFalse : Bool
194+ #expect( trackingFalse == false )
195+
196+ // Test with missing (should use default)
197+ let envMissing : [ String : String ] = [ : ]
198+ @EnvironmentProperty ( EnvironmentConfiguration . Key. triggerTrackingPermissionsRequest, source: envMissing)
199+ var trackingMissing : Bool
200+ #expect( trackingMissing == false ) // Bool default is false
201+
202+ // Test with invalid value (should use default)
203+ let envInvalid : [ String : String ] = [ " TRIGGER_TRACKING_PERMISSIONS_REQUEST " : " invalid " ]
204+ @EnvironmentProperty ( EnvironmentConfiguration . Key. triggerTrackingPermissionsRequest, source: envInvalid)
205+ var trackingInvalid : Bool
206+ #expect( trackingInvalid == false ) // Invalid bool strings default to false
207+ }
208+ }
0 commit comments