From 68cbb1ae93e416b4bb314f615ec7f83e2e047b51 Mon Sep 17 00:00:00 2001 From: Juan Martinez Ramirez Date: Mon, 22 Jul 2024 14:57:45 -0600 Subject: [PATCH] Added testing for oauth token loading mechanism --- .../SnowflakeTomlConnectionBuilderTest.cs | 260 ++++++++++++++++-- .../Core/SnowflakeTomlConnectionBuilder.cs | 25 +- 2 files changed, 253 insertions(+), 32 deletions(-) diff --git a/Snowflake.Data.Tests/UnitTests/SnowflakeTomlConnectionBuilderTest.cs b/Snowflake.Data.Tests/UnitTests/SnowflakeTomlConnectionBuilderTest.cs index 163268418..e08f22a02 100644 --- a/Snowflake.Data.Tests/UnitTests/SnowflakeTomlConnectionBuilderTest.cs +++ b/Snowflake.Data.Tests/UnitTests/SnowflakeTomlConnectionBuilderTest.cs @@ -2,21 +2,19 @@ * Copyright (c) 2024 Snowflake Computing Inc. All rights reserved. */ -using System.IO; -using System.Linq; - namespace Snowflake.Data.Tests.UnitTests { using System; - using Core.Tools; + using System.IO; using Moq; using NUnit.Framework; + using Core.Tools; using Snowflake.Data.Core; [TestFixture] class SnowflakeTomlConnectionBuilderTest { - private readonly string _basicTomlConfig = @" + private const string BasicTomlConfig = @" [default] account = ""defaultaccountname"" user = ""defaultusername"" @@ -38,12 +36,12 @@ public void TestConnectionWithReadFromDefaultValuesInEnvironmentVariables() var mockEnvironmentOperations = new Mock(); mockEnvironmentOperations .Setup(e => e.GetEnvironmentVariable(It.IsAny(), It.IsAny())) - .Returns((string e, string s) => s); + .Returns((string _, string s) => s); mockEnvironmentOperations.Setup(e => e.GetFolderPath(Environment.SpecialFolder.UserProfile)) .Returns($"{Path.DirectorySeparatorChar}home"); mockFileOperations.Setup(f => f.Exists(It.IsAny())).Returns(true); mockFileOperations.Setup(f => f.ReadAllText(It.Is(p => p.Contains(".snowflake")))) - .Returns(_basicTomlConfig); + .Returns(BasicTomlConfig); var reader = new SnowflakeTomlConnectionBuilder(mockFileOperations.Object, mockEnvironmentOperations.Object); @@ -65,12 +63,12 @@ public void TestConnectionFromCustomSnowflakeHome() .Returns($"{Path.DirectorySeparatorChar}customsnowhome"); mockEnvironmentOperations .Setup(e => e.GetEnvironmentVariable(EnvironmentVariables.SnowflakeDefaultConnectionName, It.IsAny())) - .Returns((string e, string s) => s); + .Returns((string _, string s) => s); mockEnvironmentOperations.Setup(e => e.GetFolderPath(Environment.SpecialFolder.UserProfile)) .Returns($"{Path.DirectorySeparatorChar}home"); mockFileOperations.Setup(f => f.Exists(It.IsAny())).Returns(true); mockFileOperations.Setup(f => f.ReadAllText(It.Is(p => p.Contains("customsnowhome")))) - .Returns(_basicTomlConfig); + .Returns(BasicTomlConfig); var reader = new SnowflakeTomlConnectionBuilder(mockFileOperations.Object, mockEnvironmentOperations.Object); @@ -89,7 +87,7 @@ public void TestConnectionWithUserConnectionNameFromEnvVariable() var mockEnvironmentOperations = new Mock(); mockEnvironmentOperations .Setup(e => e.GetEnvironmentVariable(EnvironmentVariables.SnowflakeHome, It.IsAny())) - .Returns((string e, string d) => d); + .Returns((string _, string d) => d); mockEnvironmentOperations .Setup(e => e.GetEnvironmentVariable(EnvironmentVariables.SnowflakeDefaultConnectionName, It.IsAny())) .Returns("testconnection"); @@ -97,7 +95,7 @@ public void TestConnectionWithUserConnectionNameFromEnvVariable() .Returns($"{Path.DirectorySeparatorChar}home"); mockFileOperations.Setup(f => f.Exists(It.IsAny())).Returns(true); mockFileOperations.Setup(f => f.ReadAllText(It.Is(p => p.Contains(".snowflake")))) - .Returns(_basicTomlConfig); + .Returns(BasicTomlConfig); var reader = new SnowflakeTomlConnectionBuilder(mockFileOperations.Object, mockEnvironmentOperations.Object); @@ -116,7 +114,7 @@ public void TestConnectionWithUserConnectionNameFromEnvVariableWithMultipleConne var mockEnvironmentOperations = new Mock(); mockEnvironmentOperations .Setup(e => e.GetEnvironmentVariable(EnvironmentVariables.SnowflakeHome, It.IsAny())) - .Returns((string e, string d) => d); + .Returns((string _, string d) => d); mockEnvironmentOperations .Setup(e => e.GetEnvironmentVariable(EnvironmentVariables.SnowflakeDefaultConnectionName, It.IsAny())) .Returns("otherconnection"); @@ -124,7 +122,7 @@ public void TestConnectionWithUserConnectionNameFromEnvVariableWithMultipleConne .Returns($"{Path.DirectorySeparatorChar}home"); mockFileOperations.Setup(f => f.Exists(It.IsAny())).Returns(true); mockFileOperations.Setup(f => f.ReadAllText(It.Is(p => p.Contains(".snowflake")))) - .Returns(_basicTomlConfig); + .Returns(BasicTomlConfig); var reader = new SnowflakeTomlConnectionBuilder(mockFileOperations.Object, mockEnvironmentOperations.Object); @@ -143,7 +141,7 @@ public void TestConnectionWithUserConnectionName() var mockEnvironmentOperations = new Mock(); mockEnvironmentOperations .Setup(e => e.GetEnvironmentVariable(EnvironmentVariables.SnowflakeHome, It.IsAny())) - .Returns((string e, string d) => d); + .Returns((string _, string d) => d); mockEnvironmentOperations .Setup(e => e.GetEnvironmentVariable(EnvironmentVariables.SnowflakeDefaultConnectionName, It.IsAny())) .Returns("otherconnection"); @@ -151,7 +149,7 @@ public void TestConnectionWithUserConnectionName() .Returns($"{Path.DirectorySeparatorChar}home"); mockFileOperations.Setup(f => f.Exists(It.IsAny())).Returns(true); mockFileOperations.Setup(f => f.ReadAllText(It.Is(p => p.Contains(".snowflake")))) - .Returns(_basicTomlConfig); + .Returns(BasicTomlConfig); var reader = new SnowflakeTomlConnectionBuilder(mockFileOperations.Object, mockEnvironmentOperations.Object); @@ -172,7 +170,7 @@ public void TestConnectionMapPropertiesFromTomlKeyValues(string tomlKeyValue, st var mockEnvironmentOperations = new Mock(); mockEnvironmentOperations .Setup(e => e.GetEnvironmentVariable(It.IsAny(), It.IsAny())) - .Returns((string e, string d) => d); + .Returns((string _, string d) => d); mockEnvironmentOperations.Setup(e => e.GetFolderPath(Environment.SpecialFolder.UserProfile)) .Returns($"{Path.DirectorySeparatorChar}home"); mockFileOperations.Setup(f => f.Exists(It.IsAny())).Returns(true); @@ -223,7 +221,7 @@ public void TestConnectionWithInvalidConnectionName() var mockEnvironmentOperations = new Mock(); mockEnvironmentOperations .Setup(e => e.GetEnvironmentVariable(EnvironmentVariables.SnowflakeHome, It.IsAny())) - .Returns((string e, string d) => d); + .Returns((string _, string d) => d); mockEnvironmentOperations .Setup(e => e.GetEnvironmentVariable(EnvironmentVariables.SnowflakeDefaultConnectionName, It.IsAny())) .Returns("wrongconnectionname"); @@ -231,7 +229,7 @@ public void TestConnectionWithInvalidConnectionName() .Returns($"{Path.DirectorySeparatorChar}home"); mockFileOperations.Setup(f => f.Exists(It.IsAny())).Returns(true); mockFileOperations.Setup(f => f.ReadAllText(It.Is(p => p.Contains(".snowflake")))) - .Returns(_basicTomlConfig); + .Returns(BasicTomlConfig); var reader = new SnowflakeTomlConnectionBuilder(mockFileOperations.Object, mockEnvironmentOperations.Object); @@ -247,7 +245,7 @@ public void TestConnectionWithNonExistingDefaultConnection() var mockEnvironmentOperations = new Mock(); mockEnvironmentOperations .Setup(e => e.GetEnvironmentVariable(It.IsAny(), It.IsAny())) - .Returns((string e, string s) => s); + .Returns((string _, string s) => s); mockEnvironmentOperations.Setup(e => e.GetFolderPath(Environment.SpecialFolder.UserProfile)) .Returns($"{Path.DirectorySeparatorChar}home"); mockFileOperations.Setup(f => f.Exists(It.IsAny())).Returns(true); @@ -272,7 +270,7 @@ public void TestConnectionWithSpecifiedConnectionEmpty() var mockEnvironmentOperations = new Mock(); mockEnvironmentOperations .Setup(e => e.GetEnvironmentVariable(EnvironmentVariables.SnowflakeHome, It.IsAny())) - .Returns((string e, string d) => d); + .Returns((string _, string d) => d); mockEnvironmentOperations .Setup(e => e.GetEnvironmentVariable(EnvironmentVariables.SnowflakeDefaultConnectionName, It.IsAny())) .Returns("testconnection1"); @@ -299,6 +297,228 @@ public void TestConnectionWithSpecifiedConnectionEmpty() // Assert Assert.AreEqual(string.Empty, connectionString); } + + [Test] + public void TestConnectionWithOauthAuthenticatorTokenFromFile() + { + // Arrange + var tokenFilePath = "/Users/testuser/token"; + var testToken = "token1234"; + var mockFileOperations = new Mock(); + var mockEnvironmentOperations = new Mock(); + mockEnvironmentOperations + .Setup(e => e.GetEnvironmentVariable(EnvironmentVariables.SnowflakeHome, It.IsAny())) + .Returns((string _, string d) => d); + mockEnvironmentOperations + .Setup(e => e.GetEnvironmentVariable(EnvironmentVariables.SnowflakeDefaultConnectionName, It.IsAny())) + .Returns("oauthconnection"); + mockEnvironmentOperations.Setup(e => e.GetFolderPath(Environment.SpecialFolder.UserProfile)) + .Returns($"{Path.DirectorySeparatorChar}home"); + mockFileOperations.Setup(f => f.Exists(It.IsAny())).Returns(true); + mockFileOperations.Setup(f => f.ReadAllText(tokenFilePath)).Returns(testToken); + mockFileOperations.Setup(f => f.ReadAllText(It.Is(p => p.Contains(".snowflake")))) + .Returns(@$" +[default] +account = ""defaultaccountname"" +user = ""defaultusername"" +password = ""defaultpassword"" +[oauthconnection] +account = ""testaccountname"" +authenticator = ""oauth"" +token_file_path = ""{tokenFilePath}"""); + + var reader = new SnowflakeTomlConnectionBuilder(mockFileOperations.Object, mockEnvironmentOperations.Object); + + // Act + var connectionString = reader.GetConnectionStringFromToml(); + + // Assert + Assert.AreEqual($"account=testaccountname;authenticator=oauth;token={testToken};", connectionString); + } + + [Test] + public void TestConnectionWithOauthAuthenticatorFromDefaultIfTokenFilePathNotExists() + { + // Arrange + var tokenFilePath = "/Users/testuser/token"; + var defaultToken = "defaultToken1234"; + var mockFileOperations = new Mock(); + var mockEnvironmentOperations = new Mock(); + mockEnvironmentOperations + .Setup(e => e.GetEnvironmentVariable(EnvironmentVariables.SnowflakeHome, It.IsAny())) + .Returns((string _, string d) => d); + mockEnvironmentOperations + .Setup(e => e.GetEnvironmentVariable(EnvironmentVariables.SnowflakeDefaultConnectionName, It.IsAny())) + .Returns("oauthconnection"); + mockEnvironmentOperations.Setup(e => e.GetFolderPath(Environment.SpecialFolder.UserProfile)) + .Returns($"{Path.DirectorySeparatorChar}home"); + mockFileOperations.Setup(f => f.Exists(tokenFilePath)).Returns(false); + mockFileOperations.Setup(f => f.Exists(It.Is(p => !p.Equals(tokenFilePath)))).Returns(true); + mockFileOperations.Setup(f => f.ReadAllText(It.Is(p => p.Contains(".snowflake")))) + .Returns(@$" +[default] +account = ""defaultaccountname"" +user = ""defaultusername"" +password = ""defaultpassword"" +[oauthconnection] +account = ""testaccountname"" +authenticator = ""oauth"" +token_file_path = ""{tokenFilePath}"""); + mockFileOperations.Setup(f => f.ReadAllText(It.Is(p => p.Contains("/snowflake/session/token")))).Returns(defaultToken); + + var reader = new SnowflakeTomlConnectionBuilder(mockFileOperations.Object, mockEnvironmentOperations.Object); + + // Act + var connectionString = reader.GetConnectionStringFromToml(); + + // Assert + Assert.AreEqual($"account=testaccountname;authenticator=oauth;token={defaultToken};", connectionString); + } + + [Test] + public void TestConnectionWithOauthAuthenticatorFromDefaultPathShouldBeLoadedIfTokenFilePathNotSpecified() + { + // Arrange + var defaultToken = "defaultToken1234"; + var mockFileOperations = new Mock(); + var mockEnvironmentOperations = new Mock(); + mockEnvironmentOperations + .Setup(e => e.GetEnvironmentVariable(EnvironmentVariables.SnowflakeHome, It.IsAny())) + .Returns((string _, string d) => d); + mockEnvironmentOperations + .Setup(e => e.GetEnvironmentVariable(EnvironmentVariables.SnowflakeDefaultConnectionName, It.IsAny())) + .Returns("oauthconnection"); + mockEnvironmentOperations.Setup(e => e.GetFolderPath(Environment.SpecialFolder.UserProfile)) + .Returns($"{Path.DirectorySeparatorChar}home"); + mockFileOperations.Setup(f => f.Exists(It.IsAny())).Returns(true); + mockFileOperations.Setup(f => f.ReadAllText(It.Is(p => p.Contains(".snowflake")))) + .Returns(@$" +[default] +account = ""defaultaccountname"" +user = ""defaultusername"" +password = ""defaultpassword"" +[oauthconnection] +account = ""testaccountname"" +authenticator = ""oauth"""); + mockFileOperations.Setup(f => f.ReadAllText(It.Is(p => p.Contains("/snowflake/session/token")))).Returns(defaultToken); + + var reader = new SnowflakeTomlConnectionBuilder(mockFileOperations.Object, mockEnvironmentOperations.Object); + + // Act + var connectionString = reader.GetConnectionStringFromToml(); + + // Assert + Assert.AreEqual($"account=testaccountname;authenticator=oauth;token={defaultToken};", connectionString); + } + + [Test] + public void TestConnectionWithOauthAuthenticatorShouldNotIncludeTokenIfNotStoredDefaultPath() + { + // Arrange + var mockFileOperations = new Mock(); + var mockEnvironmentOperations = new Mock(); + mockEnvironmentOperations + .Setup(e => e.GetEnvironmentVariable(EnvironmentVariables.SnowflakeHome, It.IsAny())) + .Returns((string _, string d) => d); + mockEnvironmentOperations + .Setup(e => e.GetEnvironmentVariable(EnvironmentVariables.SnowflakeDefaultConnectionName, It.IsAny())) + .Returns("oauthconnection"); + mockEnvironmentOperations.Setup(e => e.GetFolderPath(Environment.SpecialFolder.UserProfile)) + .Returns($"{Path.DirectorySeparatorChar}home"); + mockFileOperations.Setup(f => f.Exists(It.Is(p => p.Contains("/snowflake/session/token")))).Returns(false); + mockFileOperations.Setup(f => f.Exists(It.Is(p => !string.IsNullOrEmpty(p) && !p.Contains("/snowflake/session/token")))).Returns(true); + mockFileOperations.Setup(f => f.ReadAllText(It.Is(p => p.Contains(".snowflake")))) + .Returns(@$" +[default] +account = ""defaultaccountname"" +user = ""defaultusername"" +password = ""defaultpassword"" +[oauthconnection] +account = ""testaccountname"" +authenticator = ""oauth"""); + + var reader = new SnowflakeTomlConnectionBuilder(mockFileOperations.Object, mockEnvironmentOperations.Object); + + // Act + var connectionString = reader.GetConnectionStringFromToml(); + + // Assert + Assert.AreEqual($"account=testaccountname;authenticator=oauth;", connectionString); + } + + + [Test] + public void TestConnectionWithOauthAuthenticatorShouldNotLoadFromFileIsSpecifiedInTokenProperty() + { + // Arrange + var tokenFilePath = "/Users/testuser/token"; + var tokenFromToml = "tomlToken1234"; + var mockFileOperations = new Mock(); + var mockEnvironmentOperations = new Mock(); + mockEnvironmentOperations + .Setup(e => e.GetEnvironmentVariable(EnvironmentVariables.SnowflakeHome, It.IsAny())) + .Returns((string _, string d) => d); + mockEnvironmentOperations + .Setup(e => e.GetEnvironmentVariable(EnvironmentVariables.SnowflakeDefaultConnectionName, It.IsAny())) + .Returns("oauthconnection"); + mockEnvironmentOperations.Setup(e => e.GetFolderPath(Environment.SpecialFolder.UserProfile)) + .Returns($"{Path.DirectorySeparatorChar}home"); + mockFileOperations.Setup(f => f.Exists(It.IsAny())).Returns(true); + mockFileOperations.Setup(f => f.ReadAllText(It.Is(p => p.Contains(".snowflake")))) + .Returns(@$" +[default] +account = ""defaultaccountname"" +user = ""defaultusername"" +password = ""defaultpassword"" +[oauthconnection] +account = ""testaccountname"" +authenticator = ""oauth"" +token = ""{tokenFromToml}"" +token_file_path = ""{tokenFilePath}"""); + + var reader = new SnowflakeTomlConnectionBuilder(mockFileOperations.Object, mockEnvironmentOperations.Object); + + // Act + var connectionString = reader.GetConnectionStringFromToml(); + + // Assert + Assert.AreEqual($"account=testaccountname;authenticator=oauth;token={tokenFromToml};", connectionString); + } + + [Test] + public void TestConnectionWithOauthAuthenticatorShouldNotIncludeTokenIfNullOrEmpty() + { + // Arrange + var mockFileOperations = new Mock(); + var mockEnvironmentOperations = new Mock(); + mockEnvironmentOperations + .Setup(e => e.GetEnvironmentVariable(EnvironmentVariables.SnowflakeHome, It.IsAny())) + .Returns((string _, string d) => d); + mockEnvironmentOperations + .Setup(e => e.GetEnvironmentVariable(EnvironmentVariables.SnowflakeDefaultConnectionName, It.IsAny())) + .Returns("oauthconnection"); + mockEnvironmentOperations.Setup(e => e.GetFolderPath(Environment.SpecialFolder.UserProfile)) + .Returns($"{Path.DirectorySeparatorChar}home"); + mockFileOperations.Setup(f => f.Exists(It.IsAny())).Returns(true); + mockFileOperations.Setup(f => f.ReadAllText(It.Is(p => p.Contains(".snowflake")))) + .Returns(@$" +[default] +account = ""defaultaccountname"" +user = ""defaultusername"" +password = ""defaultpassword"" +[oauthconnection] +account = ""testaccountname"" +authenticator = ""oauth"""); + mockFileOperations.Setup(f => f.ReadAllText(It.Is(p => p.Contains("/snowflake/session/token")))).Returns(string.Empty); + + var reader = new SnowflakeTomlConnectionBuilder(mockFileOperations.Object, mockEnvironmentOperations.Object); + + // Act + var connectionString = reader.GetConnectionStringFromToml(); + + // Assert + Assert.AreEqual($"account=testaccountname;authenticator=oauth;", connectionString); + } } } diff --git a/Snowflake.Data/Core/SnowflakeTomlConnectionBuilder.cs b/Snowflake.Data/Core/SnowflakeTomlConnectionBuilder.cs index 020c4f0a3..6cee01582 100644 --- a/Snowflake.Data/Core/SnowflakeTomlConnectionBuilder.cs +++ b/Snowflake.Data/Core/SnowflakeTomlConnectionBuilder.cs @@ -7,8 +7,9 @@ namespace Snowflake.Data.Core using System; using System.Collections.Generic; using System.IO; - using System.Linq; using System.Text; + using Client; + using Log; using Tomlyn; using Tomlyn.Model; using Tools; @@ -19,13 +20,13 @@ public class SnowflakeTomlConnectionBuilder private const string DefaultSnowflakeFolder = ".snowflake"; private const string DefaultTokenPath = "/snowflake/session/token"; - private Dictionary TomlToNetPropertiesMapper = new Dictionary(StringComparer.InvariantCultureIgnoreCase) + private readonly SFLogger _logger = SFLoggerFactory.GetLogger(); + + private readonly Dictionary _tomlToNetPropertiesMapper = new Dictionary(StringComparer.InvariantCultureIgnoreCase) { { "DATABASE", "DB" } }; - - private readonly FileOperations _fileOperations; private readonly EnvironmentOperations _environmentOperations; @@ -41,14 +42,14 @@ internal SnowflakeTomlConnectionBuilder(FileOperations fileOperations, Environme public string GetConnectionStringFromToml(string connectionName = null) { + var connectionString = string.Empty; var tomlPath = ResolveConnectionTomlFile(); var connectionToml = GetTomlTableFromConfig(tomlPath, connectionName); if (connectionToml != null) { - var connectionString = GetConnectionStringFromTomlTable(connectionToml); - return connectionString; + connectionString = GetConnectionStringFromTomlTable(connectionToml); } - return string.Empty; + return connectionString; } private string GetConnectionStringFromTomlTable(TomlTable connectionToml) @@ -63,7 +64,7 @@ private string GetConnectionStringFromTomlTable(TomlTable connectionToml) tokenFilePathValue = (string)connectionToml[property]; continue; } - var mappedProperty = TomlToNetPropertiesMapper.TryGetValue(property, out var mapped) ? mapped : property; + var mappedProperty = _tomlToNetPropertiesMapper.TryGetValue(property, out var mapped) ? mapped : property; connectionStringBuilder.Append($"{mappedProperty}={(string)connectionToml[property]};"); } @@ -77,7 +78,7 @@ private string GetConnectionStringFromTomlTable(TomlTable connectionToml) } else { - // log warning TODO + _logger.Warn("The token has empty value"); } @@ -86,13 +87,13 @@ private string GetConnectionStringFromTomlTable(TomlTable connectionToml) private string LoadTokenFromFile(string tokenFilePathValue) { - var tokenFile = _fileOperations.Exists(tokenFilePathValue) ? tokenFilePathValue : DefaultTokenPath; + var tokenFile = !string.IsNullOrEmpty(tokenFilePathValue) && _fileOperations.Exists(tokenFilePathValue) ? tokenFilePathValue : DefaultTokenPath; + _logger.Debug($"Read token from file path: {tokenFile}"); return _fileOperations.Exists(tokenFile) ? _fileOperations.ReadAllText(tokenFile) : null; } private TomlTable GetTomlTableFromConfig(string tomlPath, string connectionName) { - TomlTable result = null; if (!_fileOperations.Exists(tomlPath)) { return null; @@ -111,7 +112,7 @@ private TomlTable GetTomlTableFromConfig(string tomlPath, string connectionName) throw new Exception("Specified connection name does not exist in connections.toml"); } - result = connection as TomlTable; + var result = connection as TomlTable; return result; }