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