11using System ;
22using System . Collections . Generic ;
33using System . IO ;
4+ using System . Linq ;
45using System . Text ;
56using Shouldly ;
67using Xunit ;
@@ -10,39 +11,48 @@ namespace dotenv.net.Tests;
1011public class ReaderTests : IDisposable
1112{
1213 private readonly string _tempFilePath ;
13- private readonly string _tempDirPath ;
14+
15+ private readonly string _testRootPath ;
16+ private readonly string _startPath ;
1417
1518 public ReaderTests ( )
1619 {
1720 _tempFilePath = Path . GetTempFileName ( ) ;
18- _tempDirPath = Path . Combine ( Path . GetTempPath ( ) , Guid . NewGuid ( ) . ToString ( ) ) ;
19- Directory . CreateDirectory ( _tempDirPath ) ;
21+
22+ // Create a unique root directory for this test run in the system's temp folder.
23+ _testRootPath = Path . Combine ( Path . GetTempPath ( ) , "DotEnvTests_" + Guid . NewGuid ( ) . ToString ( "N" ) ) ;
24+ Directory . CreateDirectory ( _testRootPath ) ;
25+
26+ _startPath = AppContext . BaseDirectory ;
2027 }
2128
2229 public void Dispose ( )
2330 {
2431 if ( File . Exists ( _tempFilePath ) )
2532 File . Delete ( _tempFilePath ) ;
26-
27- if ( Directory . Exists ( _tempDirPath ) )
28- Directory . Delete ( _tempDirPath , true ) ;
33+
34+ if ( Directory . Exists ( _testRootPath ) )
35+ Directory . Delete ( _testRootPath , true ) ;
2936 }
3037
3138 [ Theory ]
3239 [ InlineData ( null , false ) ]
3340 [ InlineData ( "" , false ) ]
3441 [ InlineData ( " " , false ) ]
35- public void ReadFileLines_InvalidPathAndIgnoreExceptionsFalse_ShouldThrowArgumentException ( string path , bool ignoreExceptions )
42+ public void ReadFileLines_InvalidPathAndIgnoreExceptionsFalse_ShouldThrowArgumentException ( string path ,
43+ bool ignoreExceptions )
3644 {
3745 Action act = ( ) => Reader . ReadFileLines ( path , ignoreExceptions , null ) ;
38- act . ShouldThrow < ArgumentException > ( ) . Message . ShouldContain ( "The file path cannot be null, empty or whitespace." ) ;
46+ act . ShouldThrow < ArgumentException > ( ) . Message
47+ . ShouldContain ( "The file path cannot be null, empty or whitespace." ) ;
3948 }
4049
4150 [ Theory ]
4251 [ InlineData ( null , true ) ]
4352 [ InlineData ( "" , true ) ]
4453 [ InlineData ( " " , true ) ]
45- public void ReadFileLines_InvalidPathAndIgnoreExceptionsTrue_ShouldReturnEmptySpan ( string path , bool ignoreExceptions )
54+ public void ReadFileLines_InvalidPathAndIgnoreExceptionsTrue_ShouldReturnEmptySpan ( string path ,
55+ bool ignoreExceptions )
4656 {
4757 var result = Reader . ReadFileLines ( path , ignoreExceptions , null ) . ToArray ( ) ;
4858 result . ShouldBeEmpty ( ) ;
@@ -51,9 +61,9 @@ public void ReadFileLines_InvalidPathAndIgnoreExceptionsTrue_ShouldReturnEmptySp
5161 [ Fact ]
5262 public void ReadFileLines_NonExistentFileAndIgnoreExceptionsFalse_ShouldThrowFileNotFoundException ( )
5363 {
54- var path = "nonexistent.env" ;
64+ const string path = "nonexistent.env" ;
5565 Action act = ( ) => Reader . ReadFileLines ( path , false , null ) ;
56- act . ShouldThrow < FileNotFoundException > ( ) . Message . ShouldContain ( path ) ;
66+ act . ShouldThrow < FileNotFoundException > ( ) . Message . ShouldContain ( path ) ;
5767 }
5868
5969 [ Fact ]
@@ -76,7 +86,7 @@ public void ReadFileLines_ValidFile_ShouldReturnLines()
7686 [ Fact ]
7787 public void ReadFileLines_WithCustomEncoding_ShouldReturnCorrectContent ( )
7888 {
79- var content = "KEY=üñîçø∂é" ;
89+ const string content = "KEY=üñîçø∂é" ;
8090 File . WriteAllText ( _tempFilePath , content , Encoding . UTF32 ) ;
8191 var result = Reader . ReadFileLines ( _tempFilePath , false , Encoding . UTF32 ) ;
8292 result [ 0 ] . ShouldBe ( content ) ;
@@ -109,8 +119,13 @@ public void MergeEnvKeyValues_NoArrays_ShouldReturnEmptyDictionary()
109119 [ Fact ]
110120 public void MergeEnvKeyValues_SingleArray_ShouldReturnAllItems ( )
111121 {
112- var input = new [ ] {
113- new [ ] { new KeyValuePair < string , string > ( "KEY1" , "value1" ) , new KeyValuePair < string , string > ( "KEY2" , "value2" ) }
122+ var input = new [ ]
123+ {
124+ new [ ]
125+ {
126+ new KeyValuePair < string , string > ( "KEY1" , "value1" ) ,
127+ new KeyValuePair < string , string > ( "KEY2" , "value2" )
128+ }
114129 } ;
115130 var result = Reader . MergeEnvKeyValues ( input , false ) ;
116131 result . ShouldBe ( new Dictionary < string , string > { { "KEY1" , "value1" } , { "KEY2" , "value2" } } ) ;
@@ -119,7 +134,8 @@ public void MergeEnvKeyValues_SingleArray_ShouldReturnAllItems()
119134 [ Fact ]
120135 public void MergeEnvKeyValues_MultipleArraysWithoutOverwrite_ShouldKeepFirstValue ( )
121136 {
122- var input = new [ ] {
137+ var input = new [ ]
138+ {
123139 new [ ] { new KeyValuePair < string , string > ( "KEY" , "first" ) } ,
124140 new [ ] { new KeyValuePair < string , string > ( "KEY" , "second" ) }
125141 } ;
@@ -131,7 +147,8 @@ public void MergeEnvKeyValues_MultipleArraysWithoutOverwrite_ShouldKeepFirstValu
131147 [ Fact ]
132148 public void MergeEnvKeyValues_MultipleArraysWithOverwrite_ShouldKeepLastValue ( )
133149 {
134- var input = new [ ] {
150+ var input = new [ ]
151+ {
135152 new [ ] { new KeyValuePair < string , string > ( "KEY" , "first" ) } ,
136153 new [ ] { new KeyValuePair < string , string > ( "KEY" , "second" ) }
137154 } ;
@@ -143,60 +160,78 @@ public void MergeEnvKeyValues_MultipleArraysWithOverwrite_ShouldKeepLastValue()
143160 [ Fact ]
144161 public void MergeEnvKeyValues_ComplexMerge_ShouldHandleAllCases ( )
145162 {
146- var input = new [ ] {
147- new [ ] {
163+ var input = new [ ]
164+ {
165+ new [ ]
166+ {
148167 new KeyValuePair < string , string > ( "KEY1" , "value1" ) ,
149168 new KeyValuePair < string , string > ( "KEY2" , "value2" )
150169 } ,
151- new [ ] {
170+ new [ ]
171+ {
152172 new KeyValuePair < string , string > ( "KEY2" , "updated" ) ,
153173 new KeyValuePair < string , string > ( "KEY3" , "value3" )
154174 }
155175 } ;
156176 var result = Reader . MergeEnvKeyValues ( input , true ) ;
157- result . ShouldBe ( new Dictionary < string , string > { { "KEY1" , "value1" } , { "KEY2" , "updated" } , { "KEY3" , "value3" } } ) ;
177+ result . ShouldBe ( new Dictionary < string , string >
178+ { { "KEY1" , "value1" } , { "KEY2" , "updated" } , { "KEY3" , "value3" } } ) ;
158179 }
159180
160181 [ Fact ]
161182 public void GetProbedEnvPath_FileNotFoundAndIgnoreExceptionsFalse_ShouldThrow ( )
162183 {
163- using var dir = new TempWorkingDirectory ( _tempDirPath ) ;
164- Action act = ( ) => Reader . GetProbedEnvPath ( levelsToSearch : 2 , ignoreExceptions : false ) ;
165- act . ShouldThrow < FileNotFoundException > ( )
166- . Message . ShouldContain ( DotEnvOptions . DefaultEnvFileName ) ;
184+ var levelsToSearch = 2 ;
185+ var exception = Should . Throw < FileNotFoundException > ( ( ) =>
186+ {
187+ Reader . GetProbedEnvPath ( levelsToSearch , ignoreExceptions : false ) ;
188+ } ) ;
189+
190+ exception . Message . ShouldContain ( $ "Could not find '{ DotEnvOptions . DefaultEnvFileName } '") ;
191+ exception . Message . ShouldContain ( $ "after searching { levelsToSearch } directory level(s) upwards.") ;
192+ exception . Message . ShouldContain ( "Searched paths:" ) ;
193+ exception . Message . ShouldContain ( _startPath ) ;
194+ exception . Message . ShouldContain ( "net9.0" ) ;
195+ exception . Message . ShouldContain ( "Debug" ) ;
167196 }
168197
169198 [ Fact ]
170- public void GetProbedEnvPath_FileNotFoundAndIgnoreExceptionsTrue_ShouldReturnNull ( )
199+ public void GetProbedEnvPath_FileNotFoundAndIgnoreExceptionsTrue_ShouldReturnEmpty ( )
171200 {
172- using var dir = new TempWorkingDirectory ( _tempDirPath ) ;
173201 var result = Reader . GetProbedEnvPath ( levelsToSearch : 2 , ignoreExceptions : true ) ;
174- result . ShouldBeNull ( ) ;
202+ result . ShouldBeEmpty ( ) ;
175203 }
176204
177205 [ Fact ]
178- public void GetProbedEnvPath_LevelsTooLow_ShouldNotFindFile ( )
206+ public void GetProbedEnvPath_ShouldFindFile_ThreeLevelsUp ( )
179207 {
180- var envPath = Path . Combine ( _tempDirPath , ".env" ) ;
181- File . WriteAllText ( envPath , "TEST=value" ) ;
182- var startDir = Path . Combine ( _tempDirPath , "subdir1" , "subdir2" , "subdir3" ) ;
183- Directory . CreateDirectory ( startDir ) ;
208+ var grandParentDirectory = Directory . GetParent ( _startPath ) ! . Parent ! . Parent ! . Parent ! . FullName ;
209+ var expectedPath = Path . Combine ( grandParentDirectory , DotEnvOptions . DefaultEnvFileName ) ;
184210
185- using var dir = new TempWorkingDirectory ( startDir ) ;
186- var result = Reader . GetProbedEnvPath ( levelsToSearch : 2 , ignoreExceptions : true ) ;
187- result . ShouldBeNull ( ) ;
211+ var result = Reader . GetProbedEnvPath ( levelsToSearch : 3 , ignoreExceptions : true ) . ToList ( ) ;
212+
213+ result . ShouldHaveSingleItem ( ) ;
214+ result . First ( ) . ShouldBe ( expectedPath ) ;
188215 }
189216
190- private class TempWorkingDirectory : IDisposable
217+ [ Fact ]
218+ public void GetProbedEnvPath_ShouldReturnEmpty_WhenFileExistsButIsOutOfSearchRange ( )
191219 {
192- private readonly string _originalDirectory ;
220+ // File is at level 3, but we only search up to level 1.
221+ var result = Reader . GetProbedEnvPath ( levelsToSearch : 1 , ignoreExceptions : true ) ;
193222
194- public TempWorkingDirectory ( string path )
223+ result . ShouldBeEmpty ( ) ;
224+ }
225+
226+ [ Fact ]
227+ public void GetProbedEnvPath_ShouldThrow_WhenFileExistsButIsOutOfSearchRange ( )
228+ {
229+ // File is at level 3, but we only search up to level 1.
230+ var exception = Should . Throw < FileNotFoundException > ( ( ) =>
195231 {
196- _originalDirectory = Directory . GetCurrentDirectory ( ) ;
197- Directory . SetCurrentDirectory ( path ) ;
198- }
232+ Reader . GetProbedEnvPath ( levelsToSearch : 1 , ignoreExceptions : false ) ;
233+ } ) ;
199234
200- public void Dispose ( ) => Directory . SetCurrentDirectory ( _originalDirectory ) ;
235+ exception . Message . ShouldContain ( $ "Could not find ' { DotEnvOptions . DefaultEnvFileName } '" ) ;
201236 }
202- }
237+ }
0 commit comments