diff --git a/CHANGELOG.md b/CHANGELOG.md index ee2230a..62ca689 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [v0.7.0](https://github.com/contentstack/contentstack-management-dotnet/tree/v0.7.0) + - Feat + - **Bulk publish/unpublish: query parameters (DX-3233)** + - `skip_workflow_stage_check` and `approvals` are now sent as query parameters instead of headers for bulk publish and bulk unpublish + - Unit tests updated to assert on `QueryResources` for these flags (BulkPublishServiceTest, BulkUnpublishServiceTest, BulkOperationServicesTest) + - Integration tests: bulk publish with skipWorkflowStage and approvals (Test003a), bulk unpublish with skipWorkflowStage and approvals (Test004a), and helper `EnsureBulkTestContentTypeAndEntriesAsync()` so bulk tests can run in any order + ## [v0.6.1](https://github.com/contentstack/contentstack-management-dotnet/tree/v0.6.1) (2026-02-02) - Fix - Release DELETE request no longer includes Content-Type header to comply with API requirements diff --git a/Contentstack.Management.Core.Tests/Contentstack.Management.Core.Tests.csproj b/Contentstack.Management.Core.Tests/Contentstack.Management.Core.Tests.csproj index f8be953..4ee1a12 100644 --- a/Contentstack.Management.Core.Tests/Contentstack.Management.Core.Tests.csproj +++ b/Contentstack.Management.Core.Tests/Contentstack.Management.Core.Tests.csproj @@ -4,7 +4,7 @@ net7.0 false - $(Version) + 0.1.3 true ../CSManagementSDK.snk @@ -24,6 +24,7 @@ + @@ -35,11 +36,6 @@ - - - PreserveNewest - - diff --git a/Contentstack.Management.Core.Tests/IntegrationTest/Contentstack015_BulkOperationTest.cs b/Contentstack.Management.Core.Tests/IntegrationTest/Contentstack015_BulkOperationTest.cs index 9cbc4f0..6304cac 100644 --- a/Contentstack.Management.Core.Tests/IntegrationTest/Contentstack015_BulkOperationTest.cs +++ b/Contentstack.Management.Core.Tests/IntegrationTest/Contentstack015_BulkOperationTest.cs @@ -1,7 +1,9 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Net; using System.Threading.Tasks; +using Contentstack.Management.Core.Exceptions; using Contentstack.Management.Core.Models; using Contentstack.Management.Core.Models.Fields; using Contentstack.Management.Core.Tests.Model; @@ -21,6 +23,17 @@ public class Contentstack015_BulkOperationTest private string _testReleaseUid = "bulk_test_release"; private List _createdEntries = new List(); + /// + /// Fails the test with a clear message from ContentstackErrorException or generic exception. + /// + private static void FailWithError(string operation, Exception ex) + { + if (ex is ContentstackErrorException cex) + Assert.Fail($"{operation} failed. HTTP {(int)cex.StatusCode} ({cex.StatusCode}). ErrorCode: {cex.ErrorCode}. Message: {cex.ErrorMessage ?? cex.Message}"); + else + Assert.Fail($"{operation} failed: {ex.Message}"); + } + [TestInitialize] public async Task Initialize() { @@ -196,6 +209,197 @@ public async Task Test004_Should_Perform_Bulk_Unpublish_Operation() } } + [TestMethod] + [DoNotParallelize] + public async Task Test003a_Should_Perform_Bulk_Publish_With_SkipWorkflowStage_And_Approvals() + { + try + { + await EnsureBulkTestContentTypeAndEntriesAsync(); + + List availableEntries = await FetchExistingEntries(); + Assert.IsTrue(availableEntries.Count > 0, "No entries available for bulk operation"); + + List availableEnvironments = await GetAvailableEnvironments(); + + var publishDetails = new BulkPublishDetails + { + Entries = availableEntries.Select(e => new BulkPublishEntry + { + Uid = e.Uid, + ContentType = _contentTypeUid, + Version = e.Version, + Locale = "en-us" + }).ToList(), + Locales = new List { "en-us" }, + Environments = availableEnvironments + }; + + ContentstackResponse response = _stack.BulkOperation().Publish(publishDetails, skipWorkflowStage: true, approvals: true); + + Assert.IsNotNull(response); + Assert.IsTrue(response.IsSuccessStatusCode, $"Bulk publish failed with status {(int)response.StatusCode} ({response.StatusCode})."); + Assert.AreEqual(HttpStatusCode.OK, response.StatusCode, $"Expected 200 OK, got {(int)response.StatusCode}."); + + var responseJson = response.OpenJObjectResponse(); + Assert.IsNotNull(responseJson); + } + catch (Exception ex) + { + FailWithError("Bulk publish with skipWorkflowStage and approvals", ex); + } + } + + [TestMethod] + [DoNotParallelize] + public async Task Test004a_Should_Perform_Bulk_Unpublish_With_SkipWorkflowStage_And_Approvals() + { + try + { + await EnsureBulkTestContentTypeAndEntriesAsync(); + + List availableEntries = await FetchExistingEntries(); + Assert.IsTrue(availableEntries.Count > 0, "No entries available for bulk operation"); + + List availableEnvironments = await GetAvailableEnvironments(); + + var unpublishDetails = new BulkPublishDetails + { + Entries = availableEntries.Select(e => new BulkPublishEntry + { + Uid = e.Uid, + ContentType = _contentTypeUid, + Version = e.Version, + Locale = "en-us" + }).ToList(), + Locales = new List { "en-us" }, + Environments = availableEnvironments + }; + + ContentstackResponse response = _stack.BulkOperation().Unpublish(unpublishDetails, skipWorkflowStage: true, approvals: true); + + Assert.IsNotNull(response); + Assert.IsTrue(response.IsSuccessStatusCode, $"Bulk unpublish failed with status {(int)response.StatusCode} ({response.StatusCode})."); + Assert.AreEqual(HttpStatusCode.OK, response.StatusCode, $"Expected 200 OK, got {(int)response.StatusCode}."); + + var responseJson = response.OpenJObjectResponse(); + Assert.IsNotNull(responseJson); + } + catch (Exception ex) + { + FailWithError("Bulk unpublish with skipWorkflowStage and approvals", ex); + } + } + + [TestMethod] + [DoNotParallelize] + public async Task Test003b_Should_Perform_Bulk_Publish_With_ApiVersion_3_2() + { + try + { + await EnsureBulkTestContentTypeAndEntriesAsync(); + + List availableEntries = await FetchExistingEntries(); + Assert.IsTrue(availableEntries.Count > 0, "No entries available for bulk operation"); + + List availableEnvironments = await GetAvailableEnvironments(); + + var publishDetails = new BulkPublishDetails + { + Entries = availableEntries.Select(e => new BulkPublishEntry + { + Uid = e.Uid, + ContentType = _contentTypeUid, + Version = e.Version, + Locale = "en-us" + }).ToList(), + Locales = new List { "en-us" }, + Environments = availableEnvironments + }; + + ContentstackResponse response = _stack.BulkOperation().Publish(publishDetails, skipWorkflowStage: true, approvals: true, apiVersion: "3.2"); + + Assert.IsNotNull(response); + Assert.IsTrue(response.IsSuccessStatusCode, $"Bulk publish with api_version 3.2 failed with status {(int)response.StatusCode} ({response.StatusCode})."); + Assert.AreEqual(HttpStatusCode.OK, response.StatusCode, $"Expected 200 OK, got {(int)response.StatusCode}."); + + var responseJson = response.OpenJObjectResponse(); + Assert.IsNotNull(responseJson); + } + catch (Exception ex) + { + FailWithError("Bulk publish with api_version 3.2", ex); + } + } + + [TestMethod] + [DoNotParallelize] + public async Task Test004b_Should_Perform_Bulk_Unpublish_With_ApiVersion_3_2() + { + try + { + await EnsureBulkTestContentTypeAndEntriesAsync(); + + List availableEntries = await FetchExistingEntries(); + Assert.IsTrue(availableEntries.Count > 0, "No entries available for bulk operation"); + + List availableEnvironments = await GetAvailableEnvironments(); + + var unpublishDetails = new BulkPublishDetails + { + Entries = availableEntries.Select(e => new BulkPublishEntry + { + Uid = e.Uid, + ContentType = _contentTypeUid, + Version = e.Version, + Locale = "en-us" + }).ToList(), + Locales = new List { "en-us" }, + Environments = availableEnvironments + }; + + ContentstackResponse response = _stack.BulkOperation().Unpublish(unpublishDetails, skipWorkflowStage: true, approvals: true, apiVersion: "3.2"); + + Assert.IsNotNull(response); + Assert.IsTrue(response.IsSuccessStatusCode, $"Bulk unpublish with api_version 3.2 failed with status {(int)response.StatusCode} ({response.StatusCode})."); + Assert.AreEqual(HttpStatusCode.OK, response.StatusCode, $"Expected 200 OK, got {(int)response.StatusCode}."); + + var responseJson = response.OpenJObjectResponse(); + Assert.IsNotNull(responseJson); + } + catch (Exception ex) + { + FailWithError("Bulk unpublish with api_version 3.2", ex); + } + } + + [TestMethod] + [DoNotParallelize] + public void Test004c_Should_Return_Error_When_Bulk_Unpublish_With_Invalid_Data() + { + var invalidDetails = new BulkPublishDetails + { + Entries = new List(), + Locales = new List { "en-us" }, + Environments = new List { "non_existent_environment_uid" } + }; + + try + { + _stack.BulkOperation().Unpublish(invalidDetails); + Assert.Fail("Expected ContentstackErrorException was not thrown."); + } + catch (ContentstackErrorException ex) + { + Assert.IsFalse(ex.StatusCode >= HttpStatusCode.OK && (int)ex.StatusCode < 300, "Expected non-success status code."); + Assert.IsNotNull(ex.ErrorMessage ?? ex.Message, "Error message should be present."); + } + catch (Exception ex) + { + FailWithError("Bulk unpublish with invalid data (negative test)", ex); + } + } + [TestMethod] [DoNotParallelize] public async Task Test005_Should_Perform_Bulk_Release_Operations() @@ -570,6 +774,72 @@ private async Task> GetAvailableEnvironments() } } + /// + /// Ensures bulk_test_content_type exists and has at least one entry so bulk tests can run in any order. + /// + private async Task EnsureBulkTestContentTypeAndEntriesAsync() + { + try + { + bool contentTypeExists = false; + try + { + ContentstackResponse ctResponse = _stack.ContentType(_contentTypeUid).Fetch(); + contentTypeExists = ctResponse.IsSuccessStatusCode; + } + catch + { + // Content type not found + } + + if (!contentTypeExists) + { + await CreateTestEnvironment(); + await CreateTestRelease(); + var contentModelling = new ContentModelling + { + Title = "bulk_test_content_type", + Uid = _contentTypeUid, + Schema = new List + { + new TextboxField + { + DisplayName = "Title", + Uid = "title", + DataType = "text", + Mandatory = true, + Unique = false, + Multiple = false + } + } + }; + _stack.ContentType().Create(contentModelling); + } + + // Ensure at least one entry exists + List existing = await FetchExistingEntries(); + if (existing == null || existing.Count == 0) + { + var entry = new SimpleEntry { Title = "Bulk test entry" }; + ContentstackResponse createResponse = _stack.ContentType(_contentTypeUid).Entry().Create(entry); + var responseJson = createResponse.OpenJObjectResponse(); + if (createResponse.IsSuccessStatusCode && responseJson["entry"] != null && responseJson["entry"]["uid"] != null) + { + _createdEntries.Add(new EntryInfo + { + Uid = responseJson["entry"]["uid"].ToString(), + Title = responseJson["entry"]["title"]?.ToString() ?? "Bulk test entry", + Version = responseJson["entry"]["_version"] != null ? (int)responseJson["entry"]["_version"] : 1 + }); + } + } + } + catch (Exception) + { + // Caller will handle if entries are still missing + } + } + private async Task> FetchExistingEntries() { try diff --git a/Contentstack.Management.Core.Unit.Tests/Core/Services/Stack/BulkPublishServiceTest.cs b/Contentstack.Management.Core.Unit.Tests/Core/Services/Stack/BulkPublishServiceTest.cs index 19d2073..73284a2 100644 --- a/Contentstack.Management.Core.Unit.Tests/Core/Services/Stack/BulkPublishServiceTest.cs +++ b/Contentstack.Management.Core.Unit.Tests/Core/Services/Stack/BulkPublishServiceTest.cs @@ -48,25 +48,25 @@ public void Should_Create_Service_With_Valid_Parameters() } [TestMethod] - public void Should_Set_Skip_Workflow_Stage_Header_When_True() + public void Should_Set_Skip_Workflow_Stage_Query_Parameter_When_True() { var details = new BulkPublishDetails(); var service = new BulkPublishService(serializer, new Management.Core.Models.Stack(null), details, skipWorkflowStage: true); Assert.IsNotNull(service); - Assert.IsTrue(service.Headers.ContainsKey("skip_workflow_stage_check")); - Assert.AreEqual("true", service.Headers["skip_workflow_stage_check"]); + Assert.IsTrue(service.QueryResources.ContainsKey("skip_workflow_stage_check")); + Assert.AreEqual("true", service.QueryResources["skip_workflow_stage_check"]); } [TestMethod] - public void Should_Set_Approvals_Header_When_True() + public void Should_Set_Approvals_Query_Parameter_When_True() { var details = new BulkPublishDetails(); var service = new BulkPublishService(serializer, new Management.Core.Models.Stack(null), details, approvals: true); Assert.IsNotNull(service); - Assert.IsTrue(service.Headers.ContainsKey("approvals")); - Assert.AreEqual("true", service.Headers["approvals"]); + Assert.IsTrue(service.QueryResources.ContainsKey("approvals")); + Assert.AreEqual("true", service.QueryResources["approvals"]); } [TestMethod] diff --git a/Contentstack.Management.Core.Unit.Tests/Core/Services/Stack/BulkUnpublishServiceTest.cs b/Contentstack.Management.Core.Unit.Tests/Core/Services/Stack/BulkUnpublishServiceTest.cs index d6e0a65..ff9b709 100644 --- a/Contentstack.Management.Core.Unit.Tests/Core/Services/Stack/BulkUnpublishServiceTest.cs +++ b/Contentstack.Management.Core.Unit.Tests/Core/Services/Stack/BulkUnpublishServiceTest.cs @@ -48,25 +48,25 @@ public void Should_Create_Service_With_Valid_Parameters() } [TestMethod] - public void Should_Set_Skip_Workflow_Stage_Header_When_True() + public void Should_Set_Skip_Workflow_Stage_Query_Parameter_When_True() { var details = new BulkPublishDetails(); var service = new BulkUnpublishService(serializer, new Management.Core.Models.Stack(null), details, skipWorkflowStage: true); Assert.IsNotNull(service); - Assert.IsTrue(service.Headers.ContainsKey("skip_workflow_stage_check")); - Assert.AreEqual("true", service.Headers["skip_workflow_stage_check"]); + Assert.IsTrue(service.QueryResources.ContainsKey("skip_workflow_stage_check")); + Assert.AreEqual("true", service.QueryResources["skip_workflow_stage_check"]); } [TestMethod] - public void Should_Set_Approvals_Header_When_True() + public void Should_Set_Approvals_Query_Parameter_When_True() { var details = new BulkPublishDetails(); var service = new BulkUnpublishService(serializer, new Management.Core.Models.Stack(null), details, approvals: true); Assert.IsNotNull(service); - Assert.IsTrue(service.Headers.ContainsKey("approvals")); - Assert.AreEqual("true", service.Headers["approvals"]); + Assert.IsTrue(service.QueryResources.ContainsKey("approvals")); + Assert.AreEqual("true", service.QueryResources["approvals"]); } [TestMethod] diff --git a/Contentstack.Management.Core.Unit.Tests/Services/BulkOperationServicesTest.cs b/Contentstack.Management.Core.Unit.Tests/Services/BulkOperationServicesTest.cs index 01c6b51..f2ccf92 100644 --- a/Contentstack.Management.Core.Unit.Tests/Services/BulkOperationServicesTest.cs +++ b/Contentstack.Management.Core.Unit.Tests/Services/BulkOperationServicesTest.cs @@ -142,10 +142,10 @@ public void Test004_BulkPublishService_Initialization() Assert.IsNotNull(service); Assert.AreEqual("/bulk/publish", service.ResourcePath); Assert.AreEqual("POST", service.HttpMethod); - Assert.IsTrue(service.Headers.ContainsKey("skip_workflow_stage_check")); - Assert.IsTrue(service.Headers.ContainsKey("approvals")); - Assert.AreEqual("true", service.Headers["skip_workflow_stage_check"]); - Assert.AreEqual("true", service.Headers["approvals"]); + Assert.IsTrue(service.QueryResources.ContainsKey("skip_workflow_stage_check")); + Assert.IsTrue(service.QueryResources.ContainsKey("approvals")); + Assert.AreEqual("true", service.QueryResources["skip_workflow_stage_check"]); + Assert.AreEqual("true", service.QueryResources["approvals"]); } [TestMethod] @@ -197,10 +197,10 @@ public void Test006_BulkPublishService_With_All_Flags() var service = new BulkPublishService(_serializer, _stack, publishDetails, true, true, true); Assert.IsNotNull(service); - Assert.IsTrue(service.Headers.ContainsKey("skip_workflow_stage_check")); - Assert.IsTrue(service.Headers.ContainsKey("approvals")); - Assert.AreEqual("true", service.Headers["skip_workflow_stage_check"]); - Assert.AreEqual("true", service.Headers["approvals"]); + Assert.IsTrue(service.QueryResources.ContainsKey("skip_workflow_stage_check")); + Assert.IsTrue(service.QueryResources.ContainsKey("approvals")); + Assert.AreEqual("true", service.QueryResources["skip_workflow_stage_check"]); + Assert.AreEqual("true", service.QueryResources["approvals"]); } [TestMethod] @@ -218,8 +218,8 @@ public void Test007_BulkPublishService_Without_Flags() var service = new BulkPublishService(_serializer, _stack, publishDetails, false, false, false); Assert.IsNotNull(service); - Assert.IsFalse(service.Headers.ContainsKey("skip_workflow_stage_check")); - Assert.IsFalse(service.Headers.ContainsKey("approvals")); + Assert.IsFalse(service.QueryResources.ContainsKey("skip_workflow_stage_check")); + Assert.IsFalse(service.QueryResources.ContainsKey("approvals")); } [TestMethod] @@ -248,8 +248,8 @@ public void Test008_BulkUnpublishService_Initialization() Assert.IsNotNull(service); Assert.AreEqual("/bulk/unpublish", service.ResourcePath); Assert.AreEqual("POST", service.HttpMethod); - Assert.IsTrue(service.Headers.ContainsKey("skip_workflow_stage_check")); - Assert.IsTrue(service.Headers.ContainsKey("approvals")); + Assert.IsTrue(service.QueryResources.ContainsKey("skip_workflow_stage_check")); + Assert.IsTrue(service.QueryResources.ContainsKey("approvals")); } [TestMethod] diff --git a/Contentstack.Management.Core/Services/Stack/BulkOperation/BulkPublishService.cs b/Contentstack.Management.Core/Services/Stack/BulkOperation/BulkPublishService.cs index 1aa6c87..5bf5a19 100644 --- a/Contentstack.Management.Core/Services/Stack/BulkOperation/BulkPublishService.cs +++ b/Contentstack.Management.Core/Services/Stack/BulkOperation/BulkPublishService.cs @@ -35,15 +35,15 @@ public BulkPublishService(JsonSerializer serializer, Contentstack.Management.Cor ResourcePath = "/bulk/publish"; HttpMethod = "POST"; - // Set headers based on parameters + // Set query parameters based on options if (_skipWorkflowStage) { - Headers["skip_workflow_stage_check"] = "true"; + AddQueryResource("skip_workflow_stage_check", "true"); } if (_approvals) { - Headers["approvals"] = "true"; + AddQueryResource("approvals", "true"); } if (_isNested) diff --git a/Contentstack.Management.Core/Services/Stack/BulkOperation/BulkUnpublishService.cs b/Contentstack.Management.Core/Services/Stack/BulkOperation/BulkUnpublishService.cs index 8d9689d..0993409 100644 --- a/Contentstack.Management.Core/Services/Stack/BulkOperation/BulkUnpublishService.cs +++ b/Contentstack.Management.Core/Services/Stack/BulkOperation/BulkUnpublishService.cs @@ -38,12 +38,12 @@ public BulkUnpublishService(JsonSerializer serializer, Contentstack.Management.C // Set headers based on parameters if (_skipWorkflowStage) { - Headers["skip_workflow_stage_check"] = "true"; + AddQueryResource("skip_workflow_stage_check", "true"); } if (_approvals) { - Headers["approvals"] = "true"; + AddQueryResource("approvals", "true"); } if (_isNested) diff --git a/Directory.Build.props b/Directory.Build.props index 735f780..d79e191 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,5 +1,5 @@ - 0.6.1 + 0.7.0