From 9e21d1c90cdb830f7b77d25b61dcd6463c38f6f1 Mon Sep 17 00:00:00 2001 From: Chris Rogus Date: Mon, 30 Nov 2020 02:11:08 -0500 Subject: [PATCH] Search for .env file higher than current/given dir --- .env_much_higher | 1 + DotNetEnv.sln | 17 ++- README.md | 57 ++++++++-- src/DotNetEnv/Env.cs | 54 ++++++---- src/DotNetEnv/LoadOptions.cs | 54 ++++++++++ test/.env | 1 + test/DotNetEnv.Tests/EnvTests.cs | 49 +++++---- test/DotNetEnv.Tests/LoadOptionsTests.cs | 102 ++++++++++++++++++ .../DotNetEnvTraverse.Tests.csproj | 20 ++++ test/DotNetEnvTraverse.Tests/TraverseTests.cs | 69 ++++++++++++ 10 files changed, 374 insertions(+), 50 deletions(-) create mode 100644 .env_much_higher create mode 100644 src/DotNetEnv/LoadOptions.cs create mode 100644 test/.env create mode 100644 test/DotNetEnv.Tests/LoadOptionsTests.cs create mode 100644 test/DotNetEnvTraverse.Tests/DotNetEnvTraverse.Tests.csproj create mode 100644 test/DotNetEnvTraverse.Tests/TraverseTests.cs diff --git a/.env_much_higher b/.env_much_higher new file mode 100644 index 0000000..73acaf2 --- /dev/null +++ b/.env_much_higher @@ -0,0 +1 @@ +TEST="See DotNetEnvTraverse.Tests for why this is here" diff --git a/DotNetEnv.sln b/DotNetEnv.sln index b84fae1..2101248 100644 --- a/DotNetEnv.sln +++ b/DotNetEnv.sln @@ -1,4 +1,4 @@ - + Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 VisualStudioVersion = 15.0.26124.0 @@ -11,6 +11,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{DECBE6ED-4 EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNetEnv.Tests", "test\DotNetEnv.Tests\DotNetEnv.Tests.csproj", "{062FD47B-69A9-4808-97CA-CF24CD65B0F0}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNetEnvTraverse.Tests", "test\DotNetEnvTraverse.Tests\DotNetEnvTraverse.Tests.csproj", "{947252C9-700A-4A6E-A9EA-DFA6631916EF}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -48,9 +50,22 @@ Global {062FD47B-69A9-4808-97CA-CF24CD65B0F0}.Release|x64.Build.0 = Release|x64 {062FD47B-69A9-4808-97CA-CF24CD65B0F0}.Release|x86.ActiveCfg = Release|x86 {062FD47B-69A9-4808-97CA-CF24CD65B0F0}.Release|x86.Build.0 = Release|x86 + {947252C9-700A-4A6E-A9EA-DFA6631916EF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {947252C9-700A-4A6E-A9EA-DFA6631916EF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {947252C9-700A-4A6E-A9EA-DFA6631916EF}.Debug|x64.ActiveCfg = Debug|Any CPU + {947252C9-700A-4A6E-A9EA-DFA6631916EF}.Debug|x64.Build.0 = Debug|Any CPU + {947252C9-700A-4A6E-A9EA-DFA6631916EF}.Debug|x86.ActiveCfg = Debug|Any CPU + {947252C9-700A-4A6E-A9EA-DFA6631916EF}.Debug|x86.Build.0 = Debug|Any CPU + {947252C9-700A-4A6E-A9EA-DFA6631916EF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {947252C9-700A-4A6E-A9EA-DFA6631916EF}.Release|Any CPU.Build.0 = Release|Any CPU + {947252C9-700A-4A6E-A9EA-DFA6631916EF}.Release|x64.ActiveCfg = Release|Any CPU + {947252C9-700A-4A6E-A9EA-DFA6631916EF}.Release|x64.Build.0 = Release|Any CPU + {947252C9-700A-4A6E-A9EA-DFA6631916EF}.Release|x86.ActiveCfg = Release|Any CPU + {947252C9-700A-4A6E-A9EA-DFA6631916EF}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {56AE6D80-197D-426C-B4A3-B1F329263B71} = {E1CC46FC-4D7C-42C0-8F3A-BCB1B45CD459} {062FD47B-69A9-4808-97CA-CF24CD65B0F0} = {DECBE6ED-4F14-424C-BEF1-0B9D45EEDB79} + {947252C9-700A-4A6E-A9EA-DFA6631916EF} = {DECBE6ED-4F14-424C-BEF1-0B9D45EEDB79} EndGlobalSection EndGlobal diff --git a/README.md b/README.md index 5657c7f..a2d4489 100644 --- a/README.md +++ b/README.md @@ -25,23 +25,32 @@ dotnet add package DotNetEnv ### Load env file -`Load()` will automatically look for a `.env` file in the current directory +`Load()` will automatically look for a `.env` file in the current directory by default, + or any higher parent/ancestor directory if given the option flag via TraversePath() ```csharp DotNetEnv.Env.Load(); +DotNetEnv.Env.TraversePath().Load(); ``` -Or you can specify the path to the `.env` file +Or you can specify the path directly to the `.env` file, + amd as above, with `TraversePath()`, it will start looking there + and then look in higher dirs from there if not found. ```csharp DotNetEnv.Env.Load("./path/to/.env"); ``` -It's also possible to load the (text) file as a `Stream` +It's also possible to load the (text) file as a `Stream` or `string[]` ```csharp using (var stream = File.OpenRead("./path/to/.env")) { DotNetEnv.Env.Load(stream); } + +DotNetEnv.Env.Load(new[] { + "OK=GOOD", + "TEST=\"more stuff\"", +}); ``` ### Accessing environment variables @@ -72,18 +81,26 @@ DotNetEnv.Env.GetString("THIS_DOES_NOT_EXIST", "Variable not found"); You can also pass a `LoadOptions` object arg to all `DotNetEnv.Env.Load` variants to affect the Load/Parse behavior: ```csharp -new DotNetEnv.Env.LoadOptions( +new DotNetEnv.LoadOptions( setEnvVars: true, - clobberExistingVars: true + clobberExistingVars: true, + onlyExactPath: true ) ``` +However the recommended approach is with a fluent syntax for turning flags off such as: + +```csharp +DotNetEnv.Env.NoEnvVars().NoClobber().TraversePath().Load(); +``` + All parameters default to true, which means: 1. `setEnvVars`, first arg: `true` in order to actually update env vars. Setting it `false` allows consumers of this library to process the .env file but use it for things other than updating env vars, as a generic configuration file. - The Load methods all return an `IEnumerable> for this. + The Load methods all return an `IEnumerable> for this, but + there is an extension method ToDictionary to get a dict with the last value for each key. ```env KEY=value @@ -91,10 +108,14 @@ KEY=value ```csharp var kvps = DotNetEnv.Env.Load( - new DotNetEnv.Env.LoadOptions( + options: new DotNetEnv.Env.LoadOptions( setEnvVars: false ) ) + +// or the recommended, cleaner (fluent) approach: +var dict = DotNetEnv.Env.NoEnvVars().Load().ToDictionary(); + // not "value" from the .env file null == System.Environment.GetEnvironmentVariable("KEY") "KEY" == kvps.First().Key @@ -111,14 +132,31 @@ KEY=value ```csharp System.Environment.SetEnvironmentVariable("KEY", "really important value, don't overwrite"); DotNetEnv.Env.Load( - new DotNetEnv.Env.LoadOptions( + options: new DotNetEnv.Env.LoadOptions( clobberExistingVars: false ) ) + +// or the recommended, cleaner (fluent) approach: +DotNetEnv.Env.NoClobber().Load(); + // not "value" from the .env file "really important value, don't overwrite" == System.Environment.GetEnvironmentVariable("KEY") ``` +3. `exactPathOnly`, third arg: `true` to require .env to be + in the current directory if not specified, or to match the exact path passed in, + `false` would traverse the parent directories above the current or given path + to find the nearest `.env` file or whatever name was passed in. + This option only applies to Env.Load that takes a string path. + +See `DotNetEnvTraverse.Tests` for examples. + +```csharp +// the recommended, cleaner (fluent) approach: +DotNetEnv.Env.TraversePath().Load(); +``` + ## .env file structure All lines must be valid assignments or empty lines (with optional comments). @@ -238,7 +276,7 @@ If you have found a bug or if you have a feature request, please report them at ## Contributing -Run `dotnet test test/DotNetEnv.Tests` to run all tests. +Run `dotnet test` to run all tests. Or some more specific test examples: @@ -249,6 +287,7 @@ Or some more specific test examples: `src/DotNetEnvEnv/Parsers.cs` defines all the [Sprache](https://github.com/sprache/Sprache) parsers. +The `DotNetEnvTraverse.Tests` project tests loading `.env` files in parent (or higher) directories from the executable. Open a PR on Github if you have some changes, or an issue if you want to discuss some proposed changes before creating a PR for them. diff --git a/src/DotNetEnv/Env.cs b/src/DotNetEnv/Env.cs index 6167d92..01121a9 100755 --- a/src/DotNetEnv/Env.cs +++ b/src/DotNetEnv/Env.cs @@ -11,17 +11,42 @@ public class Env { public const string DEFAULT_ENVFILENAME = ".env"; - private static LoadOptions DEFAULT_OPTIONS = new LoadOptions(); - public static IEnumerable> Load (string[] lines, LoadOptions options = null) { return LoadContents(String.Join("\n", lines), options); } - public static IEnumerable> Load (string path, LoadOptions options = null) + public static IEnumerable> Load (string path = null, LoadOptions options = null) { + if (options == null) options = LoadOptions.DEFAULT; + + var file = Path.GetFileName(path); + if (file == null || file == string.Empty) file = DEFAULT_ENVFILENAME; + var dir = Path.GetDirectoryName(path); + if (dir == null || dir == string.Empty) dir = Directory.GetCurrentDirectory(); + path = Path.Combine(dir, file); + + if (options.OnlyExactPath) + { + if (!File.Exists(path)) path = null; + } + else + { + while (!File.Exists(path)) + { + var parent = Directory.GetParent(dir); + if (parent == null) + { + path = null; + break; + } + dir = parent.FullName; + path = Path.Combine(dir, file); + } + } + // in production, there should be no .env file, so this should be the common code path - if (!File.Exists(path)) + if (path == null) { return Enumerable.Empty>(); } @@ -38,7 +63,7 @@ public static IEnumerable> Load (Stream file, LoadO public static IEnumerable> LoadContents (string contents, LoadOptions options = null) { - if (options == null) options = DEFAULT_OPTIONS; + if (options == null) options = LoadOptions.DEFAULT; if (options.SetEnvVars) { @@ -57,9 +82,6 @@ public static IEnumerable> LoadContents (string con } } - public static IEnumerable> Load (LoadOptions options = null) => - Load(Path.Combine(Directory.GetCurrentDirectory(), DEFAULT_ENVFILENAME), options); - public static string GetString (string key, string fallback = default(string)) => Environment.GetEnvironmentVariable(key) ?? fallback; @@ -72,19 +94,9 @@ public static IEnumerable> Load (LoadOptions option public static double GetDouble (string key, double fallback = default(double)) => double.TryParse(Environment.GetEnvironmentVariable(key), NumberStyles.Any, CultureInfo.InvariantCulture, out var value) ? value : fallback; - public class LoadOptions - { - public bool SetEnvVars { get; } - public bool ClobberExistingVars { get; } - - public LoadOptions( - bool setEnvVars = true, - bool clobberExistingVars = true - ) { - SetEnvVars = setEnvVars; - ClobberExistingVars = clobberExistingVars; - } - } + public static LoadOptions NoEnvVars () => LoadOptions.NoEnvVars(); + public static LoadOptions NoClobber () => LoadOptions.NoClobber(); + public static LoadOptions TraversePath () => LoadOptions.TraversePath(); } public static class Extensions diff --git a/src/DotNetEnv/LoadOptions.cs b/src/DotNetEnv/LoadOptions.cs new file mode 100644 index 0000000..642d6da --- /dev/null +++ b/src/DotNetEnv/LoadOptions.cs @@ -0,0 +1,54 @@ +using System; +using System.Linq; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Text; + +namespace DotNetEnv +{ + public class LoadOptions + { + public static readonly LoadOptions DEFAULT = new LoadOptions(); + + public bool SetEnvVars { get; } + public bool ClobberExistingVars { get; } + public bool OnlyExactPath { get; } + + public LoadOptions( + bool setEnvVars = true, + bool clobberExistingVars = true, + bool onlyExactPath = true + ) { + SetEnvVars = setEnvVars; + ClobberExistingVars = clobberExistingVars; + OnlyExactPath = onlyExactPath; + } + + public LoadOptions( + LoadOptions old, + bool? setEnvVars = null, + bool? clobberExistingVars = null, + bool? onlyExactPath = null + ) { + SetEnvVars = setEnvVars ?? old.SetEnvVars; + ClobberExistingVars = clobberExistingVars ?? old.ClobberExistingVars; + OnlyExactPath = onlyExactPath ?? old.OnlyExactPath; + } + + public static LoadOptions NoEnvVars (LoadOptions options = null) => + options == null ? DEFAULT.NoEnvVars() : options.NoEnvVars(); + + public static LoadOptions NoClobber (LoadOptions options = null) => + options == null ? DEFAULT.NoClobber() : options.NoClobber(); + + public static LoadOptions TraversePath (LoadOptions options = null) => + options == null ? DEFAULT.TraversePath() : options.TraversePath(); + + public LoadOptions NoEnvVars () => new LoadOptions(this, setEnvVars: false); + public LoadOptions NoClobber () => new LoadOptions(this, clobberExistingVars: false); + public LoadOptions TraversePath () => new LoadOptions(this, onlyExactPath: false); + + public IEnumerable> Load (string path = null) => Env.Load(path, this); + } +} diff --git a/test/.env b/test/.env new file mode 100644 index 0000000..f8f9c8d --- /dev/null +++ b/test/.env @@ -0,0 +1 @@ +TEST=here diff --git a/test/DotNetEnv.Tests/EnvTests.cs b/test/DotNetEnv.Tests/EnvTests.cs index 5a0d65e..d977824 100755 --- a/test/DotNetEnv.Tests/EnvTests.cs +++ b/test/DotNetEnv.Tests/EnvTests.cs @@ -26,6 +26,17 @@ public void LoadTest() Assert.Equal("SPECIAL STUFF---\nLONG-BASE64\\ignore\"slash", Environment.GetEnvironmentVariable("SSL_CERT")); } + [Fact] + public void LoadDotenvHigherSkip() + { + Environment.SetEnvironmentVariable("TEST", null); + Environment.SetEnvironmentVariable("NAME", null); + // ./DotNetEnv.Tests/bin/Debug/netcoreapp3.1/DotNetEnv.Tests.dll -- get to the ./ + DotNetEnv.Env.Load("../../../../"); + Assert.Equal("here", Environment.GetEnvironmentVariable("TEST")); + Assert.Null(Environment.GetEnvironmentVariable("NAME")); + } + [Fact] public void LoadPathTest() { @@ -90,13 +101,13 @@ public void LoadNoClobberTest() var expected = "totally the original value"; Environment.SetEnvironmentVariable("NAME", null); Environment.SetEnvironmentVariable("URL", expected); - DotNetEnv.Env.Load(new DotNetEnv.Env.LoadOptions(clobberExistingVars: false)); + DotNetEnv.Env.Load(options: new DotNetEnv.LoadOptions(clobberExistingVars: false)); Assert.Equal(expected, Environment.GetEnvironmentVariable("URL")); Assert.Equal("Toni", Environment.GetEnvironmentVariable("NAME")); Environment.SetEnvironmentVariable("NAME", null); Environment.SetEnvironmentVariable("URL", "i'm going to be overwritten"); - DotNetEnv.Env.Load(new DotNetEnv.Env.LoadOptions(clobberExistingVars: true)); + DotNetEnv.Env.Load(options: new DotNetEnv.LoadOptions(clobberExistingVars: true)); Assert.Equal("https://github.com/tonerdo", Environment.GetEnvironmentVariable("URL")); Assert.Equal("Toni", Environment.GetEnvironmentVariable("NAME")); } @@ -107,14 +118,14 @@ public void LoadNoSetEnvVarsTest() var expected = "totally the original value"; Environment.SetEnvironmentVariable("NAME", null); Environment.SetEnvironmentVariable("URL", expected); - DotNetEnv.Env.Load(new DotNetEnv.Env.LoadOptions(setEnvVars: false)); + DotNetEnv.Env.Load(options: new DotNetEnv.LoadOptions(setEnvVars: false)); Assert.Equal(expected, Environment.GetEnvironmentVariable("URL")); // this env var remaining null is the difference between NoSetEnvVars and NoClobber Assert.Null(Environment.GetEnvironmentVariable("NAME")); Environment.SetEnvironmentVariable("NAME", null); Environment.SetEnvironmentVariable("URL", "i'm going to be overwritten"); - DotNetEnv.Env.Load(new DotNetEnv.Env.LoadOptions(setEnvVars: true)); + DotNetEnv.Env.Load(options: new DotNetEnv.LoadOptions(setEnvVars: true)); Assert.Equal("https://github.com/tonerdo", Environment.GetEnvironmentVariable("URL")); Assert.Equal("Toni", Environment.GetEnvironmentVariable("NAME")); } @@ -125,7 +136,7 @@ public void LoadNoRequireEnvTest() var expected = "totally the original value"; Environment.SetEnvironmentVariable("URL", expected); // this env file Does Not Exist - DotNetEnv.Env.Load("./.env_DNE", new DotNetEnv.Env.LoadOptions()); + DotNetEnv.Env.Load("./.env_DNE"); Assert.Equal(expected, Environment.GetEnvironmentVariable("URL")); // it didn't throw an exception and crash for a missing file } @@ -134,21 +145,21 @@ public void LoadNoRequireEnvTest() public void LoadOsCasingTest() { Environment.SetEnvironmentVariable("CASING", "neither"); - DotNetEnv.Env.Load("./.env_casing", new DotNetEnv.Env.LoadOptions(clobberExistingVars: false)); + DotNetEnv.Env.Load("./.env_casing", new DotNetEnv.LoadOptions(clobberExistingVars: false)); Assert.Equal(IsWindows ? "neither" : "lower", Environment.GetEnvironmentVariable("casing")); Assert.Equal("neither", Environment.GetEnvironmentVariable("CASING")); - DotNetEnv.Env.Load("./.env_casing", new DotNetEnv.Env.LoadOptions(clobberExistingVars: true)); + DotNetEnv.Env.Load("./.env_casing", new DotNetEnv.LoadOptions(clobberExistingVars: true)); Assert.Equal("lower", Environment.GetEnvironmentVariable("casing")); Assert.Equal(IsWindows ? "lower" : "neither", Environment.GetEnvironmentVariable("CASING")); Environment.SetEnvironmentVariable("CASING", null); Environment.SetEnvironmentVariable("casing", "neither"); - DotNetEnv.Env.Load("./.env_casing", new DotNetEnv.Env.LoadOptions(clobberExistingVars: false)); + DotNetEnv.Env.Load("./.env_casing", new DotNetEnv.LoadOptions(clobberExistingVars: false)); Assert.Equal("neither", Environment.GetEnvironmentVariable("casing")); Assert.Equal(IsWindows ? "neither" : null, Environment.GetEnvironmentVariable("CASING")); - DotNetEnv.Env.Load("./.env_casing", new DotNetEnv.Env.LoadOptions(clobberExistingVars: true)); + DotNetEnv.Env.Load("./.env_casing", new DotNetEnv.LoadOptions(clobberExistingVars: true)); Assert.Equal("lower", Environment.GetEnvironmentVariable("casing")); Assert.Equal(IsWindows ? "lower" : null, Environment.GetEnvironmentVariable("CASING")); } @@ -317,49 +328,49 @@ public void BadSyntaxTest() ParseException ex; ex = Assert.Throws( - () => DotNetEnv.Env.Load(new [] { + () => DotNetEnv.Env.Load(new[] { "KEY=VAL UE", }) ); Assert.Equal("Parsing failure: unexpected 'U'; expected LineTerminator (Line 1, Column 9); recently consumed: KEY=VAL ", ex.Message); ex = Assert.Throws( - () => DotNetEnv.Env.Load(new [] { + () => DotNetEnv.Env.Load(new[] { "NOVALUE", }) ); Assert.Equal("Parsing failure: Unexpected end of input reached; expected = (Line 1, Column 8); recently consumed: NOVALUE", ex.Message); ex = Assert.Throws( - () => DotNetEnv.Env.Load(new [] { + () => DotNetEnv.Env.Load(new[] { "MULTI WORD KEY", }) ); Assert.Equal("Parsing failure: unexpected 'W'; expected = (Line 1, Column 7); recently consumed: MULTI ", ex.Message); ex = Assert.Throws( - () => DotNetEnv.Env.Load(new [] { + () => DotNetEnv.Env.Load(new[] { "UNMATCHEDQUOTE='", }) ); Assert.Equal("Parsing failure: unexpected '''; expected LineTerminator (Line 1, Column 16); recently consumed: CHEDQUOTE=", ex.Message); ex = Assert.Throws( - () => DotNetEnv.Env.Load(new [] { + () => DotNetEnv.Env.Load(new[] { "BADQUOTE='\\''", }) ); Assert.Equal("Parsing failure: unexpected '''; expected LineTerminator (Line 1, Column 13); recently consumed: DQUOTE='\\'", ex.Message); ex = Assert.Throws( - () => DotNetEnv.Env.Load(new [] { + () => DotNetEnv.Env.Load(new[] { "UNMATCHEDQUOTE=\"", }) ); Assert.Equal("Parsing failure: unexpected '\"'; expected LineTerminator (Line 1, Column 16); recently consumed: CHEDQUOTE=", ex.Message); ex = Assert.Throws( - () => DotNetEnv.Env.Load(new [] { + () => DotNetEnv.Env.Load(new[] { "SSL_CERT=\"SPECIAL STUFF---\nLONG-BASE64\\ignore\"slash\"", }) ); @@ -368,7 +379,7 @@ public void BadSyntaxTest() // this test confirms that the entire file must be valid, not just at least one assignment at the start // otherwise it silently discards any remainder after the first failure, so long as at least one success... ex = Assert.Throws( - () => DotNetEnv.Env.Load(new [] { + () => DotNetEnv.Env.Load(new[] { "OK=GOOD", "SSL_CERT=\"SPECIAL STUFF---\nLONG-BASE64\\ignore\"slash\"", }) @@ -379,10 +390,10 @@ public void BadSyntaxTest() [Fact] public void BasicsTest() { - DotNetEnv.Env.Load(new [] { "ENV_TEST_KEY=VALUE" }); + DotNetEnv.Env.Load(new[] { "ENV_TEST_KEY=VALUE" }); Assert.Equal("VALUE", Environment.GetEnvironmentVariable("ENV_TEST_KEY")); - DotNetEnv.Env.Load(new [] { "ENV_TEST_K1=V1", "ENV_TEST_K2=V2" }); + DotNetEnv.Env.Load(new[] { "ENV_TEST_K1=V1", "ENV_TEST_K2=V2" }); Assert.Equal("V1", Environment.GetEnvironmentVariable("ENV_TEST_K1")); Assert.Equal("V2", Environment.GetEnvironmentVariable("ENV_TEST_K2")); } diff --git a/test/DotNetEnv.Tests/LoadOptionsTests.cs b/test/DotNetEnv.Tests/LoadOptionsTests.cs new file mode 100644 index 0000000..2c507f9 --- /dev/null +++ b/test/DotNetEnv.Tests/LoadOptionsTests.cs @@ -0,0 +1,102 @@ +using System; +using Xunit; + +namespace DotNetEnv.Tests +{ + public class LoadOptionsTests + { + [Fact] + public void StaticEnvTest() + { + LoadOptions options; + + options = DotNetEnv.Env.NoEnvVars(); + Assert.False(options.SetEnvVars); + Assert.True(options.ClobberExistingVars); + Assert.True(options.OnlyExactPath); + + options = DotNetEnv.Env.NoClobber(); + Assert.True(options.SetEnvVars); + Assert.False(options.ClobberExistingVars); + Assert.True(options.OnlyExactPath); + + options = DotNetEnv.Env.TraversePath(); + Assert.True(options.SetEnvVars); + Assert.True(options.ClobberExistingVars); + Assert.False(options.OnlyExactPath); + } + + [Fact] + public void StaticOptionsTest() + { + LoadOptions options; + + options = DotNetEnv.LoadOptions.NoEnvVars(); + Assert.False(options.SetEnvVars); + Assert.True(options.ClobberExistingVars); + Assert.True(options.OnlyExactPath); + + options = DotNetEnv.LoadOptions.NoClobber(); + Assert.True(options.SetEnvVars); + Assert.False(options.ClobberExistingVars); + Assert.True(options.OnlyExactPath); + + options = DotNetEnv.LoadOptions.TraversePath(); + Assert.True(options.SetEnvVars); + Assert.True(options.ClobberExistingVars); + Assert.False(options.OnlyExactPath); + } + + [Fact] + public void InstanceTest() + { + LoadOptions options; + + options = new DotNetEnv.LoadOptions(); + Assert.True(options.SetEnvVars); + Assert.True(options.ClobberExistingVars); + Assert.True(options.OnlyExactPath); + + options = new DotNetEnv.LoadOptions().NoEnvVars(); + Assert.False(options.SetEnvVars); + Assert.True(options.ClobberExistingVars); + Assert.True(options.OnlyExactPath); + + options = new DotNetEnv.LoadOptions().NoClobber(); + Assert.True(options.SetEnvVars); + Assert.False(options.ClobberExistingVars); + Assert.True(options.OnlyExactPath); + + options = new DotNetEnv.LoadOptions().TraversePath(); + Assert.True(options.SetEnvVars); + Assert.True(options.ClobberExistingVars); + Assert.False(options.OnlyExactPath); + } + + [Fact] + public void ComboTest() + { + LoadOptions options; + + options = DotNetEnv.Env.NoEnvVars().NoClobber().TraversePath(); + Assert.False(options.SetEnvVars); + Assert.False(options.ClobberExistingVars); + Assert.False(options.OnlyExactPath); + + options = DotNetEnv.Env.NoClobber().TraversePath(); + Assert.True(options.SetEnvVars); + Assert.False(options.ClobberExistingVars); + Assert.False(options.OnlyExactPath); + + options = DotNetEnv.Env.NoEnvVars().NoClobber(); + Assert.False(options.SetEnvVars); + Assert.False(options.ClobberExistingVars); + Assert.True(options.OnlyExactPath); + + options = DotNetEnv.Env.NoEnvVars().TraversePath(); + Assert.False(options.SetEnvVars); + Assert.True(options.ClobberExistingVars); + Assert.False(options.OnlyExactPath); + } + } +} diff --git a/test/DotNetEnvTraverse.Tests/DotNetEnvTraverse.Tests.csproj b/test/DotNetEnvTraverse.Tests/DotNetEnvTraverse.Tests.csproj new file mode 100644 index 0000000..36830e8 --- /dev/null +++ b/test/DotNetEnvTraverse.Tests/DotNetEnvTraverse.Tests.csproj @@ -0,0 +1,20 @@ + + + + netcoreapp3.1 + + false + + + + + + + + + + + + + + diff --git a/test/DotNetEnvTraverse.Tests/TraverseTests.cs b/test/DotNetEnvTraverse.Tests/TraverseTests.cs new file mode 100644 index 0000000..8dda7ec --- /dev/null +++ b/test/DotNetEnvTraverse.Tests/TraverseTests.cs @@ -0,0 +1,69 @@ +using System; +using System.Linq; +using Xunit; +using DotNetEnv; // only needed for ToDictionary extension method + +namespace DotNetEnvTraverse.Tests +{ + public class TraverseTests + { + public TraverseTests () + { + Environment.SetEnvironmentVariable("TEST", null); + Environment.SetEnvironmentVariable("NAME", null); + } + + [Fact] + public void LoadDotenvTraverse() + { + var kvps = DotNetEnv.Env.TraversePath().Load().ToArray(); + Assert.Single(kvps); + var dict = kvps.ToDictionary(); + Assert.Equal("here", dict["TEST"]); + Assert.Equal("here", Environment.GetEnvironmentVariable("TEST")); + Assert.Null(Environment.GetEnvironmentVariable("NAME")); + } + + [Fact] + public void LoadRenamedDotenvTraverse() + { + var kvps = DotNetEnv.Env.TraversePath().Load("./.env").ToArray(); + Assert.Single(kvps); + var dict = kvps.ToDictionary(); + Assert.Equal("here", dict["TEST"]); + Assert.Equal("here", Environment.GetEnvironmentVariable("TEST")); + Assert.Null(Environment.GetEnvironmentVariable("NAME")); + } + + [Fact] + public void LoadRenamedDotenvMuchTraverse() + { + var kvps = DotNetEnv.Env.TraversePath().Load(".env_much_higher").ToArray(); + Assert.Single(kvps); + var dict = kvps.ToDictionary(); + Assert.Equal("See DotNetEnvTraverse.Tests for why this is here", dict["TEST"]); + Assert.Equal("See DotNetEnvTraverse.Tests for why this is here", Environment.GetEnvironmentVariable("TEST")); + Assert.Null(Environment.GetEnvironmentVariable("NAME")); + } + + [Fact] + public void LoadOnlyDirectoryDotenvTraverse() + { + var kvps = DotNetEnv.Env.TraversePath().Load("./").ToArray(); + Assert.Single(kvps); + var dict = kvps.ToDictionary(); + Assert.Equal("here", dict["TEST"]); + Assert.Equal("here", Environment.GetEnvironmentVariable("TEST")); + Assert.Null(Environment.GetEnvironmentVariable("NAME")); + } + + [Fact] + public void DoNotLoadSkippedDotenv() + { + var kvps = DotNetEnv.Env.TraversePath().Load("../../../../../../").ToArray(); + Assert.Empty(kvps); + Assert.Null(Environment.GetEnvironmentVariable("TEST")); + Assert.Null(Environment.GetEnvironmentVariable("NAME")); + } + } +}