Skip to content

Commit 46adc2a

Browse files
Merge pull request #63 from bolorundurowb/bug/#56/fix-exception-path-in-probe-for-env
#56 | fix exception path in probe for env
2 parents 16df909 + 04f7bf9 commit 46adc2a

5 files changed

Lines changed: 186 additions & 95 deletions

File tree

src/dotenv.net.Tests/DotEnvOptionsTests.cs

Lines changed: 47 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -31,19 +31,36 @@ public void Constructor_WithNullEncoding_ShouldUseUtf8()
3131
}
3232

3333
[Fact]
34-
public void WithEncoding_WithNullEncoding_ShouldUseUtf8()
34+
public void Constructor_WithProbeForEnvAndNoLevels_ShouldSetDefaults()
35+
{
36+
var options = new DotEnvOptions(probeForEnv: true, envFilePaths: null);
37+
options.ProbeForEnv.ShouldBeTrue();
38+
options.ProbeLevelsToSearch.ShouldBe(DotEnvOptions.DefaultProbeAscendLimit);
39+
}
40+
41+
[Fact]
42+
public void Constructor_WithProbeForEnvAndExplicitLevels_ShouldRespectProvidedLevel()
43+
{
44+
var options = new DotEnvOptions(probeForEnv: true, probeLevelsToSearch: 2, envFilePaths: null);
45+
options.ProbeLevelsToSearch.ShouldBe(2);
46+
}
47+
48+
[Fact]
49+
public void WithEncoding_WithNullEncoding_ShouldThrowException()
3550
{
3651
var options = new DotEnvOptions();
37-
options.WithEncoding(null!);
38-
options.Encoding.ShouldBe(Encoding.UTF8);
52+
Action action = () => options.WithEncoding(null!);
53+
action.ShouldThrow<ArgumentNullException>()
54+
.Message.ShouldBe("Encoding cannot be null (Parameter 'encoding')");
3955
}
4056

4157
[Fact]
42-
public void WithEnvFiles_WithNullParams_ShouldUseDefaultPath()
58+
public void WithEnvFiles_WithNullParams_ShouldThrowException()
4359
{
4460
var options = new DotEnvOptions();
45-
options.WithEnvFiles(null!);
46-
options.EnvFilePaths.ShouldBe([DotEnvOptions.DefaultEnvFileName]);
61+
Action action = () => options.WithEnvFiles(null!);
62+
action.ShouldThrow<ArgumentNullException>()
63+
.Message.ShouldBe("EnvFilePaths cannot be null (Parameter 'envFilePaths')");
4764
}
4865

4966
[Fact]
@@ -54,6 +71,22 @@ public void WithEnvFiles_WithEmptyParams_ShouldUseDefaultPath()
5471
options.EnvFilePaths.ShouldBe([DotEnvOptions.DefaultEnvFileName]);
5572
}
5673

74+
[Fact]
75+
public void WithEnvFiles_WhenProbeForEnvIsTrue_ShouldThrow()
76+
{
77+
var options = new DotEnvOptions(probeForEnv: true);
78+
var ex = Should.Throw<InvalidOperationException>(() => options.WithEnvFiles("custom.env"));
79+
ex.Message.ShouldBe("EnvFiles paths cannot be set when ProbeForEnv is true");
80+
}
81+
82+
[Fact]
83+
public void WithEnvFiles_WithNonEmptyList_ShouldSetPaths()
84+
{
85+
var options = new DotEnvOptions();
86+
options.WithEnvFiles("test.env");
87+
options.EnvFilePaths.ShouldBe(["test.env"]);
88+
}
89+
5790
[Fact]
5891
public void WithProbeForEnv_WithNegativeProbeLevels_ShouldUseDefaultProbeDepth()
5992
{
@@ -62,6 +95,14 @@ public void WithProbeForEnv_WithNegativeProbeLevels_ShouldUseDefaultProbeDepth()
6295
options.ProbeLevelsToSearch.ShouldBe(DotEnvOptions.DefaultProbeAscendLimit);
6396
}
6497

98+
[Fact]
99+
public void WithProbeForEnv_WhenCustomEnvFilePathSet_ShouldThrow()
100+
{
101+
var options = new DotEnvOptions(envFilePaths: new[] { "custom.env" });
102+
var ex = Should.Throw<InvalidOperationException>(() => options.WithProbeForEnv());
103+
ex.Message.ShouldBe("Cannot use ProbeForEnv when EnvFiles is set.");
104+
}
105+
65106
[Fact]
66107
public void WithoutProbeForEnv_ShouldResetProbeLevelsToDefault()
67108
{
@@ -71,14 +112,6 @@ public void WithoutProbeForEnv_ShouldResetProbeLevelsToDefault()
71112
options.ProbeLevelsToSearch.ShouldBe(DotEnvOptions.DefaultProbeAscendLimit);
72113
}
73114

74-
[Fact]
75-
public void WithDefaultEncoding_ShouldResetToUtf8()
76-
{
77-
var options = new DotEnvOptions().WithEncoding(Encoding.ASCII);
78-
options.WithDefaultEncoding();
79-
options.Encoding.ShouldBe(Encoding.UTF8);
80-
}
81-
82115
[Theory]
83116
[InlineData(true)]
84117
[InlineData(false)]

src/dotenv.net.Tests/ReaderTests.cs

Lines changed: 78 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Collections.Generic;
33
using System.IO;
4+
using System.Linq;
45
using System.Text;
56
using Shouldly;
67
using Xunit;
@@ -10,39 +11,48 @@ namespace dotenv.net.Tests;
1011
public 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+
}

src/dotenv.net/DotEnv.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ public static IDictionary<string, string> Read(DotEnvOptions? options = null)
1919
{
2020
options ??= new DotEnvOptions();
2121
var envFilePaths = options.ProbeForEnv
22-
? [Reader.GetProbedEnvPath(options.ProbeLevelsToSearch, options.IgnoreExceptions)]
22+
? Reader.GetProbedEnvPath(options.ProbeLevelsToSearch!.Value, options.IgnoreExceptions)
2323
: options.EnvFilePaths;
2424
var envFileKeyValues = envFilePaths
2525
.Select(envFilePath =>

0 commit comments

Comments
 (0)