diff --git a/README.md b/README.md
index 0309ea1..e041892 100644
--- a/README.md
+++ b/README.md
@@ -65,16 +65,35 @@ StackifyLib.Logger.GlobalEnvironment = "";
By default the library will use the WebRequest.DefaultWebProxy. If you want to set a specific proxy server only for StackifyLib, you can do so in code OR via config.
+For proxy using custom credentials:
```xml
```
+For proxy using default credentials:
+```xml
+
+
+
+```
+
+For proxy that needs to be tailor fit
```csharp
StackifyLib.Utils.HttpClient.CustomWebProxy = new WebProxy();
```
+In case it need some web request intervention, a delegate has been provided to customize the http request
+```csharp
+StackifyLib.Utils.HttpClient.CustomRequestModifier = (request) =>
+{
+ // Examples:
+ request.PreAuthenticate = true; // If proxy needs pre-authentication
+ request.Timeout = 30000;
+};
+```
+
If you are having problems you can get logging out of the framework by hooking in to its custom logging.
```csharp
diff --git a/Src/NLog.Targets.Stackify/NLog.Targets.Stackify.csproj b/Src/NLog.Targets.Stackify/NLog.Targets.Stackify.csproj
index 742c498..ff29ab3 100644
--- a/Src/NLog.Targets.Stackify/NLog.Targets.Stackify.csproj
+++ b/Src/NLog.Targets.Stackify/NLog.Targets.Stackify.csproj
@@ -2,7 +2,7 @@
NLog.Targets.Stackify
- 2.0.0
+ 3.0.0
netstandard2.0;net40;net45;net461
NLog.Targets.Stackify
NLog.Targets.Stackify
@@ -15,7 +15,7 @@
false
false
false
- 2.2.0-beta1
+ 3.0.0
https://github.com/stackify/stackify-api-dotnet/blob/master/LICENSE
https://github.com/stackify/stackify-api-dotnet
https://stackify.com/wp-content/uploads/2017/02/stk.png
@@ -25,8 +25,8 @@
-
-
+
+
diff --git a/Src/NLog.Web.Stackify/NLog.Web.Stackify.csproj b/Src/NLog.Web.Stackify/NLog.Web.Stackify.csproj
index 3847e4b..d2db6ba 100644
--- a/Src/NLog.Web.Stackify/NLog.Web.Stackify.csproj
+++ b/Src/NLog.Web.Stackify/NLog.Web.Stackify.csproj
@@ -15,7 +15,7 @@
false
false
false
- 2.2.0-beta1
+ 2.2.0
https://github.com/stackify/stackify-api-dotnet/blob/master/LICENSE
https://github.com/stackify/stackify-api-dotnet
https://stackify.com/wp-content/uploads/2017/02/stk.png
diff --git a/Src/StackifyLib.log4net.Tests/StackifyLib.log4net.Tests.csproj b/Src/StackifyLib.log4net.Tests/StackifyLib.log4net.Tests.csproj
index 75acbae..66781be 100644
--- a/Src/StackifyLib.log4net.Tests/StackifyLib.log4net.Tests.csproj
+++ b/Src/StackifyLib.log4net.Tests/StackifyLib.log4net.Tests.csproj
@@ -33,8 +33,8 @@
4
-
- ..\packages\log4net.2.0.11\lib\net45\log4net.dll
+
+ ..\packages\log4net.2.0.17\lib\net45\log4net.dll
..\packages\Newtonsoft.Json.13.0.1\lib\net45\Newtonsoft.Json.dll
@@ -42,9 +42,13 @@
..\packages\NUnit.3.13.2\lib\net45\nunit.framework.dll
+
+ ..\packages\StackifyLib.2.2.15\lib\net452\StackifyLib.dll
+
+
@@ -65,10 +69,6 @@
{7d98e5e3-a3e7-4613-8bb7-80a60d86a5e8}
StackifyLib.log4net
-
- {03cf7411-65e2-4998-a157-9224eca6199b}
- StackifyLib
-
diff --git a/Src/StackifyLib.log4net.Tests/packages.config b/Src/StackifyLib.log4net.Tests/packages.config
index f89ee51..19fcb11 100644
--- a/Src/StackifyLib.log4net.Tests/packages.config
+++ b/Src/StackifyLib.log4net.Tests/packages.config
@@ -1,6 +1,7 @@
-
+
+
\ No newline at end of file
diff --git a/Src/StackifyLib.log4net/StackifyLib.log4net.csproj b/Src/StackifyLib.log4net/StackifyLib.log4net.csproj
index a02870b..3fd6483 100644
--- a/Src/StackifyLib.log4net/StackifyLib.log4net.csproj
+++ b/Src/StackifyLib.log4net/StackifyLib.log4net.csproj
@@ -2,7 +2,8 @@
Stackify Log4Net Appender
- 2.0.0
+ 2.1.7
+
netstandard2.0;net40;net45
StackifyLib.log4net
StackifyLib.log4net
@@ -13,7 +14,7 @@
false
false
false
- 2.1.6
+ 2.1.7
https://github.com/stackify/stackify-api-dotnet/blob/master/LICENSE
https://github.com/stackify/stackify-api-dotnet
https://github.com/stackify/stackify-api-dotnet
@@ -22,11 +23,8 @@
-
-
-
-
-
+
+
diff --git a/Src/StackifyLib/Config.cs b/Src/StackifyLib/Config.cs
index 23e0c6f..f8322c4 100644
--- a/Src/StackifyLib/Config.cs
+++ b/Src/StackifyLib/Config.cs
@@ -166,6 +166,13 @@ public static void LoadSettings()
{
RumKey = rumKey;
}
+
+ ProxyServer = Get("Stackify.ProxyServer", string.Empty);
+ var proxyUseDefaultCredentials = Get("Stackify.ProxyUseDefaultCredentials", string.Empty);
+ if (string.IsNullOrWhiteSpace(proxyUseDefaultCredentials) == false)
+ {
+ ProxyUseDefaultCredentials = proxyUseDefaultCredentials.Equals(bool.TrueString, StringComparison.CurrentCultureIgnoreCase);
+ }
}
catch (Exception ex)
{
@@ -217,6 +224,9 @@ public static void LoadSettings()
public static string RumKey { get; set; }
+ public static string ProxyServer { get; set; }
+ public static bool? ProxyUseDefaultCredentials { get; set; } = false;
+
///
/// Attempts to fetch a setting value given the key.
diff --git a/Src/StackifyLib/Models/AzureConfig.cs b/Src/StackifyLib/Models/AzureConfig.cs
index 8dad9a0..7f1eefa 100644
--- a/Src/StackifyLib/Models/AzureConfig.cs
+++ b/Src/StackifyLib/Models/AzureConfig.cs
@@ -1,10 +1,8 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Text;
-using System.Text.RegularExpressions;
+// Copyright (c) 2024-2025 BMC Software, Inc.
+// Copyright (c) 2021-2024 Netreo
+// Copyright (c) 2019 Stackify
+using System;
using StackifyLib.Utils;
-using System.Linq;
#if NETFULL
using Microsoft.Win32;
#endif
@@ -13,22 +11,26 @@ namespace StackifyLib.Models
{
public class AzureConfig
{
- private static bool? _inAzure;
- private static AzureRoleType _azureRoleType = AzureRoleType.Unknown;
- private static string _azureRoleName;
- private static string _azureInstanceName;
- private static string _entryPoint;
+ private static AzureConfig _instance = new AzureConfig();
+ public static AzureConfig Instance => _instance;
- private static readonly DateTime AppStarted = DateTime.UtcNow;
+ private bool? _inAzure;
+ private AzureRoleType _azureRoleType = AzureRoleType.Unknown;
+ private string _azureRoleName;
+ private string _azureInstanceName;
+ private string _entryPoint;
- private static readonly object Locker = new object();
+ private readonly DateTime AppStarted = DateTime.UtcNow;
+ private readonly object Locker = new object();
+ public const string ProductionEnvName = "Production";
- public static string AzureAppWebConfigEnvironment { get; set; }
- public static string AzureAppWebConfigApiKey { get; set; }
- public static string AzureDriveLetter { get; set; }
- public static string AzureInstanceName
+ public string AzureAppWebConfigEnvironment { get; set; }
+ public string AzureAppWebConfigApiKey { get; set; }
+ public string AzureDriveLetter { get; set; }
+
+ public string AzureInstanceName
{
get
{
@@ -37,7 +39,7 @@ public static string AzureInstanceName
}
}
- public static bool InAzure
+ public bool InAzure
{
get
{
@@ -62,7 +64,7 @@ public static bool InAzure
}
}
- public static bool IsWebsite
+ public bool IsWebsite
{
get
{
@@ -75,12 +77,24 @@ public static bool IsWebsite
}
}
- private static void EnsureInAzureRan()
+ private EnvironmentDetail _environmentDetail = null;
+
+ public AzureConfig()
+ {
+
+ }
+
+ public AzureConfig(EnvironmentDetail environmentDetail)
+ {
+ _environmentDetail = environmentDetail;
+ }
+
+ private void EnsureInAzureRan()
{
bool ensureTestHasBeenDone = InAzure;
}
- public static void LoadAzureSettings()
+ public void LoadAzureSettings()
{
if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("WEBSITE_SITE_NAME")) == false && string.IsNullOrEmpty(Environment.GetEnvironmentVariable("WEBSITE_INSTANCE_ID")) == false)
{
@@ -90,13 +104,13 @@ public static void LoadAzureSettings()
var slotId = GetDeploymentSlotId() ?? "0000";
_azureRoleName = Environment.GetEnvironmentVariable("WEBSITE_SITE_NAME");
- _azureInstanceName = $"{Environment.GetEnvironmentVariable("WEBSITE_SITE_NAME")} {ServerConfigHelper.GetEnvironment()} [{slotId}-{Environment.GetEnvironmentVariable("WEBSITE_INSTANCE_ID").Left(6)}]";
+ _azureInstanceName = $"{Environment.GetEnvironmentVariable("WEBSITE_SITE_NAME")} {GetEnvironment()} [{slotId}-{Environment.GetEnvironmentVariable("WEBSITE_INSTANCE_ID").Left(6)}]";
return;
}
}
- public static string GetDeploymentSlotId()
+ public string GetDeploymentSlotId()
{
try
{
@@ -113,6 +127,32 @@ public static string GetDeploymentSlotId()
return null;
}
+
+ public string GetEnvironment()
+ {
+ if (IsWebsite)
+ {
+ if (_environmentDetail == null)
+ {
+ _environmentDetail = EnvironmentDetail.Get();
+ }
+
+ var key = Environment.GetEnvironmentVariable("Stackify.Environment");
+ if (string.IsNullOrEmpty(key))
+ {
+ key = _environmentDetail.ConfiguredEnvironmentName;
+ }
+
+ if (string.IsNullOrEmpty(key) == false)
+ {
+ return key;
+ }
+
+ return ProductionEnvName;
+ }
+
+ return string.Empty;
+ }
}
public enum AzureRoleType : short
diff --git a/Src/StackifyLib/Models/EnvironmentDetail.cs b/Src/StackifyLib/Models/EnvironmentDetail.cs
index f16d4ff..9fbbd04 100644
--- a/Src/StackifyLib/Models/EnvironmentDetail.cs
+++ b/Src/StackifyLib/Models/EnvironmentDetail.cs
@@ -1,4 +1,4 @@
-// Copyright (c) 2024 BMC Software, Inc.
+// Copyright (c) 2024-2025 BMC Software, Inc.
// Copyright (c) 2021-2024 Netreo
// Copyright (c) 2019 Stackify
@@ -30,9 +30,9 @@ public static EnvironmentDetail Get()
return _CachedCopy ?? (_CachedCopy = new EnvironmentDetail());
}
#if NETSTANDARD
- private static System.Net.Http.HttpClient Client => new System.Net.Http.HttpClient();
+ private System.Net.Http.HttpClient Client => new System.Net.Http.HttpClient();
#endif
- private static bool registryAccessFailure = false;
+ private bool registryAccessFailure = false;
///
/// Figures out if the server is in azure and if so gets the azure instance name
@@ -41,9 +41,9 @@ private void GetAzureInfo()
{
//IsAzureWorkerRole is set when instantiating EnvironmentDetail
//Useful in other parts directly referencing AzureInstanceName
- if(!IsAzureWorkerRole && AzureConfig.InAzure && AzureConfig.IsWebsite)
+ if(!IsAzureWorkerRole && AzureConfig.Instance.InAzure && AzureConfig.Instance.IsWebsite)
{
- AzureInstanceName = AzureConfig.AzureInstanceName;
+ AzureInstanceName = AzureConfig.Instance.AzureInstanceName;
}
@@ -110,23 +110,23 @@ private void GetAzureInfo()
#endif
}
- private static bool? _isIMDSv1;
+ private bool? _isIMDSv1;
// http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-metadata.html#d0e30002
private const string EC2InstanceIdUrl = "http://169.254.169.254/latest/meta-data/instance-id";
private const string IMDS_BASE_URL = "http://169.254.169.254/latest";
private const string IMDS_TOKEN_PATH = "/api/token";
private const string IMDS_INSTANCE_ID_PATH = "/meta-data/instance-id";
private const string IMDSV1_BASE_URL = "http://169.254.169.254/latest/meta-data/";
- public static readonly object ec2InstanceLock = new object();
- private static DateTimeOffset? ec2InstanceIdLastUpdate = null;
- private static string ec2InstanceId = null;
+ public static readonly object ec2InstanceLock = new object();
+ private DateTimeOffset? ec2InstanceIdLastUpdate = null;
+ private string ec2InstanceId = string.Empty;
///
/// Get the EC2 Instance name if it exists else null
///
#if NETFULL
- public static string GetDeviceName()
+ public string GetDeviceName()
{
var deviceName = Environment.GetEnvironmentVariable("STACKIFY_DEVICE_NAME");
if (!String.IsNullOrEmpty(deviceName))
@@ -139,7 +139,7 @@ public static string GetDeviceName()
var isDefaultDeviceNameEc2 = IsEc2MachineName(deviceName);
- if (Config.IsEc2 == null || Config.IsEc2 == true || isDefaultDeviceNameEc2)
+ if ((Config.IsEc2.HasValue && Config.IsEc2 == true) || isDefaultDeviceNameEc2)
{
var instanceID_task = GetEC2InstanceId();
if (string.IsNullOrWhiteSpace(instanceID_task) == false)
@@ -151,7 +151,7 @@ public static string GetDeviceName()
return deviceName.Substring(0, deviceName.Length > 60 ? 60 : deviceName.Length);
}
- public static bool IsIMDSv1()
+ public bool IsIMDSv1()
{
if (_isIMDSv1.HasValue)
@@ -174,7 +174,7 @@ public static bool IsIMDSv1()
}
}
- public static string GetAccessToken()
+ public string GetAccessToken()
{
var url = IMDS_BASE_URL + IMDS_TOKEN_PATH;
var httpRequest = (HttpWebRequest)WebRequest.Create(url);
@@ -190,7 +190,7 @@ public static string GetAccessToken()
}
- public static string GetEC2InstanceId()
+ public string GetEC2InstanceId()
{
string r = null;
@@ -258,7 +258,7 @@ public static string GetEC2InstanceId()
return r;
}
#else
- public static string GetDeviceName()
+ public string GetDeviceName()
{
var deviceName = Environment.GetEnvironmentVariable("STACKIFY_DEVICE_NAME");
if (!String.IsNullOrEmpty(deviceName))
@@ -271,7 +271,7 @@ public static string GetDeviceName()
var isDefaultDeviceNameEc2 = IsEc2MachineName(deviceName);
- if (Config.IsEc2 == null || Config.IsEc2 == true || isDefaultDeviceNameEc2)
+ if ((Config.IsEc2.HasValue && Config.IsEc2 == true) || isDefaultDeviceNameEc2)
{
var instanceID_task = GetEC2InstanceId();
instanceID_task.Wait();
@@ -284,7 +284,7 @@ public static string GetDeviceName()
return deviceName.Substring(0, deviceName.Length > 60 ? 60 : deviceName.Length);
}
- public static async Task GetAccessTokenAsync()
+ public async Task GetAccessTokenAsync()
{
var url = IMDS_BASE_URL + IMDS_TOKEN_PATH;
var request = new System.Net.Http.HttpRequestMessage(System.Net.Http.HttpMethod.Put, url);
@@ -294,7 +294,7 @@ public static async Task GetAccessTokenAsync()
return await response.Content.ReadAsStringAsync().ConfigureAwait(false);
}
- public static async Task IsIMDSv1()
+ public async Task IsIMDSv1()
{
if (_isIMDSv1.HasValue)
{
@@ -314,7 +314,7 @@ public static async Task IsIMDSv1()
}
}
- public static async Task GetEC2InstanceId()
+ public async Task GetEC2InstanceId()
{
string r = null;
try
@@ -345,7 +345,7 @@ public static async Task GetEC2InstanceId()
}
#endif
- private static bool IsEc2MachineName(string machineName)
+ private bool IsEc2MachineName(string machineName)
{
if (string.IsNullOrWhiteSpace(machineName))
{
diff --git a/Src/StackifyLib/Models/ErrorItem.cs b/Src/StackifyLib/Models/ErrorItem.cs
index b14c7e1..4692b00 100644
--- a/Src/StackifyLib/Models/ErrorItem.cs
+++ b/Src/StackifyLib/Models/ErrorItem.cs
@@ -298,15 +298,15 @@ public string FramesToString()
{
StringBuilder sb = new StringBuilder();
- foreach (var item in this.StackTrace)
+ if (InnerError != null)
{
- sb.AppendFormat(" at {0}\r\n", item.Method);
+ sb.Append(InnerError.FramesToString());
+ sb.Append("--- End of inner exception stack trace ---\r\n");
}
- if (InnerError != null)
+ foreach (var item in this.StackTrace)
{
- sb.Append("--- End of inner exception stack trace ---\r\n");
- sb.Append(InnerError.FramesToString());
+ sb.AppendFormat(" at {0}\r\n", item.Method);
}
return sb.ToString();
diff --git a/Src/StackifyLib/StackifyLib.csproj b/Src/StackifyLib/StackifyLib.csproj
index 961f4eb..68a1a16 100644
--- a/Src/StackifyLib/StackifyLib.csproj
+++ b/Src/StackifyLib/StackifyLib.csproj
@@ -2,7 +2,8 @@
Stackify API
- 2.2.14
+ 2.2.18
+
netstandard2.0;net40;net45;net451;net452;net46;net461;net462
StackifyLib
StackifyLib
@@ -13,15 +14,15 @@
false
false
false
- 2.2.14-beta
+ 2.2.18
StackifyLib
https://github.com/stackify/stackify-api-dotnet
https://github.com/stackify/stackify-api-dotnet/blob/master/LICENSE
https://github.com/stackify/stackify-api-dotnet
git
https://stackify.com/wp-content/uploads/2017/02/stk.png
- 2.2.14.0
- 2.2.14.0
+ 2.2.18.0
+ 2.2.18.0
Remove default internal file logger
false
diff --git a/Src/StackifyLib/Utils/HttpClient.cs b/Src/StackifyLib/Utils/HttpClient.cs
index c8619d2..729c6ac 100644
--- a/Src/StackifyLib/Utils/HttpClient.cs
+++ b/Src/StackifyLib/Utils/HttpClient.cs
@@ -1,4 +1,7 @@
-using System;
+// Copyright (c) 2024-2025 BMC Software, Inc.
+// Copyright (c) 2021-2024 Netreo
+// Copyright (c) 2019 Stackify
+using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
@@ -28,7 +31,9 @@ public class AppIdentityInfo
public class HttpClient
{
- public static IWebProxy CustomWebProxy = null;
+ public static IWebProxy CustomWebProxy = null;
+ public static Action CustomRequestModifier = null;
+ public static bool IsUnitTest = false;
public string BaseAPIUrl { get; private set; }
@@ -80,9 +85,7 @@ public bool IsClientError()
static HttpClient()
{
-#if NETFULL
LoadWebProxyConfig();
-#endif
}
public HttpClient(string apiKey, string apiUrl)
@@ -118,39 +121,31 @@ public HttpClient(string apiKey, string apiUrl)
BaseAPIUrl += "/";
}
-#if NETFULL
public static void LoadWebProxyConfig()
{
try
{
- string val = Config.Get("Stackify.ProxyServer");
-
+ string val = Config.ProxyServer;
if (!string.IsNullOrEmpty(val))
{
-
StackifyAPILogger.Log("Setting proxy server based on override config", true);
-
var uri = new Uri(val);
-
- var proxy = new WebProxy(uri, false);
+ WebProxy proxy = new WebProxy(uri);
if (!string.IsNullOrEmpty(uri.UserInfo) && uri.UserInfo.Contains(":"))
{
string[] pieces = uri.UserInfo.Split(':');
-
- proxy.Credentials = new NetworkCredential(pieces[0], pieces[1]);
+ proxy = new WebProxy(uri)
+ {
+ Credentials = new NetworkCredential(pieces[0], pieces[1])
+ };
}
else
{
-
- string settingUseDefault = Config.Get("Stackify.ProxyUseDefaultCredentials");
-
- bool useDefault;
-
- if (!string.IsNullOrEmpty(settingUseDefault) && bool.TryParse(settingUseDefault, out useDefault))
+ bool? settingUseDefault = Config.ProxyUseDefaultCredentials;
+ if (settingUseDefault.HasValue && settingUseDefault.Value)
{
- //will make it use the user of the running windows service
- proxy.UseDefaultCredentials = useDefault;
+ proxy.UseDefaultCredentials = true;
}
}
CustomWebProxy = proxy;
@@ -161,7 +156,6 @@ public static void LoadWebProxyConfig()
StackifyAPILogger.Log("Error setting default web proxy " + ex.Message, true);
}
}
-#endif
///
/// This method does some throttling when errors happen to control when it should try again. Error backoff logic
@@ -313,9 +307,9 @@ public bool IdentifyApp()
}
//Applicable only for Azure AppService
- if(AzureConfig.InAzure && AzureConfig.IsWebsite)
+ if(AzureConfig.Instance.InAzure && AzureConfig.Instance.IsWebsite)
{
- env.DeviceName = AzureConfig.AzureInstanceName;
+ env.DeviceName = AzureConfig.Instance.AzureInstanceName;
}
string jsonData = JsonConvert.SerializeObject(env, new JsonSerializerSettings() { NullValueHandling = NullValueHandling.Ignore });
@@ -611,19 +605,35 @@ private HttpWebRequest BuildJsonRequest(string url, string jsonData, bool compre
request.Headers[HttpRequestHeader.UserAgent] = "StackifyLib-" + _version;
#endif
- request.Headers["X-Stackify-Key"] = this.APIKey;
+ request.Headers.Add("X-Stackify-Key", this.APIKey);
request.ContentType = "application/json";
- //if (HttpClient.CustomWebProxy != null)
- //{
+ if (CustomWebProxy != null)
+ {
+ request.Proxy = CustomWebProxy;
+ }
- // request.Proxy = HttpClient.CustomWebProxy;
- //}
+ if (CustomRequestModifier != null)
+ {
+ try
+ {
+ CustomRequestModifier(request);
+ }
+ catch (Exception ex)
+ {
+ StackifyAPILogger.Log($"Failed to set CustomRequestModifier - Message: {ex.Message}", true);
+ }
+ }
if (!string.IsNullOrEmpty(jsonData) && compress)
{
request.Method = "POST";
- request.Headers[HttpRequestHeader.ContentEncoding] = "gzip";
+ request.Headers.Add(HttpRequestHeader.ContentEncoding, "gzip");
+
+ if (IsUnitTest)
+ {
+ return request;
+ }
byte[] payload = Encoding.UTF8.GetBytes(jsonData);
@@ -644,8 +654,12 @@ private HttpWebRequest BuildJsonRequest(string url, string jsonData, bool compre
{
request.Method = "POST";
- byte[] payload = Encoding.UTF8.GetBytes(jsonData);
+ if (IsUnitTest)
+ {
+ return request;
+ }
+ byte[] payload = Encoding.UTF8.GetBytes(jsonData);
#if NETFULL
request.ContentLength= payload.Length;
using (Stream stream = request.GetRequestStream())
@@ -694,14 +708,30 @@ private HttpWebRequest BuildPOSTRequest(string url, string postdata)
request.Headers[HttpRequestHeader.ContentLength] = "0";
#endif
- //if (HttpClient.CustomWebProxy != null)
- //{
- // request.Proxy = HttpClient.CustomWebProxy;
- //}
+ if (HttpClient.CustomWebProxy != null)
+ {
+ request.Proxy = HttpClient.CustomWebProxy;
+ }
+ if (CustomRequestModifier != null)
+ {
+ try
+ {
+ CustomRequestModifier(request);
+ }
+ catch (Exception ex)
+ {
+ StackifyAPILogger.Log($"Failed to set CustomRequestModifier - Message: {ex.Message}", true);
+ }
+ }
request.Method = "POST";
+ if (IsUnitTest)
+ {
+ return request;
+ }
+
if (!String.IsNullOrEmpty(postdata))
{
byte[] payload = Encoding.UTF8.GetBytes(postdata);
diff --git a/Src/StackifyLib/Utils/ServerConfigHelper.cs b/Src/StackifyLib/Utils/ServerConfigHelper.cs
deleted file mode 100644
index 67834c6..0000000
--- a/Src/StackifyLib/Utils/ServerConfigHelper.cs
+++ /dev/null
@@ -1,34 +0,0 @@
-using StackifyLib.Models;
-using System;
-using System.Collections.Generic;
-using System.Text;
-
-namespace StackifyLib.Utils
-{
- public class ServerConfigHelper
- {
- public const string ProductionEnvName = "Production";
-
- public static string GetEnvironment()
- {
- if (AzureConfig.IsWebsite)
- {
- var key = Environment.GetEnvironmentVariable("Stackify.Environment");
-
- if (string.IsNullOrEmpty(key) == false)
- {
- return key;
- }
-
- if (string.IsNullOrEmpty(AzureConfig.AzureAppWebConfigEnvironment) == false)
- {
- return AzureConfig.AzureAppWebConfigEnvironment;
- }
-
- return ProductionEnvName;
- }
-
- return string.Empty;
- }
- }
-}
diff --git a/Src/StackifyLibCore.sln b/Src/StackifyLibCore.sln
index a726538..6f15531 100644
--- a/Src/StackifyLibCore.sln
+++ b/Src/StackifyLibCore.sln
@@ -1,7 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 16
-VisualStudioVersion = 16.0.30907.101
+# Visual Studio Version 17
+VisualStudioVersion = 17.11.35303.130
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StackifyLib", "StackifyLib\StackifyLib.csproj", "{03CF7411-65E2-4998-A157-9224ECA6199B}"
EndProject
@@ -71,11 +71,9 @@ Global
{D0E1AABD-6397-4CC7-9737-4904A4BF5BCD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D0E1AABD-6397-4CC7-9737-4904A4BF5BCD}.Release|Any CPU.Build.0 = Release|Any CPU
{2B832B34-BA30-4B5A-8E78-D6DC78AF16FB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {2B832B34-BA30-4B5A-8E78-D6DC78AF16FB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2B832B34-BA30-4B5A-8E78-D6DC78AF16FB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2B832B34-BA30-4B5A-8E78-D6DC78AF16FB}.Release|Any CPU.Build.0 = Release|Any CPU
{BC6FDF2B-E063-4F8B-ABBA-6BD9425D4C64}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {BC6FDF2B-E063-4F8B-ABBA-6BD9425D4C64}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BC6FDF2B-E063-4F8B-ABBA-6BD9425D4C64}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BC6FDF2B-E063-4F8B-ABBA-6BD9425D4C64}.Release|Any CPU.Build.0 = Release|Any CPU
{CB09D719-F393-413D-B5CD-E0125543AD24}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
@@ -99,15 +97,12 @@ Global
{647F27AD-13D7-40C2-A7BB-97A5D19CE3D5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{647F27AD-13D7-40C2-A7BB-97A5D19CE3D5}.Release|Any CPU.Build.0 = Release|Any CPU
{ADBD2138-9DE7-4211-AF58-84C7F0F9BEA5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {ADBD2138-9DE7-4211-AF58-84C7F0F9BEA5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{ADBD2138-9DE7-4211-AF58-84C7F0F9BEA5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{ADBD2138-9DE7-4211-AF58-84C7F0F9BEA5}.Release|Any CPU.Build.0 = Release|Any CPU
{99C81723-20FB-4241-862C-32F7ADDB7A9B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {99C81723-20FB-4241-862C-32F7ADDB7A9B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{99C81723-20FB-4241-862C-32F7ADDB7A9B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{99C81723-20FB-4241-862C-32F7ADDB7A9B}.Release|Any CPU.Build.0 = Release|Any CPU
{EC5742DF-C1A8-4A7A-81DC-B6E0A4D3472D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {EC5742DF-C1A8-4A7A-81DC-B6E0A4D3472D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EC5742DF-C1A8-4A7A-81DC-B6E0A4D3472D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EC5742DF-C1A8-4A7A-81DC-B6E0A4D3472D}.Release|Any CPU.Build.0 = Release|Any CPU
{18F96933-468E-4104-961E-0C68A5EF2B29}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
diff --git a/azure-pipelines.yml b/azure-pipelines.yml
index 5aa5fb7..1299979 100644
--- a/azure-pipelines.yml
+++ b/azure-pipelines.yml
@@ -16,14 +16,6 @@ pool:
# name: Azure Pipelines
parameters:
- - name: RUN_VERACODE_SCAN
- displayName: 'Upload to Veracode'
- type: boolean
- default: true
- - name: RUN_VERACODE_SCA
- displayName: 'Execute Veracode SCA'
- type: boolean
- default: true
- name: 'Build StackifyLib'
type: boolean
default: true
@@ -63,9 +55,21 @@ stages:
- task: AzureKeyVault@2
displayName: 'Azure Key Vault: keyvault-build-resources'
inputs:
- ConnectedServiceName: 514ed7d6-3846-4422-8013-af27483dd22c
+ ConnectedServiceName: 5313674e-7e2b-426b-9eaf-8d3d5bd22e2c
KeyVaultName: keyvault-build-resources
RunAsPreJob: true
+ - task: DotNetCoreCLI@2
+ displayName: dotnet restore
+ inputs:
+ command: restore
+ projects: |
+ Src\StackifyLib\*.csproj
+ Src\StackifyLib.AspNetCore\*.csproj
+ Src\StackifyLib.CoreLogger\*.csproj
+ Src\StackifyLib.log4net\*.csproj
+ Src\Nlog.Targets.Stackify\*.csproj
+ Src\NLog.Web.Stackify\*.csproj
+ Src\StackifyLib.StackifyTraceListener\*.csproj
- ${{ if eq(parameters['Build StackifyLib Signed'], true) }}:
- task: DownloadSecureFile@1
name: SNK
@@ -94,19 +98,6 @@ stages:
(gc $_) -replace ([regex]::Escape("//[assembly: AssemblyKeyFile")), "[assembly: AssemblyKeyFile" | Set-Content $_.FullName -Encoding utf8
}
displayName: Uncoment signing attribute
- - task: DotNetCoreCLI@2
- displayName: dotnet restore
- inputs:
- command: restore
- projects: |
- Src\StackifyLib\*.csproj
- Src\StackifyLib.AspNetCore\*.csproj
- Src\StackifyLib.CoreLogger\*.csproj
- Src\StackifyLib.log4net\*.csproj
- Src\Nlog.Targets.Stackify\*.csproj
- Src\NLog.Web.Stackify\*.csproj
- Src\StackifyLib.StackifyTraceListener\*.csproj
- - ${{ if eq(parameters['Build StackifyLib'], true) }}:
- task: DotNetCoreCLI@2
displayName: dotnet build
inputs:
@@ -119,7 +110,6 @@ stages:
command: pack
searchPatternPack: Src\StackifyLib\*.csproj;
nobuild: true
- - ${{ if eq(parameters['Build StackifyLib Signed'], true) }}:
- task: DotNetCoreCLI@2
displayName: Install NuGetKeyVaultSignTool
continueOnError: true
@@ -184,6 +174,19 @@ stages:
targetType: inline
script: >
(Get-Content -path Src\StackifyLib\StackifyLib.csproj -Raw) -replace 'StackifyLib.signed','StackifyLib' | Set-Content -Path Src\StackifyLib\StackifyLib.csproj
+ - ${{ if eq(parameters['Build StackifyLib'], true) }}:
+ - task: DotNetCoreCLI@2
+ displayName: dotnet build
+ inputs:
+ projects: |
+ Src\StackifyLib\*.csproj
+ arguments: '-c $(BuildConfiguration)'
+ - task: DotNetCoreCLI@2
+ displayName: dotnet pack unsigned stackify lib
+ inputs:
+ command: pack
+ searchPatternPack: Src\StackifyLib\*.csproj;
+ nobuild: true
- ${{ if eq(parameters['Build StackifyLib.AspNetCore'], true) }}:
- template: templates/build-and-pack.yml
parameters:
@@ -243,23 +246,4 @@ stages:
$version = "$version".Trim()
echo "##vso[task.setvariable variable=version]$version"
echo "StackifyLib."$version".nupkg"
- workingDirectory: $(Build.SourcesDirectory)
- - task: Veracode@3
- displayName: 'Upload and scan: $(Build.ArtifactStagingDirectory)/StackifyLib.$(version).nupkg'
- condition: and(succeeded(), eq('${{ parameters.RUN_VERACODE_SCAN }}', 'true'))
- enabled: True
- inputs:
- AnalysisService: 51003f89-58ab-463c-8e20-41484888d9c7
- veracodeAppProfile: Retrace .Net StackifyLib
- version: AZ-Devops-Build-$(build.buildNumber)
- filepath: $(Build.ArtifactStagingDirectory)/StackifyLib.$(version).nupkg
-
- - job: SCA
- pool:
- vmImage: 'windows-latest'
- steps:
- - task: PowerShell@2
- inputs:
- targetType: 'inline'
- script: $Env:SRCCLR_API_TOKEN="$(SRCCLR_API_TOKEN)"; Set-ExecutionPolicy AllSigned -Scope Process -Force; iex ((New-Object System.Net.WebClient).DownloadString('https://download.srcclr.com/ci.ps1')); srcclr scan --allow-dirty .;
- condition: and(succeeded(), eq('${{ parameters.RUN_VERACODE_SCA }}', 'true'))
\ No newline at end of file
+ workingDirectory: $(Build.SourcesDirectory)
\ No newline at end of file
diff --git a/samples/CoreConsoleApp/CoreConsoleApp.csproj b/samples/CoreConsoleApp/CoreConsoleApp.csproj
index 70540e5..0196127 100644
--- a/samples/CoreConsoleApp/CoreConsoleApp.csproj
+++ b/samples/CoreConsoleApp/CoreConsoleApp.csproj
@@ -13,7 +13,6 @@
-
@@ -22,7 +21,8 @@
-
+
+
diff --git a/samples/CoreWebApp/CoreWebApp.csproj b/samples/CoreWebApp/CoreWebApp.csproj
index 9dc3766..c8ce504 100644
--- a/samples/CoreWebApp/CoreWebApp.csproj
+++ b/samples/CoreWebApp/CoreWebApp.csproj
@@ -16,7 +16,6 @@
-
@@ -31,7 +30,8 @@
-
+
+
diff --git a/test/StackifyLib.UnitTests/ConfigJSON_Tests.cs b/test/StackifyLib.UnitTests/ConfigJSON_Tests.cs
index 3b4fbce..06d23de 100644
--- a/test/StackifyLib.UnitTests/ConfigJSON_Tests.cs
+++ b/test/StackifyLib.UnitTests/ConfigJSON_Tests.cs
@@ -1,3 +1,7 @@
+// Copyright (c) 2024 BMC Software, Inc.
+// Copyright (c) 2021-2024 Netreo
+// Copyright (c) 2019 Stackify
+#if NETCORE
using System;
using System.Collections.Generic;
using System.Diagnostics;
@@ -125,3 +129,4 @@ private void ResetConfig()
}
}
+#endif
\ No newline at end of file
diff --git a/test/StackifyLib.UnitTests/JsonSerialization_Tests.cs b/test/StackifyLib.UnitTests/JsonSerialization_Tests.cs
index 68ca968..14803d5 100644
--- a/test/StackifyLib.UnitTests/JsonSerialization_Tests.cs
+++ b/test/StackifyLib.UnitTests/JsonSerialization_Tests.cs
@@ -1,3 +1,7 @@
+// Copyright (c) 2024 BMC Software, Inc.
+// Copyright (c) 2021-2024 Netreo
+// Copyright (c) 2019 Stackify
+#if NETCORE
using System;
using System.Collections.Generic;
using System.Diagnostics;
@@ -134,3 +138,4 @@ private static object GetTestObject()
}
}
}
+#endif
\ No newline at end of file
diff --git a/test/StackifyLib.UnitTests/Models/AzureConfig_Tests.cs b/test/StackifyLib.UnitTests/Models/AzureConfig_Tests.cs
new file mode 100644
index 0000000..7ef334e
--- /dev/null
+++ b/test/StackifyLib.UnitTests/Models/AzureConfig_Tests.cs
@@ -0,0 +1,319 @@
+// Copyright (c) 2024-2025 BMC Software, Inc.
+using StackifyLib.Models;
+using System;
+using System.Collections.Generic;
+using Xunit;
+using Moq;
+#if NETCORE || NETCOREX
+using Microsoft.Extensions.Configuration;
+#endif
+
+namespace StackifyLib.UnitTests.Models
+{
+ public class AzureConfig_Tests
+ {
+ private readonly MockRepository _mockRepository;
+ private readonly Dictionary _originalEnvironment = new Dictionary();
+
+ public AzureConfig_Tests()
+ {
+ _mockRepository = new MockRepository(MockBehavior.Strict);
+ BackupEnvironmentVariables();
+ }
+
+ public void Dispose()
+ {
+ RestoreEnvironmentVariables();
+ }
+
+ private void BackupEnvironmentVariables()
+ {
+ _originalEnvironment["WEBSITE_SITE_NAME"] = Environment.GetEnvironmentVariable("WEBSITE_SITE_NAME");
+ _originalEnvironment["WEBSITE_INSTANCE_ID"] = Environment.GetEnvironmentVariable("WEBSITE_INSTANCE_ID");
+ _originalEnvironment["WEBSITE_IIS_SITE_NAME"] = Environment.GetEnvironmentVariable("WEBSITE_IIS_SITE_NAME");
+ _originalEnvironment["Stackify.Environment"] = Environment.GetEnvironmentVariable("Stackify.Environment");
+ }
+
+ private void RestoreEnvironmentVariables()
+ {
+ foreach (var kvp in _originalEnvironment)
+ {
+ Environment.SetEnvironmentVariable(kvp.Key, kvp.Value);
+ }
+ }
+
+ private void ResetAzureConfig()
+ {
+ var instanceField = typeof(AzureConfig).GetField("_instance", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static);
+ instanceField.SetValue(null, new AzureConfig());
+#if NETCORE || NETCOREX
+ Config.SetConfiguration(null);
+#endif
+ }
+
+ private void SetEnvironmentVariables(string siteName, string instanceId, string iisSiteName = null)
+ {
+ Environment.SetEnvironmentVariable("WEBSITE_SITE_NAME", siteName);
+ Environment.SetEnvironmentVariable("WEBSITE_INSTANCE_ID", instanceId);
+ Environment.SetEnvironmentVariable("WEBSITE_IIS_SITE_NAME", iisSiteName);
+ }
+
+ [Fact]
+ public void InAzure_ReturnsTrue_WhenAzureEnvironmentDetected()
+ {
+ SetEnvironmentVariables("my-site", "instance-id");
+ ResetAzureConfig();
+
+ var result = new AzureConfig(new EnvironmentDetail()).InAzure;
+ Assert.True(result);
+ }
+
+ [Fact]
+ public void InAzure_ReturnsFalse_WhenNotInAzure()
+ {
+ SetEnvironmentVariables(null, null);
+ ResetAzureConfig();
+
+ var result = new AzureConfig(new EnvironmentDetail()).InAzure;
+ Assert.False(result);
+ }
+
+ [Fact]
+ public void IsWebsite_ReturnsTrue_WhenWebAppDetected()
+ {
+ SetEnvironmentVariables("my-site", "instance-id");
+ ResetAzureConfig();
+
+ var result = new AzureConfig(new EnvironmentDetail()).IsWebsite;
+ Assert.True(result);
+ }
+
+ [Fact]
+ public void AzureInstanceName_CorrectFormat_WhenInWebApp()
+ {
+ SetEnvironmentVariables("my-site", "full-instance-id-12345", "site__1234");
+ ResetAzureConfig();
+
+ var instanceName = new AzureConfig(new EnvironmentDetail()).AzureInstanceName;
+ Assert.Equal("my-site Production [1234-full-i]", instanceName);
+ }
+
+ [Fact]
+ public void GetDeploymentSlotId_ReturnsLast4Chars_WhenSlotPresent()
+ {
+ SetEnvironmentVariables(null, null, "site-name__abcd");
+ ResetAzureConfig();
+
+ var slotId = new AzureConfig(new EnvironmentDetail()).GetDeploymentSlotId();
+ Assert.Equal("abcd", slotId);
+ }
+
+ [Fact]
+ public void GetDeploymentSlotId_ReturnsNull_WhenNoSlotPresent()
+ {
+ SetEnvironmentVariables(null, null, "site-name");
+ ResetAzureConfig();
+
+ var slotId = new AzureConfig(new EnvironmentDetail()).GetDeploymentSlotId();
+ Assert.Null(slotId);
+ }
+
+ [Fact(Skip = "Environment set is dynamic. Skip and run manually")]
+ public void GetEnvironment_ReturnsConfiguredEnvironment_WhenSet()
+ {
+ SetEnvironmentVariables("site", "instance");
+ Environment.SetEnvironmentVariable("Stackify.Environment", "Staging");
+ ResetAzureConfig();
+
+ var env = new AzureConfig(new EnvironmentDetail()).GetEnvironment();
+ Assert.Equal("Staging", env);
+ }
+
+ [Fact(Skip = "Environment set is dynamic. Skip and run manually")]
+ public void AzureInstanceName_CorrectFormat_WhenEnvStagingTestSet()
+ {
+ Environment.SetEnvironmentVariable("Stackify.Environment", "StagingTest");
+ SetEnvironmentVariables("my-site", "full-instance-id-12345", "site__1234");
+ ResetAzureConfig();
+
+ var instanceName = new AzureConfig(new EnvironmentDetail()).AzureInstanceName;
+ Assert.Equal("my-site StagingTest [1234-full-i]", instanceName);
+ }
+
+ [Fact]
+ public void GetEnvironment_ReturnsProduction_WhenNoConfigurationSet()
+ {
+ SetEnvironmentVariables("site", "instance");
+ ResetAzureConfig();
+ Config.LoadSettings();
+
+ var env = new AzureConfig(new EnvironmentDetail()).GetEnvironment();
+ Assert.Equal("Production", env);
+ }
+
+ [Fact]
+ public void GetEnvironment_ReturnsEmptyString_WhenNotWebsite()
+ {
+ SetEnvironmentVariables(null, null); // Not in Azure
+ ResetAzureConfig();
+
+ var env = new AzureConfig().GetEnvironment();
+ Assert.Equal(string.Empty, env);
+ }
+
+#if NETFULL
+ [Fact(Skip = "Environment set is dynamic. Skip and run manually")]
+ public void GetEnvironment_ReturnsTest_WhenAppSettingsSet()
+ {
+ Environment.SetEnvironmentVariable("Stackify.Environment", "");
+ System.Configuration.ConfigurationManager.AppSettings["Stackify.Environment"] = "Test";
+ SetEnvironmentVariables("site", "instance");
+ ResetAzureConfig();
+
+ var env = new AzureConfig(new EnvironmentDetail()).GetEnvironment();
+ Assert.Equal("Test", env);
+ }
+
+ [Fact(Skip = "Environment set is dynamic. Skip and run manually")]
+ public void GetEnvironment_ReturnsTest_WhenEnvSetFirst_AppSettingsSet()
+ {
+ Environment.SetEnvironmentVariable("Stackify.Environment", "StagingFirstApp");
+ System.Configuration.ConfigurationManager.AppSettings["Stackify.Environment"] = "Test";
+ SetEnvironmentVariables("site", "instance");
+ ResetAzureConfig();
+
+ var env = new AzureConfig(new EnvironmentDetail()).GetEnvironment();
+ Assert.Equal("StagingFirstApp", env);
+ }
+
+ [Fact(Skip = "Environment set is dynamic. Skip and run manually")]
+ public void AzureInstanceName_CorrectFormat_WhenAppSettingsEnvStagingAppSet()
+ {
+ Environment.SetEnvironmentVariable("Stackify.Environment", "");
+ System.Configuration.ConfigurationManager.AppSettings["Stackify.Environment"] = "StagingApp";
+ SetEnvironmentVariables("my-site", "full-instance-id-12345", "site__1234");
+ ResetAzureConfig();
+
+ var instanceName = new AzureConfig(new EnvironmentDetail()).AzureInstanceName;
+ Assert.Equal("my-site StagingApp [1234-full-i]", instanceName);
+ }
+
+ [Fact(Skip = "Environment set is dynamic. Skip and run manually")]
+ public void AzureInstanceName_CorrectFormat_WhenEnvSetFirst_AppSettings()
+ {
+ Environment.SetEnvironmentVariable("Stackify.Environment", "StagingFirst");
+ System.Configuration.ConfigurationManager.AppSettings["Stackify.Environment"] = "StagingApp";
+ SetEnvironmentVariables("my-site", "full-instance-id-12345", "site__1234");
+ ResetAzureConfig();
+
+ var instanceName = new AzureConfig(new EnvironmentDetail()).AzureInstanceName;
+ Assert.Equal("my-site StagingFirst [1234-full-i]", instanceName);
+ }
+#endif
+
+#if NETCORE || NETCOREX
+ [Fact(Skip = "Environment set is dynamic. Skip and run manually")]
+ public void GetEnvironment_ReturnsTestCore_WhenIConfigurationSet()
+ {
+ Environment.SetEnvironmentVariable("Stackify.Environment", "");
+ SetEnvironmentVariables("site", "instance");
+ ResetAzureConfig();
+
+ var iconfig = new ConfigurationBuilder()
+ .AddInMemoryCollection(new Dictionary
+ {
+ ["Stackify:Environment"] = "TestCore"
+ })
+ .Build();
+ Config.SetConfiguration(iconfig);
+ Config.LoadSettings();
+
+ var env = new AzureConfig(new EnvironmentDetail()).GetEnvironment();
+ Assert.Equal("TestCore", env);
+ }
+
+ [Fact(Skip = "Environment set is dynamic. Skip and run manually")]
+ public void GetEnvironment_ReturnsTestCore_WhenEnvFirst_IConfigurationSet()
+ {
+ Environment.SetEnvironmentVariable("Stackify.Environment", "TestCoreFirst");
+ SetEnvironmentVariables("site", "instance");
+ ResetAzureConfig();
+
+ var iconfig = new ConfigurationBuilder()
+ .AddInMemoryCollection(new Dictionary
+ {
+ ["Stackify:Environment"] = "TestCore"
+ })
+ .Build();
+ Config.SetConfiguration(iconfig);
+ Config.LoadSettings();
+
+ var env = new AzureConfig(new EnvironmentDetail()).GetEnvironment();
+ Assert.Equal("TestCoreFirst", env);
+ }
+
+ [Fact(Skip = "Environment set is dynamic. Skip and run manually")]
+ public void AzureInstanceName_CorrectFormat_WhenEnvSetFirst_IConfigurationEnvStagingConfigSet()
+ {
+ Environment.SetEnvironmentVariable("Stackify.Environment", "StagingConfigFirst");
+ SetEnvironmentVariables("my-site", "full-instance-id-12345", "site__1234");
+ ResetAzureConfig();
+
+ var iconfig = new ConfigurationBuilder()
+ .AddInMemoryCollection(new Dictionary
+ {
+ ["Stackify:Environment"] = "StagingConfig"
+ })
+ .Build();
+ Config.SetConfiguration(iconfig);
+ Config.LoadSettings();
+
+ var instanceName = new AzureConfig(new EnvironmentDetail()).AzureInstanceName;
+ Assert.Equal("my-site StagingConfigFirst [1234-full-i]", instanceName);
+ }
+
+ [Fact(Skip = "Environment set is dynamic. Skip and run manually")]
+ public void AzureInstanceName_CorrectFormat_WhenIConfigurationEnvStagingConfigSet()
+ {
+ Environment.SetEnvironmentVariable("Stackify.Environment", "");
+ SetEnvironmentVariables("my-site", "full-instance-id-12345", "site__1234");
+ ResetAzureConfig();
+
+ var iconfig = new ConfigurationBuilder()
+ .AddInMemoryCollection(new Dictionary
+ {
+ ["Stackify:Environment"] = "StagingConfig"
+ })
+ .Build();
+ Config.SetConfiguration(iconfig);
+ Config.LoadSettings();
+
+ var instanceName = new AzureConfig(new EnvironmentDetail()).AzureInstanceName;
+ Assert.Equal("my-site StagingConfig [1234-full-i]", instanceName);
+ }
+#endif
+
+ [Fact]
+ public void AzureRoleType_SetToWebApp_WhenWebsiteDetected()
+ {
+ // Arrange
+ SetEnvironmentVariables("site", "instance");
+ ResetAzureConfig();
+
+ // Act
+ bool _ = AzureConfig.Instance.InAzure; // Force initialization
+ var roleType = GetPrivateField("_azureRoleType");
+
+ // Assert
+ Assert.Equal(AzureRoleType.WebApp, roleType);
+ }
+
+ // Helper to access private fields
+ private T GetPrivateField(string fieldName)
+ {
+ var field = typeof(AzureConfig).GetField(fieldName,
+ System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
+ return (T)field.GetValue(AzureConfig.Instance);
+ }
+ }
+}
diff --git a/test/StackifyLib.UnitTests/Models/EnvironmentDetail_Tests.cs b/test/StackifyLib.UnitTests/Models/EnvironmentDetail_Tests.cs
new file mode 100644
index 0000000..c571ec0
--- /dev/null
+++ b/test/StackifyLib.UnitTests/Models/EnvironmentDetail_Tests.cs
@@ -0,0 +1,43 @@
+// Copyright (c) 2024 BMC Software, Inc.
+// Copyright (c) 2021-2024 Netreo
+// Copyright (c) 2019 Stackify
+using StackifyLib.Models;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Reflection;
+using System.Text;
+using System.Threading.Tasks;
+using Xunit;
+using Moq;
+
+namespace StackifyLib.UnitTests.Models
+{
+ public class EnvironmentDetail_Tests
+ {
+#if NETCORE
+ [Fact]
+ public async Task TestIfConfigEc2IsNull()
+ {
+ var envService = new EnvironmentDetail();
+ Assert.Equal(Environment.MachineName, envService.GetDeviceName());
+
+ var type = envService.GetType();
+ var property = type.GetField("ec2InstanceId", BindingFlags.NonPublic | BindingFlags.Instance);
+ Assert.Equal(property.GetValue(envService) as string, string.Empty);
+ }
+#endif
+#if NETFULL
+ [Fact]
+ public void TestIfConfigEc2IsNull()
+ {
+ var envService = new EnvironmentDetail();
+ Assert.Equal(Environment.MachineName, envService.GetDeviceName());
+
+ var type = envService.GetType();
+ var property = type.GetField("ec2InstanceId", BindingFlags.NonPublic | BindingFlags.Instance);
+ Assert.Equal(property.GetValue(envService) as string, string.Empty);
+ }
+#endif
+ }
+}
diff --git a/test/StackifyLib.UnitTests/Models/ErrorItem_Tests.cs b/test/StackifyLib.UnitTests/Models/ErrorItem_Tests.cs
new file mode 100644
index 0000000..633b046
--- /dev/null
+++ b/test/StackifyLib.UnitTests/Models/ErrorItem_Tests.cs
@@ -0,0 +1,78 @@
+// Copyright (c) 2024 BMC Software, Inc.
+// Copyright (c) 2021-2024 Netreo
+// Copyright (c) 2019 Stackify
+using StackifyLib.Models;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Reflection;
+using System.Text;
+using System.Threading.Tasks;
+using Xunit;
+using Moq;
+
+namespace StackifyLib.UnitTests.Models
+{
+ public class ErrorItem_Tests
+ {
+
+ [Fact]
+ public async Task TestExceptionStringWithInnerException()
+ {
+ var expectedString = string.Join(
+ Environment.NewLine,
+ "System.Exception: Wrapping the exception to see the differences between outer and inner exceptions. ---> System.Exception: Throwing this exception to see all properties like stacktrace filled",
+ " at StackifyLib.UnitTests.Models.ExampleService.ThrowInnerException()",
+ " at StackifyLib.UnitTests.Models.ExampleService.CreateCaughtException()",
+ "--- End of inner exception stack trace ---",
+ " at StackifyLib.UnitTests.Models.ExampleService.ThrowOuterException(System.Exception)",
+ " at StackifyLib.UnitTests.Models.ExampleService.CreateCaughtException()",
+ " at StackifyLib.UnitTests.Models.ErrorItem_Tests+d__0.MoveNext()",
+ ""
+ );
+ try
+ {
+ //ExampleService service = new ExampleService();
+ ExampleService.CreateCaughtException();
+ }
+ catch (Exception ex)
+ {
+ //PrintExceptionInfo(ex);
+ var exceptionString = new ErrorItem(ex).ToString();
+ Assert.Contains(expectedString, exceptionString);
+ }
+ }
+ }
+
+ class ExampleService
+ {
+ public static Exception CreateCaughtException()
+ {
+ try
+ {
+ try
+ {
+ ThrowInnerException();
+ }
+ catch (Exception exception)
+ {
+ ThrowOuterException(exception);
+ }
+ }
+ catch (Exception)
+ {
+ throw;
+ }
+ // NOTE: never reached, but needed for the compiler.
+ return null;
+ }
+ private static void ThrowOuterException(Exception exception)
+ {
+ throw new Exception("Wrapping the exception to see the differences between outer and inner exceptions.", exception);
+ }
+ private static void ThrowInnerException()
+ {
+ throw new Exception("Throwing this exception to see all properties like stacktrace filled");
+ }
+ }
+}
diff --git a/test/StackifyLib.UnitTests/StackifyLib.UnitTests.csproj b/test/StackifyLib.UnitTests/StackifyLib.UnitTests.csproj
index 4408082..d9c07c3 100644
--- a/test/StackifyLib.UnitTests/StackifyLib.UnitTests.csproj
+++ b/test/StackifyLib.UnitTests/StackifyLib.UnitTests.csproj
@@ -1,16 +1,25 @@
- netcoreapp2.1
+ net6.0;net462
+ false
+ true
+
+ NETCORE
+
+
+
+ NETFULL
+
+
-
-
- all
- runtime; build; native; contentfiles; analyzers
-
+
+
+
+
diff --git a/test/StackifyLib.UnitTests/Utils/HttpClient_Loading_Tests.cs b/test/StackifyLib.UnitTests/Utils/HttpClient_Loading_Tests.cs
new file mode 100644
index 0000000..4a3316d
--- /dev/null
+++ b/test/StackifyLib.UnitTests/Utils/HttpClient_Loading_Tests.cs
@@ -0,0 +1,55 @@
+// Copyright (c) 2024-2025 BMC Software, Inc.
+// Copyright (c) 2021-2024 Netreo
+// Copyright (c) 2019 Stackify
+using Moq;
+using StackifyLib.Models;
+using StackifyLib.Utils;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Net;
+using System.Reflection;
+using System.Security.Policy;
+using System.Text;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace StackifyLib.UnitTests.Utils
+{
+ public class HttpClient_Loading_Tests : IDisposable
+ {
+ private readonly HttpClient _httpClient;
+
+ public HttpClient_Loading_Tests()
+ {
+ // Store original values to restore after tests
+ Config.ProxyServer = "http://testuser:testpass@docker-hyperv.local:8881";
+ _httpClient = new HttpClient("test-api-key", "https://test.api.com/");
+ HttpClient.IsUnitTest = true;
+ }
+
+ public void Dispose()
+ {
+ HttpClient.IsUnitTest = false;
+ }
+
+ [Fact(Skip = "This test should be run explicitly (due to static loading nature of HttpClient)")]
+ public void LoadWebProxyConfig_DefaultBehavior()
+ {
+ // Arrange - Mock the Config.Get method to return true for UseDefaultCredentials
+ try
+ {
+ var customProxy = HttpClient.CustomWebProxy as WebProxy;
+ Assert.NotNull(customProxy);
+
+ // Note: We can't easily test UseDefaultCredentials without mocking Config.Get
+ // This test verifies the method runs without error when proxy is configured
+ Assert.Equal("docker-hyperv.local", customProxy.Address.Host);
+ Assert.Equal(8881, customProxy.Address.Port);
+ }
+ finally
+ {
+ }
+ }
+ }
+}
diff --git a/test/StackifyLib.UnitTests/Utils/HttpClient_Tests.cs b/test/StackifyLib.UnitTests/Utils/HttpClient_Tests.cs
new file mode 100644
index 0000000..102160c
--- /dev/null
+++ b/test/StackifyLib.UnitTests/Utils/HttpClient_Tests.cs
@@ -0,0 +1,860 @@
+// Copyright (c) 2024-2025 BMC Software, Inc.
+// Copyright (c) 2021-2024 Netreo
+// Copyright (c) 2019 Stackify
+using Moq;
+using StackifyLib.Models;
+using StackifyLib.Utils;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Net;
+using System.Reflection;
+using System.Security.Policy;
+using System.Text;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace StackifyLib.UnitTests.Utils
+{
+ public class HttpClient_Tests : IDisposable
+ {
+ private readonly HttpClient _httpClient;
+ private IWebProxy _originalProxy;
+ private Action _originalRequestModifier;
+
+ public HttpClient_Tests()
+ {
+ // Store original values to restore after tests
+ _originalProxy = HttpClient.CustomWebProxy;
+ _originalRequestModifier = HttpClient.CustomRequestModifier;
+
+ _httpClient = new HttpClient("test-api-key", "https://test.api.com/");
+ HttpClient.IsUnitTest = true;
+ }
+
+ public void Dispose()
+ {
+ // Restore original values
+ HttpClient.CustomWebProxy = _originalProxy;
+ HttpClient.CustomRequestModifier = _originalRequestModifier;
+ HttpClient.IsUnitTest = false;
+ }
+
+ [Fact]
+ public void Constructor_SetsBaseAPIUrl_WithTrailingSlash()
+ {
+ var client = new HttpClient("test-key", "https://api.test.com");
+
+ Assert.Equal("https://api.test.com/", client.BaseAPIUrl);
+ }
+
+ [Fact]
+ public void Constructor_PreservesTrailingSlash_WhenAlreadyPresent()
+ {
+ var client = new HttpClient("test-key", "https://api.test.com/");
+
+ Assert.Equal("https://api.test.com/", client.BaseAPIUrl);
+ }
+
+ [Fact]
+ public void CustomWebProxy_CanBeSetAndRetrieved()
+ {
+ var testProxy = new WebProxy("http://proxy.test.com:8080");
+
+ HttpClient.CustomWebProxy = testProxy;
+
+ Assert.Equal(testProxy, HttpClient.CustomWebProxy);
+ }
+
+ [Fact]
+ public void CustomRequestModifier_CanBeSetAndRetrieved()
+ {
+ var testModifier = new Action(req => req.Timeout = 30000);
+
+ HttpClient.CustomRequestModifier = testModifier;
+
+ Assert.Equal(testModifier, HttpClient.CustomRequestModifier);
+ }
+
+ [Fact]
+ public void CustomRequestModifier_ModifiesRequest()
+ {
+ bool modifierCalled = false;
+ string capturedUserAgent = null;
+
+ HttpClient.CustomRequestModifier = (request) =>
+ {
+ modifierCalled = true;
+ capturedUserAgent = request.Headers["User-Agent"];
+ request.Timeout = 30000;
+ };
+
+ // Use reflection to test the BuildJsonRequest method
+ var method = typeof(HttpClient).GetMethod("BuildJsonRequest", BindingFlags.NonPublic | BindingFlags.Instance);
+ Assert.NotNull(method);
+
+ var finalRequest = (HttpWebRequest)method.Invoke(_httpClient, new object[] { "https://test.api.com/test", "{}", false });
+
+ Assert.True(modifierCalled);
+ Assert.Equal(30000, finalRequest.Timeout);
+ Assert.NotNull(capturedUserAgent);
+ }
+
+ [Fact]
+ public void CustomRequestModifier_ModifiesPOSTRequest()
+ {
+ bool modifierCalled = false;
+
+ HttpClient.CustomRequestModifier = (request) =>
+ {
+ modifierCalled = true;
+ request.Timeout = 45000;
+ };
+
+ // Use reflection to test the BuildPOSTRequest method
+ var method = typeof(HttpClient).GetMethod("BuildPOSTRequest", BindingFlags.NonPublic | BindingFlags.Instance);
+ Assert.NotNull(method);
+
+ var finalRequest = (HttpWebRequest)method.Invoke(_httpClient, new object[] { "https://test.api.com/test", "data=test" });
+
+ Assert.True(modifierCalled);
+ Assert.Equal(45000, finalRequest.Timeout);
+ }
+
+ [Fact]
+ public void BuildJsonRequest_SetsCustomWebProxy_WhenProvided()
+ {
+ var testProxy = new WebProxy("http://proxy1.test.com:8080");
+ HttpClient.CustomWebProxy = testProxy;
+
+ // Use reflection to access private method
+ var method = typeof(HttpClient).GetMethod("BuildJsonRequest", BindingFlags.NonPublic | BindingFlags.Instance);
+ Assert.NotNull(method);
+
+ var request = (HttpWebRequest)method.Invoke(_httpClient, new object[] { "https://test.api.com/test", "{}", false });
+
+ Assert.Equal(testProxy, request.Proxy);
+ }
+
+ [Fact]
+ public void BuildPOSTRequest_SetsCustomWebProxy_WhenProvided()
+ {
+ var testProxy = new WebProxy("http://proxy.test.com:8080");
+ HttpClient.CustomWebProxy = testProxy;
+
+ // Use reflection to access private method
+ var method = typeof(HttpClient).GetMethod("BuildPOSTRequest", BindingFlags.NonPublic | BindingFlags.Instance);
+ Assert.NotNull(method);
+
+ var request = (HttpWebRequest)method.Invoke(_httpClient, new object[] { "https://test.api.com/test", "data=test" });
+
+ Assert.Equal(testProxy, request.Proxy);
+ }
+
+ [Fact]
+ public void BuildJsonRequest_DoesNotSetProxy_WhenCustomWebProxyIsNull()
+ {
+ HttpClient.CustomWebProxy = null;
+
+ // Use reflection to access private method
+ var method = typeof(HttpClient).GetMethod("BuildJsonRequest", BindingFlags.NonPublic | BindingFlags.Instance);
+ Assert.NotNull(method);
+
+ var request = (HttpWebRequest)method.Invoke(_httpClient, new object[] { "https://test.api.com/test", "{}", false });
+
+ // When CustomWebProxy is null, the request should use default proxy settings
+ Assert.NotEqual(HttpClient.CustomWebProxy, request.Proxy);
+ }
+
+ [Fact]
+ public void BuildJsonRequest_SetsCorrectHeaders()
+ {
+ // Use reflection to access private method
+ var method = typeof(HttpClient).GetMethod("BuildJsonRequest", BindingFlags.NonPublic | BindingFlags.Instance);
+ Assert.NotNull(method);
+
+ var request = (HttpWebRequest)method.Invoke(_httpClient, new object[] { "https://test.api.com/test", "{\"test\": \"data\"}", false });
+
+ Assert.Equal("application/json", request.ContentType);
+ Assert.Equal("test-api-key", request.Headers["X-Stackify-Key"]);
+ Assert.Equal("POST", request.Method);
+#if NETFULL
+ Assert.Contains("StackifyLib-", request.UserAgent);
+#else
+ Assert.Contains("StackifyLib-", request.Headers["User-Agent"]);
+#endif
+ }
+
+ [Fact]
+ public void BuildJsonRequest_WithCompression_SetsCorrectHeaders()
+ {
+ // Use reflection to access private method
+ var method = typeof(HttpClient).GetMethod("BuildJsonRequest", BindingFlags.NonPublic | BindingFlags.Instance);
+ Assert.NotNull(method);
+
+ var request = (HttpWebRequest)method.Invoke(_httpClient, new object[] { "https://test.api.com/test", "{\"test\": \"data\"}", true });
+
+ Assert.Equal("application/json", request.ContentType);
+ Assert.Equal("gzip", request.Headers["Content-Encoding"]);
+ Assert.Equal("POST", request.Method);
+ }
+
+ [Fact]
+ public void BuildJsonRequest_WithEmptyJsonData_UsesGETMethod()
+ {
+ // Use reflection to access private method
+ var method = typeof(HttpClient).GetMethod("BuildJsonRequest", BindingFlags.NonPublic | BindingFlags.Instance);
+ Assert.NotNull(method);
+
+ var request = (HttpWebRequest)method.Invoke(_httpClient, new object[] { "https://test.api.com/test", "", false });
+
+ Assert.Equal("GET", request.Method);
+ }
+
+ [Fact]
+ public void BuildPOSTRequest_SetsCorrectHeaders()
+ {
+ // Use reflection to access private method
+ var method = typeof(HttpClient).GetMethod("BuildPOSTRequest", BindingFlags.NonPublic | BindingFlags.Instance);
+ Assert.NotNull(method);
+
+ var request = (HttpWebRequest)method.Invoke(_httpClient, new object[] { "https://test.api.com/test", "data=test" });
+
+ Assert.Equal("application/x-www-form-urlencoded", request.ContentType);
+ Assert.Equal("test-api-key", request.Headers["X-Stackify-Key"]);
+ Assert.Equal("POST", request.Method);
+#if NETFULL
+ Assert.Contains("StackifyLib-", request.UserAgent);
+#else
+ Assert.Contains("StackifyLib-", request.Headers["User-Agent"]);
+#endif
+ }
+
+ [Fact]
+ public void ProxyAndCustomModifier_WorkTogether()
+ {
+ var testProxy = new WebProxy("http://proxy.test.com:8080");
+ HttpClient.CustomWebProxy = testProxy;
+
+ bool modifierCalled = false;
+ HttpClient.CustomRequestModifier = (request) =>
+ {
+ modifierCalled = true;
+ request.Timeout = 60000;
+ };
+
+ // Use reflection to access private method
+ var method = typeof(HttpClient).GetMethod("BuildJsonRequest", BindingFlags.NonPublic | BindingFlags.Instance);
+ Assert.NotNull(method);
+
+ var finalRequest = (HttpWebRequest)method.Invoke(_httpClient, new object[] { "https://test.api.com/test", "{}", false });
+
+ Assert.Equal(testProxy, finalRequest.Proxy);
+ Assert.True(modifierCalled);
+ Assert.Equal(60000, finalRequest.Timeout);
+ }
+
+ [Fact]
+ public void CustomRequestModifier_DoesNotThrow_WhenNull()
+ {
+ HttpClient.CustomRequestModifier = null;
+
+ // Use reflection to access private method
+ var method = typeof(HttpClient).GetMethod("BuildJsonRequest", BindingFlags.NonPublic | BindingFlags.Instance);
+ Assert.NotNull(method);
+
+ // Should not throw exception when CustomRequestModifier is null
+ var exception = Record.Exception(() =>
+ method.Invoke(_httpClient, new object[] { "https://test.api.com/test", "{}", false })
+ );
+
+ Assert.Null(exception);
+ }
+
+#if NETFULL
+ [Fact]
+ public void LoadWebProxyConfig_SetsCustomWebProxy_FromConfig()
+ {
+ // This test would need to mock the Config.ProxyServer property
+ // For now, we'll test that the method exists and can be called
+ var method = typeof(HttpClient).GetMethod("LoadWebProxyConfig", BindingFlags.Public | BindingFlags.Static);
+ Assert.NotNull(method);
+
+ // Should not throw exception
+ var exception = Record.Exception(() => method.Invoke(null, null));
+ Assert.Null(exception);
+ }
+#endif
+
+ [Theory]
+ [InlineData("http://proxy.example.com:8080")]
+ [InlineData("https://secure-proxy.example.com:443", true)]
+ [InlineData("http://user:pass@proxy.example.com:3128")]
+ public void CustomWebProxy_AcceptsDifferentProxyUrls(string proxyUrl, bool throwsException = false)
+ {
+ var testProxy = new WebProxy(proxyUrl);
+ HttpClient.CustomWebProxy = testProxy;
+
+ // Use reflection to access private method
+ var method = typeof(HttpClient).GetMethod("BuildJsonRequest", BindingFlags.NonPublic | BindingFlags.Instance);
+ Assert.NotNull(method);
+
+ if (throwsException)
+ {
+#if NETFULL
+ Assert.Throws(() => (HttpWebRequest)method.Invoke(_httpClient, new object[] { "https://test.api.com/test", "{}", false }));
+#else
+ var request = (HttpWebRequest)method.Invoke(_httpClient, new object[] { "https://test.api.com/test", "{}", false });
+ Assert.Equal(testProxy, request.Proxy);
+#endif
+ }
+ else
+ {
+ var request = (HttpWebRequest)method.Invoke(_httpClient, new object[] { "https://test.api.com/test", "{}", false });
+ Assert.Equal(testProxy, request.Proxy);
+ }
+ }
+
+ [Fact]
+ public void CustomRequestModifier_CanModifyMultipleProperties()
+ {
+ string customHeaderValue = "CustomValue123";
+
+ HttpClient.CustomRequestModifier = (request) =>
+ {
+ request.Timeout = 120000;
+ request.Headers.Add("X-Custom-Header", customHeaderValue);
+ request.KeepAlive = false;
+ };
+
+ // Use reflection to access private method
+ var method = typeof(HttpClient).GetMethod("BuildJsonRequest", BindingFlags.NonPublic | BindingFlags.Instance);
+ Assert.NotNull(method);
+
+ var finalRequest = (HttpWebRequest)method.Invoke(_httpClient, new object[] { "https://test.api.com/test", "{}", false });
+
+ Assert.Equal(120000, finalRequest.Timeout);
+ Assert.Equal(customHeaderValue, finalRequest.Headers["X-Custom-Header"]);
+ Assert.False(finalRequest.KeepAlive);
+ }
+
+ [Fact]
+ public void ProxyConfiguration_RespectsOrder_ProxyThenCustomModifier()
+ {
+ var testProxy = new WebProxy("http://proxy.test.com:8080");
+ HttpClient.CustomWebProxy = testProxy;
+
+ // Custom modifier that verifies proxy was set first
+ bool proxyWasSetBeforeModifier = false;
+ HttpClient.CustomRequestModifier = (request) =>
+ {
+ proxyWasSetBeforeModifier = (request.Proxy == testProxy);
+ request.Timeout = 25000;
+ };
+
+ // Use reflection to access private method
+ var method = typeof(HttpClient).GetMethod("BuildJsonRequest", BindingFlags.NonPublic | BindingFlags.Instance);
+ Assert.NotNull(method);
+
+ var finalRequest = (HttpWebRequest)method.Invoke(_httpClient, new object[] { "https://test.api.com/test", "{}", false });
+
+ Assert.True(proxyWasSetBeforeModifier, "Proxy should be set before CustomRequestModifier is called");
+ Assert.Equal(testProxy, finalRequest.Proxy);
+ Assert.Equal(25000, finalRequest.Timeout);
+ }
+
+ [Fact]
+ public void CustomRequestModifier_CanOverrideProxySettings()
+ {
+ var initialProxy = new WebProxy("http://initial-proxy.test.com:8080");
+ var overrideProxy = new WebProxy("http://override-proxy.test.com:3128");
+
+ HttpClient.CustomWebProxy = initialProxy;
+
+ HttpClient.CustomRequestModifier = (request) =>
+ {
+ // Override the proxy set by the framework
+ request.Proxy = overrideProxy;
+ };
+
+ // Use reflection to access private method
+ var method = typeof(HttpClient).GetMethod("BuildJsonRequest", BindingFlags.NonPublic | BindingFlags.Instance);
+ Assert.NotNull(method);
+
+ var finalRequest = (HttpWebRequest)method.Invoke(_httpClient, new object[] { "https://test.api.com/test", "{}", false });
+
+ Assert.Equal(overrideProxy, finalRequest.Proxy);
+ Assert.NotEqual(initialProxy, finalRequest.Proxy);
+ }
+
+ [Fact]
+ public void BuildPOSTRequest_WithoutProxy_DoesNotSetProxy()
+ {
+ HttpClient.CustomWebProxy = null;
+
+ // Use reflection to access private method
+
+ HttpWebRequest requestDefault = (HttpWebRequest)WebRequest.Create("http://test.com");
+ var method = typeof(HttpClient).GetMethod("BuildPOSTRequest", BindingFlags.NonPublic | BindingFlags.Instance);
+ Assert.NotNull(method);
+
+ var request = (HttpWebRequest)method.Invoke(_httpClient, new object[] { "https://test.api.com/test", "data=test" });
+ Assert.Equivalent(request.Proxy, requestDefault.Proxy);
+ }
+
+ [Theory]
+ [InlineData("http://proxy.test.com:8080", "")]
+ [InlineData("http://proxy.test.com:8080", "data=test")]
+ [InlineData("https://secure-proxy.test.com:443", "param1=value1¶m2=value2", true)]
+ public void BuildPOSTRequest_WithDifferentProxyAndData_SetsProxyCorrectly(string proxyUrl, string postData, bool throwsException = false)
+ {
+ var testProxy = new WebProxy(proxyUrl);
+ HttpClient.CustomWebProxy = testProxy;
+
+ // Use reflection to access private method
+ var method = typeof(HttpClient).GetMethod("BuildPOSTRequest", BindingFlags.NonPublic | BindingFlags.Instance);
+ Assert.NotNull(method);
+
+
+ if (throwsException)
+ {
+#if NETFULL
+ Assert.Throws(() => (HttpWebRequest)method.Invoke(_httpClient, new object[] { "https://test.api.com/test", postData }));
+#else
+ var request = (HttpWebRequest)method.Invoke(_httpClient, new object[] { "https://test.api.com/test", postData });
+ Assert.Equal(testProxy, request.Proxy);
+ Assert.Equal("POST", request.Method);
+ Assert.Equal("application/x-www-form-urlencoded", request.ContentType);
+#endif
+ }
+ else
+ {
+ var request = (HttpWebRequest)method.Invoke(_httpClient, new object[] { "https://test.api.com/test", postData });
+ Assert.Equal(testProxy, request.Proxy);
+ Assert.Equal("POST", request.Method);
+ Assert.Equal("application/x-www-form-urlencoded", request.ContentType);
+ }
+
+ }
+
+ [Fact]
+ public void APIKey_Property_ReturnsCorrectValue()
+ {
+ var client = new HttpClient("test-api-key", "https://test.api.com/");
+
+ // Access the APIKey property
+ Assert.Equal("test-api-key", client.APIKey);
+ }
+
+ [Fact]
+ public void StackifyWebResponse_IsClientError_ReturnsCorrectValues()
+ {
+ var response = new HttpClient.StackifyWebResponse();
+
+ // Test 4xx status codes (client errors)
+ response.StatusCode = HttpStatusCode.BadRequest;
+ Assert.True(response.IsClientError());
+
+ response.StatusCode = HttpStatusCode.Unauthorized;
+ Assert.True(response.IsClientError());
+
+ response.StatusCode = HttpStatusCode.NotFound;
+ Assert.True(response.IsClientError());
+
+ // Test non-4xx status codes
+ response.StatusCode = HttpStatusCode.OK;
+ Assert.False(response.IsClientError());
+
+ response.StatusCode = HttpStatusCode.InternalServerError;
+ Assert.False(response.IsClientError());
+
+ response.StatusCode = HttpStatusCode.BadGateway;
+ Assert.False(response.IsClientError());
+ }
+
+ [Fact]
+ public void LoadWebProxyConfig_SetsUseDefaultCredentials_WhenConfigIsTrue()
+ {
+ // Arrange - Mock the Config.Get method to return true for UseDefaultCredentials
+ var originalProxyServer = Config.ProxyServer;
+ var configType = typeof(Config);
+ var proxyServerProperty = configType.GetProperty("ProxyServer", BindingFlags.Public | BindingFlags.Static);
+
+ try
+ {
+ // Set up a proxy server without credentials in URL
+ proxyServerProperty.SetValue(null, "http://proxy.test.com:8080");
+
+ // We'll test this by calling LoadWebProxyConfig and checking the result
+ HttpClient.LoadWebProxyConfig();
+
+ var customProxy = HttpClient.CustomWebProxy as WebProxy;
+ Assert.NotNull(customProxy);
+
+ // Note: We can't easily test UseDefaultCredentials without mocking Config.Get
+ // This test verifies the method runs without error when proxy is configured
+ Assert.Equal("proxy.test.com", customProxy.Address.Host);
+ Assert.Equal(8080, customProxy.Address.Port);
+ }
+ finally
+ {
+ // Restore original state
+ proxyServerProperty.SetValue(null, originalProxyServer);
+ HttpClient.CustomWebProxy = null;
+ }
+ }
+
+ [Fact]
+ public void LoadWebProxyConfig_SetsCredentials_WhenProxyHasUserInfo()
+ {
+ // Arrange
+ var originalProxyServer = Config.ProxyServer;
+ var configType = typeof(Config);
+ var proxyServerProperty = configType.GetProperty("ProxyServer", BindingFlags.Public | BindingFlags.Static);
+
+ try
+ {
+ // Set up a proxy server with credentials in URL
+ proxyServerProperty.SetValue(null, "http://testuser:testpass@proxy.test.com:8080");
+
+ // Act
+ HttpClient.LoadWebProxyConfig();
+
+ // Assert
+ var customProxy = HttpClient.CustomWebProxy as WebProxy;
+ Assert.NotNull(customProxy);
+ Assert.Equal("proxy.test.com", customProxy.Address.Host);
+ Assert.Equal(8080, customProxy.Address.Port);
+
+ // Verify credentials were set
+ Assert.NotNull(customProxy.Credentials);
+ var networkCredential = customProxy.Credentials as NetworkCredential;
+ Assert.NotNull(networkCredential);
+ Assert.Equal("testuser", networkCredential.UserName);
+ Assert.Equal("testpass", networkCredential.Password);
+ }
+ finally
+ {
+ // Restore original state
+ proxyServerProperty.SetValue(null, originalProxyServer);
+ HttpClient.CustomWebProxy = null;
+ }
+ }
+
+ [Fact]
+ public void LoadWebProxyConfig_DoesNotSetProxy_WhenProxyServerIsEmpty()
+ {
+ // Arrange
+ var originalProxyServer = Config.ProxyServer;
+ var configType = typeof(Config);
+ var proxyServerProperty = configType.GetProperty("ProxyServer", BindingFlags.Public | BindingFlags.Static);
+
+ try
+ {
+ // Set empty proxy server
+ proxyServerProperty.SetValue(null, string.Empty);
+
+ // Act
+ HttpClient.LoadWebProxyConfig();
+
+ // Assert - CustomWebProxy should remain null
+ Assert.Null(HttpClient.CustomWebProxy);
+ }
+ finally
+ {
+ // Restore original state
+ proxyServerProperty.SetValue(null, originalProxyServer);
+ HttpClient.CustomWebProxy = null;
+ }
+ }
+
+ [Fact]
+ public void LoadWebProxyConfig_HandlesException_WhenInvalidProxyUrl()
+ {
+ // Arrange
+ var originalProxyServer = Config.ProxyServer;
+ var configType = typeof(Config);
+ var proxyServerProperty = configType.GetProperty("ProxyServer", BindingFlags.Public | BindingFlags.Static);
+
+ try
+ {
+ // Set invalid proxy server URL
+ proxyServerProperty.SetValue(null, "invalid-url-format");
+
+ // Act - should not throw exception
+ var exception = Record.Exception(() => HttpClient.LoadWebProxyConfig());
+
+ // Assert - should handle exception gracefully
+ Assert.Null(exception);
+ // CustomWebProxy might be null or unchanged depending on the exception handling
+ }
+ finally
+ {
+ // Restore original state
+ proxyServerProperty.SetValue(null, originalProxyServer);
+ HttpClient.CustomWebProxy = null;
+ }
+ }
+
+ [Theory]
+ [InlineData("http://proxy.test.com:8080")]
+ [InlineData("https://secure-proxy.test.com:443")]
+ [InlineData("http://proxy.company.com:3128")]
+ public void LoadWebProxyConfig_SetsCorrectProxyAddress_ForValidUrls(string proxyUrl)
+ {
+ // Arrange
+ var originalProxyServer = Config.ProxyServer;
+ var configType = typeof(Config);
+ var proxyServerProperty = configType.GetProperty("ProxyServer", BindingFlags.Public | BindingFlags.Static);
+
+ try
+ {
+ // Set proxy server
+ proxyServerProperty.SetValue(null, proxyUrl);
+
+ // Act
+ HttpClient.LoadWebProxyConfig();
+
+ // Assert
+ var customProxy = HttpClient.CustomWebProxy as WebProxy;
+ Assert.NotNull(customProxy);
+
+ var expectedUri = new Uri(proxyUrl);
+ Assert.Equal(expectedUri.Host, customProxy.Address.Host);
+ Assert.Equal(expectedUri.Port, customProxy.Address.Port);
+ Assert.Equal(expectedUri.Scheme, customProxy.Address.Scheme);
+ }
+ finally
+ {
+ // Restore original state
+ proxyServerProperty.SetValue(null, originalProxyServer);
+ HttpClient.CustomWebProxy = null;
+ }
+ }
+ [Fact]
+ public void LoadWebProxyConfig_WithProxyUseDefaultCredentials_True()
+ {
+ // Arrange - Save original values
+ var originalProxyServer = Config.ProxyServer;
+ var originalProxyUseDefaultCredentials = Config.ProxyUseDefaultCredentials;
+
+ try
+ {
+ // Set up Config values
+ var configType = typeof(Config);
+ var proxyServerProperty = configType.GetProperty("ProxyServer", BindingFlags.Public | BindingFlags.Static);
+ var proxyUseDefaultCredentialsProperty = configType.GetProperty("ProxyUseDefaultCredentials", BindingFlags.Public | BindingFlags.Static);
+
+ proxyServerProperty.SetValue(null, "http://corporate-proxy.test.com:8080");
+ proxyUseDefaultCredentialsProperty.SetValue(null, true);
+
+ // Act
+ HttpClient.LoadWebProxyConfig();
+
+ // Assert
+ var customProxy = HttpClient.CustomWebProxy as WebProxy;
+ Assert.NotNull(customProxy);
+ Assert.Equal("corporate-proxy.test.com", customProxy.Address.Host);
+ Assert.Equal(8080, customProxy.Address.Port);
+ Assert.True(customProxy.UseDefaultCredentials);
+ Assert.NotNull(customProxy.Credentials); // No explicit credentials when using default
+ Assert.Empty((customProxy.Credentials as NetworkCredential).UserName);
+ }
+ finally
+ {
+ // Restore original values
+ var configType = typeof(Config);
+ var proxyServerProperty = configType.GetProperty("ProxyServer", BindingFlags.Public | BindingFlags.Static);
+ var proxyUseDefaultCredentialsProperty = configType.GetProperty("ProxyUseDefaultCredentials", BindingFlags.Public | BindingFlags.Static);
+
+ proxyServerProperty.SetValue(null, originalProxyServer);
+ proxyUseDefaultCredentialsProperty.SetValue(null, originalProxyUseDefaultCredentials);
+ HttpClient.CustomWebProxy = null;
+ }
+ }
+
+ [Fact]
+ public void LoadWebProxyConfig_WithProxyUseDefaultCredentials_False()
+ {
+ // Arrange - Save original values
+ var originalProxyServer = Config.ProxyServer;
+ var originalProxyUseDefaultCredentials = Config.ProxyUseDefaultCredentials;
+
+ try
+ {
+ // Set up Config values
+ var configType = typeof(Config);
+ var proxyServerProperty = configType.GetProperty("ProxyServer", BindingFlags.Public | BindingFlags.Static);
+ var proxyUseDefaultCredentialsProperty = configType.GetProperty("ProxyUseDefaultCredentials", BindingFlags.Public | BindingFlags.Static);
+
+ proxyServerProperty.SetValue(null, "http://corporate-proxy.test.com:8080");
+ proxyUseDefaultCredentialsProperty.SetValue(null, false);
+
+ // Act
+ HttpClient.LoadWebProxyConfig();
+
+ // Assert
+ var customProxy = HttpClient.CustomWebProxy as WebProxy;
+ Assert.NotNull(customProxy);
+ Assert.Equal("corporate-proxy.test.com", customProxy.Address.Host);
+ Assert.Equal(8080, customProxy.Address.Port);
+ Assert.False(customProxy.UseDefaultCredentials);
+ Assert.Null(customProxy.Credentials);
+ }
+ finally
+ {
+ // Restore original values
+ var configType = typeof(Config);
+ var proxyServerProperty = configType.GetProperty("ProxyServer", BindingFlags.Public | BindingFlags.Static);
+ var proxyUseDefaultCredentialsProperty = configType.GetProperty("ProxyUseDefaultCredentials", BindingFlags.Public | BindingFlags.Static);
+
+ proxyServerProperty.SetValue(null, originalProxyServer);
+ proxyUseDefaultCredentialsProperty.SetValue(null, originalProxyUseDefaultCredentials);
+ HttpClient.CustomWebProxy = null;
+ }
+ }
+
+ [Fact]
+ public void LoadWebProxyConfig_WithProxyUseDefaultCredentials_Null()
+ {
+ // Arrange - Save original values
+ var originalProxyServer = Config.ProxyServer;
+ var originalProxyUseDefaultCredentials = Config.ProxyUseDefaultCredentials;
+
+ try
+ {
+ // Set up Config values
+ var configType = typeof(Config);
+ var proxyServerProperty = configType.GetProperty("ProxyServer", BindingFlags.Public | BindingFlags.Static);
+ var proxyUseDefaultCredentialsProperty = configType.GetProperty("ProxyUseDefaultCredentials", BindingFlags.Public | BindingFlags.Static);
+
+ proxyServerProperty.SetValue(null, "http://corporate-proxy.test.com:8080");
+ proxyUseDefaultCredentialsProperty.SetValue(null, null); // Explicitly set to null
+
+ // Act
+ HttpClient.LoadWebProxyConfig();
+
+ // Assert
+ var customProxy = HttpClient.CustomWebProxy as WebProxy;
+ Assert.NotNull(customProxy);
+ Assert.Equal("corporate-proxy.test.com", customProxy.Address.Host);
+ Assert.Equal(8080, customProxy.Address.Port);
+ Assert.False(customProxy.UseDefaultCredentials); // Should remain false when null
+ Assert.Null(customProxy.Credentials);
+ }
+ finally
+ {
+ // Restore original values
+ var configType = typeof(Config);
+ var proxyServerProperty = configType.GetProperty("ProxyServer", BindingFlags.Public | BindingFlags.Static);
+ var proxyUseDefaultCredentialsProperty = configType.GetProperty("ProxyUseDefaultCredentials", BindingFlags.Public | BindingFlags.Static);
+
+ proxyServerProperty.SetValue(null, originalProxyServer);
+ proxyUseDefaultCredentialsProperty.SetValue(null, originalProxyUseDefaultCredentials);
+ HttpClient.CustomWebProxy = null;
+ }
+ }
+
+ [Fact]
+ public void LoadWebProxyConfig_WithEmbeddedCredentials_IgnoresProxyUseDefaultCredentials()
+ {
+ // Arrange - Save original values
+ var originalProxyServer = Config.ProxyServer;
+ var originalProxyUseDefaultCredentials = Config.ProxyUseDefaultCredentials;
+
+ try
+ {
+ // Set up Config values - proxy with embedded credentials
+ var configType = typeof(Config);
+ var proxyServerProperty = configType.GetProperty("ProxyServer", BindingFlags.Public | BindingFlags.Static);
+ var proxyUseDefaultCredentialsProperty = configType.GetProperty("ProxyUseDefaultCredentials", BindingFlags.Public | BindingFlags.Static);
+
+ proxyServerProperty.SetValue(null, "http://testuser:testpass@corporate-proxy.test.com:8080");
+ proxyUseDefaultCredentialsProperty.SetValue(null, true); // This should be ignored
+
+ // Act
+ HttpClient.LoadWebProxyConfig();
+
+ // Assert - Embedded credentials take precedence over UseDefaultCredentials
+ var customProxy = HttpClient.CustomWebProxy as WebProxy;
+ Assert.NotNull(customProxy);
+ Assert.Equal("corporate-proxy.test.com", customProxy.Address.Host);
+ Assert.Equal(8080, customProxy.Address.Port);
+ Assert.False(customProxy.UseDefaultCredentials); // Should be false when explicit credentials are used
+
+ // Verify explicit credentials were set
+ Assert.NotNull(customProxy.Credentials);
+ var networkCredential = customProxy.Credentials as NetworkCredential;
+ Assert.NotNull(networkCredential);
+ Assert.Equal("testuser", networkCredential.UserName);
+ Assert.Equal("testpass", networkCredential.Password);
+ }
+ finally
+ {
+ // Restore original values
+ var configType = typeof(Config);
+ var proxyServerProperty = configType.GetProperty("ProxyServer", BindingFlags.Public | BindingFlags.Static);
+ var proxyUseDefaultCredentialsProperty = configType.GetProperty("ProxyUseDefaultCredentials", BindingFlags.Public | BindingFlags.Static);
+
+ proxyServerProperty.SetValue(null, originalProxyServer);
+ proxyUseDefaultCredentialsProperty.SetValue(null, originalProxyUseDefaultCredentials);
+ HttpClient.CustomWebProxy = null;
+ }
+ }
+
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ [InlineData(null)]
+ public void LoadWebProxyConfig_ProxyUseDefaultCredentials_VariousValues(bool? configValue)
+ {
+ // Arrange - Save original values
+ var originalProxyServer = Config.ProxyServer;
+ var originalProxyUseDefaultCredentials = Config.ProxyUseDefaultCredentials;
+
+ try
+ {
+ // Set up Config values
+ var configType = typeof(Config);
+ var proxyServerProperty = configType.GetProperty("ProxyServer", BindingFlags.Public | BindingFlags.Static);
+ var proxyUseDefaultCredentialsProperty = configType.GetProperty("ProxyUseDefaultCredentials", BindingFlags.Public | BindingFlags.Static);
+
+ proxyServerProperty.SetValue(null, "http://test-proxy.company.com:3128");
+ proxyUseDefaultCredentialsProperty.SetValue(null, configValue);
+
+ // Act
+ HttpClient.LoadWebProxyConfig();
+
+ // Assert
+ var customProxy = HttpClient.CustomWebProxy as WebProxy;
+ Assert.NotNull(customProxy);
+ Assert.Equal("test-proxy.company.com", customProxy.Address.Host);
+ Assert.Equal(3128, customProxy.Address.Port);
+
+ // UseDefaultCredentials should only be true when configValue is true
+ bool expectedUseDefault = configValue.HasValue && configValue.Value;
+ Assert.Equal(expectedUseDefault, customProxy.UseDefaultCredentials);
+ if (configValue.HasValue && configValue.Value)
+ {
+ Assert.NotNull(customProxy.Credentials);
+ Assert.Empty((customProxy.Credentials as NetworkCredential).UserName);
+ }
+ else
+ {
+ Assert.Null(customProxy.Credentials);
+ }
+ }
+ finally
+ {
+ // Restore original values
+ var configType = typeof(Config);
+ var proxyServerProperty = configType.GetProperty("ProxyServer", BindingFlags.Public | BindingFlags.Static);
+ var proxyUseDefaultCredentialsProperty = configType.GetProperty("ProxyUseDefaultCredentials", BindingFlags.Public | BindingFlags.Static);
+
+ proxyServerProperty.SetValue(null, originalProxyServer);
+ proxyUseDefaultCredentialsProperty.SetValue(null, originalProxyUseDefaultCredentials);
+ HttpClient.CustomWebProxy = null;
+ }
+ }
+ }
+}