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