diff --git a/Contentstack.AspNetCore/Contentstack.AspNetCore.csproj b/Contentstack.AspNetCore/Contentstack.AspNetCore.csproj
index 4d960fc..b71bafa 100644
--- a/Contentstack.AspNetCore/Contentstack.AspNetCore.csproj
+++ b/Contentstack.AspNetCore/Contentstack.AspNetCore.csproj
@@ -8,7 +8,7 @@
Contentstack
$(Version)
Main release
- Copyright (c) 2012-2024 Contentstack (http://app.contentstack.com). All Rights Reserved
+ Copyright (c) 2012-2025 Contentstack (http://app.contentstack.com). All Rights Reserved
https://github.com/contentstack/contentstack-dotnet
v$(Version)
$(Version)
diff --git a/Contentstack.Core.Tests/VersionUtilityTest.cs b/Contentstack.Core.Tests/VersionUtilityTest.cs
new file mode 100644
index 0000000..8811807
--- /dev/null
+++ b/Contentstack.Core.Tests/VersionUtilityTest.cs
@@ -0,0 +1,379 @@
+using System;
+using System.Reflection;
+using Contentstack.Core.Internals;
+using Xunit;
+
+namespace Contentstack.Core.Tests
+{
+ public class VersionUtilityTest
+ {
+ #region GetSdkVersion Tests
+
+ [Fact]
+ public void GetSdkVersion_ReturnsValidFormat()
+ {
+ // Act
+ var version = VersionUtility.GetSdkVersion();
+
+ // Assert
+ Assert.NotNull(version);
+ Assert.StartsWith("contentstack-delivery-dotnet/", version);
+ Assert.True(version.Length > "contentstack-delivery-dotnet/".Length);
+ }
+
+ [Fact]
+ public void GetSdkVersion_ReturnsConsistentResult()
+ {
+ // Act
+ var version1 = VersionUtility.GetSdkVersion();
+ var version2 = VersionUtility.GetSdkVersion();
+
+ // Assert
+ Assert.Equal(version1, version2);
+ }
+
+ [Fact]
+ public void GetSdkVersion_DoesNotReturnNull()
+ {
+ // Act
+ var version = VersionUtility.GetSdkVersion();
+
+ // Assert
+ Assert.NotNull(version);
+ Assert.NotEmpty(version);
+ }
+
+ [Fact]
+ public void GetSdkVersion_DoesNotReturnEmptyString()
+ {
+ // Act
+ var version = VersionUtility.GetSdkVersion();
+
+ // Assert
+ Assert.NotEmpty(version);
+ }
+
+ [Fact]
+ public void GetSdkVersion_ContainsExpectedPrefix()
+ {
+ // Act
+ var version = VersionUtility.GetSdkVersion();
+
+ // Assert
+ Assert.StartsWith("contentstack-delivery-dotnet/", version);
+ }
+
+ [Fact]
+ public void GetSdkVersion_DoesNotContainSpaces()
+ {
+ // Act
+ var version = VersionUtility.GetSdkVersion();
+
+ // Assert
+ Assert.DoesNotContain(" ", version);
+ }
+
+ [Fact]
+ public void GetSdkVersion_DoesNotContainNewlines()
+ {
+ // Act
+ var version = VersionUtility.GetSdkVersion();
+
+ // Assert
+ Assert.DoesNotContain("\n", version);
+ Assert.DoesNotContain("\r", version);
+ }
+
+ #endregion
+
+ #region ExtractSemanticVersion Tests (via Reflection)
+
+ [Theory]
+ [InlineData("1.2.3", "1.2.3")]
+ [InlineData("1.2.3-beta.1", "1.2.3-beta")]
+ [InlineData("1.2.3+abc123", "1.2.3")]
+ [InlineData("1.2.3-beta.1+abc123", "1.2.3-beta")]
+ [InlineData("2.25.0", "2.25.0")]
+ [InlineData("2.25.0-beta.1", "2.25.0-beta")]
+ [InlineData("2.25.0+abc123", "2.25.0")]
+ [InlineData("2.25.0-beta.1+abc123", "2.25.0-beta")]
+ [InlineData("10.20.30", "10.20.30")]
+ [InlineData("0.1.0", "0.1.0")]
+ public void ExtractSemanticVersion_ValidInputs_ReturnsCorrectVersion(string input, string expected)
+ {
+ // Arrange
+ var method = typeof(VersionUtility).GetMethod("ExtractSemanticVersion", BindingFlags.NonPublic | BindingFlags.Static);
+
+ // Act
+ var result = method.Invoke(null, new object[] { input }) as string;
+
+ // Assert
+ Assert.Equal(expected, result);
+ }
+
+ [Theory]
+ [InlineData("1.2")]
+ [InlineData("1")]
+ [InlineData("")]
+ [InlineData("invalid")]
+ [InlineData(" ")]
+ [InlineData("\t")]
+ [InlineData("\n")]
+ [InlineData("\r\n")]
+ public void ExtractSemanticVersion_InvalidInputs_ReturnsNull(string input)
+ {
+ // Arrange
+ var method = typeof(VersionUtility).GetMethod("ExtractSemanticVersion", BindingFlags.NonPublic | BindingFlags.Static);
+
+ // Act
+ var result = method.Invoke(null, new object[] { input }) as string;
+
+ // Assert
+ Assert.Null(result);
+ }
+
+ [Fact]
+ public void ExtractSemanticVersion_NullInput_ReturnsNull()
+ {
+ // Arrange
+ var method = typeof(VersionUtility).GetMethod("ExtractSemanticVersion", BindingFlags.NonPublic | BindingFlags.Static);
+
+ // Act
+ var result = method.Invoke(null, new object[] { null }) as string;
+
+ // Assert
+ Assert.Null(result);
+ }
+
+ [Theory]
+ [InlineData("1.2.3+", "1.2.3")] // Should handle trailing + correctly
+ [InlineData(" 1.2.3 ", "1.2.3")] // Should handle whitespace
+ [InlineData("1.2.3.4.5", "1.2.3")] // Should handle extra version parts
+ public void ExtractSemanticVersion_ImprovedHandling_WorksCorrectly(string input, string expected)
+ {
+ // Arrange
+ var method = typeof(VersionUtility).GetMethod("ExtractSemanticVersion", BindingFlags.NonPublic | BindingFlags.Static);
+
+ // Act
+ var result = method.Invoke(null, new object[] { input }) as string;
+
+ // Assert
+ Assert.Equal(expected, result);
+ }
+
+ [Theory]
+ [InlineData("1.2.3.4", "1.2.3")] // Should take only first 3 parts
+ [InlineData("1.2.3.4.5.6", "1.2.3")] // Should take only first 3 parts
+ [InlineData("1.2.3.4.5", "1.2.3")] // Should take only first 3 parts
+ public void ExtractSemanticVersion_MoreThanThreeParts_TakesFirstThree(string input, string expected)
+ {
+ // Arrange
+ var method = typeof(VersionUtility).GetMethod("ExtractSemanticVersion", BindingFlags.NonPublic | BindingFlags.Static);
+
+ // Act
+ var result = method.Invoke(null, new object[] { input }) as string;
+
+ // Assert
+ Assert.Equal(expected, result);
+ }
+
+ [Theory]
+ [InlineData("1.2.3+metadata", "1.2.3")]
+ [InlineData("1.2.3-beta.1+metadata", "1.2.3-beta")]
+ [InlineData("1.2.3+very-long-metadata-string", "1.2.3")]
+ [InlineData("1.2.3+", "1.2.3")]
+ public void ExtractSemanticVersion_WithBuildMetadata_RemovesMetadata(string input, string expected)
+ {
+ // Arrange
+ var method = typeof(VersionUtility).GetMethod("ExtractSemanticVersion", BindingFlags.NonPublic | BindingFlags.Static);
+
+ // Act
+ var result = method.Invoke(null, new object[] { input }) as string;
+
+ // Assert
+ Assert.Equal(expected, result);
+ }
+
+ [Theory]
+ [InlineData("1.2.3-beta.1", "1.2.3-beta")]
+ [InlineData("1.2.3-alpha.1", "1.2.3-alpha")]
+ [InlineData("1.2.3-rc.1", "1.2.3-rc")]
+ [InlineData("1.2.3-preview.1", "1.2.3-preview")]
+ public void ExtractSemanticVersion_WithPreReleaseIdentifiers_KeepsPreRelease(string input, string expected)
+ {
+ // Arrange
+ var method = typeof(VersionUtility).GetMethod("ExtractSemanticVersion", BindingFlags.NonPublic | BindingFlags.Static);
+
+ // Act
+ var result = method.Invoke(null, new object[] { input }) as string;
+
+ // Assert
+ Assert.Equal(expected, result);
+ }
+
+ #endregion
+
+ #region Edge Cases and Error Scenarios
+
+ [Fact]
+ public void GetSdkVersion_HandlesExceptions_Gracefully()
+ {
+ // This test ensures that GetSdkVersion doesn't throw exceptions
+ // and returns a fallback value when assembly reflection fails
+
+ // Act & Assert - should not throw
+ var version = VersionUtility.GetSdkVersion();
+ Assert.NotNull(version);
+ Assert.NotEmpty(version);
+ }
+
+ [Fact]
+ public void GetSdkVersion_ReturnsFallbackWhenAssemblyVersionIsInvalid()
+ {
+ // This test verifies that when assembly version is 0.0.0.0 or invalid,
+ // the method falls back to other version sources or returns "dev"
+
+ // Act
+ var version = VersionUtility.GetSdkVersion();
+
+ // Assert
+ Assert.NotNull(version);
+ Assert.True(version == "contentstack-delivery-dotnet/dev" ||
+ version.StartsWith("contentstack-delivery-dotnet/"));
+ }
+
+ [Theory]
+ [InlineData("")]
+ [InlineData(" ")]
+ [InlineData(" ")]
+ [InlineData("\t")]
+ [InlineData("\n")]
+ [InlineData("\r\n")]
+ public void ExtractSemanticVersion_WhitespaceInputs_ReturnsNull(string input)
+ {
+ // Arrange
+ var method = typeof(VersionUtility).GetMethod("ExtractSemanticVersion", BindingFlags.NonPublic | BindingFlags.Static);
+
+ // Act
+ var result = method.Invoke(null, new object[] { input }) as string;
+
+ // Assert
+ Assert.Null(result);
+ }
+
+ [Theory]
+ [InlineData("1.2.3.4.5.6.7.8.9.10")] // Very long version
+ [InlineData("999999999.999999999.999999999")] // Very large numbers
+ [InlineData("0.0.0")] // All zeros
+ public void ExtractSemanticVersion_EdgeCaseInputs_HandlesCorrectly(string input)
+ {
+ // Arrange
+ var method = typeof(VersionUtility).GetMethod("ExtractSemanticVersion", BindingFlags.NonPublic | BindingFlags.Static);
+
+ // Act
+ var result = method.Invoke(null, new object[] { input }) as string;
+
+ // Assert
+ if (input == "0.0.0")
+ {
+ Assert.Equal("0.0.0", result);
+ }
+ else
+ {
+ Assert.NotNull(result);
+ Assert.True(result.Split('.').Length == 3);
+ }
+ }
+
+ [Fact]
+ public void ExtractSemanticVersion_HandlesExceptions_Gracefully()
+ {
+ // This test ensures that ExtractSemanticVersion doesn't throw exceptions
+ // and returns null when parsing fails
+
+ // Arrange
+ var method = typeof(VersionUtility).GetMethod("ExtractSemanticVersion", BindingFlags.NonPublic | BindingFlags.Static);
+
+ // Act & Assert - should not throw
+ var result = method.Invoke(null, new object[] { "invalid-version-string" }) as string;
+ Assert.Null(result);
+ }
+
+ #endregion
+
+ #region Integration Tests
+
+ [Fact]
+ public void GetSdkVersion_Integration_ReturnsValidUserAgentFormat()
+ {
+ // Act
+ var version = VersionUtility.GetSdkVersion();
+
+ // Assert
+ Assert.NotNull(version);
+ Assert.StartsWith("contentstack-delivery-dotnet/", version);
+
+ // Verify it's in a format suitable for User-Agent headers
+ Assert.DoesNotContain(" ", version);
+ Assert.DoesNotContain("\n", version);
+ Assert.DoesNotContain("\r", version);
+ Assert.DoesNotContain("\t", version);
+ }
+
+ [Fact]
+ public void GetSdkVersion_Integration_CanBeUsedInHttpHeaders()
+ {
+ // Act
+ var version = VersionUtility.GetSdkVersion();
+
+ // Assert
+ // Verify the version string is suitable for HTTP headers
+ Assert.NotNull(version);
+ Assert.NotEmpty(version);
+
+ // Should not contain characters that would break HTTP headers
+ Assert.DoesNotContain("\"", version);
+ Assert.DoesNotContain("'", version);
+ Assert.DoesNotContain("\n", version);
+ Assert.DoesNotContain("\r", version);
+ }
+
+ #endregion
+
+ #region Performance Tests
+
+ [Fact]
+ public void GetSdkVersion_Performance_ReturnsQuickly()
+ {
+ // Act & Assert
+ var startTime = DateTime.UtcNow;
+ var version = VersionUtility.GetSdkVersion();
+ var endTime = DateTime.UtcNow;
+
+ // Should complete quickly (less than 1 second)
+ var duration = endTime - startTime;
+ Assert.True(duration.TotalSeconds < 1, $"GetSdkVersion took {duration.TotalSeconds} seconds");
+ Assert.NotNull(version);
+ }
+
+ [Fact]
+ public void GetSdkVersion_Performance_MultipleCalls_Consistent()
+ {
+ // Act
+ var versions = new string[100];
+ for (int i = 0; i < 100; i++)
+ {
+ versions[i] = VersionUtility.GetSdkVersion();
+ }
+
+ // Assert
+ var firstVersion = versions[0];
+ for (int i = 1; i < versions.Length; i++)
+ {
+ Assert.Equal(firstVersion, versions[i]);
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/Contentstack.Core/Contentstack.Core.csproj b/Contentstack.Core/Contentstack.Core.csproj
index 63bf128..2ac53aa 100644
--- a/Contentstack.Core/Contentstack.Core.csproj
+++ b/Contentstack.Core/Contentstack.Core.csproj
@@ -8,7 +8,7 @@
$(Version)
Contentstack
Reference in entry Live preview support added
- Copyright © 2012-2024 Contentstack. All Rights Reserved
+ Copyright © 2012-2025 Contentstack. All Rights Reserved
true
v$(Version)
https://github.com/contentstack/contentstack-dotnet
@@ -54,4 +54,9 @@
+
+
+ <_Parameter1>Contentstack.Core.Tests
+
+
diff --git a/Contentstack.Core/Internals/VersionUtility.cs b/Contentstack.Core/Internals/VersionUtility.cs
index c8c4003..16d8172 100644
--- a/Contentstack.Core/Internals/VersionUtility.cs
+++ b/Contentstack.Core/Internals/VersionUtility.cs
@@ -18,15 +18,20 @@ public static string GetSdkVersion()
{
var assembly = Assembly.GetExecutingAssembly();
var version = assembly.GetName().Version;
-
+
// Check if version is valid (not 0.0.0.0 which is default for unversioned assemblies)
- if (version != null && (version.Major > 0 || version.Minor > 0 || version.Build > 0))
+ if (
+ version != null
+ && (version.Major > 0 || version.Minor > 0 || version.Build > 0)
+ )
{
return $"contentstack-delivery-dotnet/{version.Major}.{version.Minor}.{version.Build}";
}
-
+
// Try to get version from assembly file version as fallback
- var fileVersion = assembly.GetCustomAttribute()?.Version;
+ var fileVersion = assembly
+ .GetCustomAttribute()
+ ?.Version;
if (!string.IsNullOrEmpty(fileVersion))
{
// Parse file version and extract only Major.Minor.Build (first 3 parts)
@@ -37,9 +42,11 @@ public static string GetSdkVersion()
}
return $"contentstack-delivery-dotnet/{fileVersion}";
}
-
+
// Try to get version from assembly informational version
- var infoVersion = assembly.GetCustomAttribute()?.InformationalVersion;
+ var infoVersion = assembly
+ .GetCustomAttribute()
+ ?.InformationalVersion;
if (!string.IsNullOrEmpty(infoVersion))
{
// Extract semantic version (Major.Minor.Patch) from informational version
@@ -56,7 +63,7 @@ public static string GetSdkVersion()
{
// Ignore exceptions and continue to fallback
}
-
+
// Final fallback - use a generic identifier that doesn't imply a specific version
return "contentstack-delivery-dotnet/dev";
}
@@ -70,19 +77,44 @@ private static string ExtractSemanticVersion(string informationalVersion)
{
try
{
+ // Handle null or empty input
+ if (string.IsNullOrWhiteSpace(informationalVersion))
+ {
+ return null;
+ }
+
// Remove build metadata (everything after +)
var versionWithoutMetadata = informationalVersion.Split('+')[0];
-
+
+ // Handle case where version ends with + (e.g., "1.2.3+")
+ if (string.IsNullOrWhiteSpace(versionWithoutMetadata))
+ {
+ return null;
+ }
+
// Split by dots to get version parts
var parts = versionWithoutMetadata.Split('.');
-
+
// Ensure we have at least 3 parts (Major.Minor.Patch)
if (parts.Length >= 3)
{
- // Take only the first 3 parts (Major.Minor.Patch)
- return $"{parts[0]}.{parts[1]}.{parts[2]}";
+ // Validate that all parts are numeric or contain valid version identifiers
+ var major = parts[0].Trim();
+ var minor = parts[1].Trim();
+ var patch = parts[2].Trim();
+
+ // Check if we have valid version components
+ if (
+ !string.IsNullOrEmpty(major)
+ && !string.IsNullOrEmpty(minor)
+ && !string.IsNullOrEmpty(patch)
+ )
+ {
+ // Take only the first 3 parts (Major.Minor.Patch)
+ return $"{major}.{minor}.{patch}";
+ }
}
-
+
return null;
}
catch
diff --git a/Contentstack.Core/LICENSE.txt b/Contentstack.Core/LICENSE.txt
index ec1403b..e1238b5 100644
--- a/Contentstack.Core/LICENSE.txt
+++ b/Contentstack.Core/LICENSE.txt
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2012-2024 Contentstack (http://app.contentstack.com). All Rights Reserved
+Copyright (c) 2012-2025 Contentstack (http://app.contentstack.com). All Rights Reserved
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal