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..74cce31 100644
--- a/Contentstack.Management.Core.Tests/Contentstack.Management.Core.Tests.csproj
+++ b/Contentstack.Management.Core.Tests/Contentstack.Management.Core.Tests.csproj
@@ -24,6 +24,7 @@
+
@@ -35,14 +36,17 @@
+
-
+
PreserveNewest
+
+
diff --git a/Contentstack.Management.Core.Tests/IntegrationTest/Contentstack015_BulkOperationTest.cs b/Contentstack.Management.Core.Tests/IntegrationTest/Contentstack015_BulkOperationTest.cs
index 9cbc4f0..95b56ab 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;
@@ -12,6 +14,11 @@
namespace Contentstack.Management.Core.Tests.IntegrationTest
{
+ ///
+ /// Bulk operation integration tests. ClassInitialize ensures environment (find or create "bulk_test_env"), then finds or creates workflow "workflow_test" (2 stages: New stage 1, New stage 2) and publish rule (Stage 2) once.
+ /// Tests are independent. Four workflow-based tests assign entries to Stage 1/Stage 2 then run bulk unpublish/publish with/without version and params.
+ /// No cleanup so you can verify workflow, publish rules, and entry allotment in the UI.
+ ///
[TestClass]
public class Contentstack015_BulkOperationTest
{
@@ -21,25 +28,275 @@ public class Contentstack015_BulkOperationTest
private string _testReleaseUid = "bulk_test_release";
private List _createdEntries = new List();
+ // Workflow and publishing rule for bulk tests (static so one create/delete across all test instances)
+ private static string _bulkTestWorkflowUid;
+ private static string _bulkTestWorkflowStageUid; // Stage 2 (Complete) – used by publish rule and backward compat
+ private static string _bulkTestWorkflowStage1Uid; // Stage 1 (Review)
+ private static string _bulkTestWorkflowStage2Uid; // Stage 2 (Complete) – selected in publishing rule
+ private static string _bulkTestPublishRuleUid;
+ private static string _bulkTestEnvironmentUid; // Environment used for workflow/publish rule (ensured in ClassInitialize or Test000b/000c)
+ private static string _bulkTestWorkflowSetupError; // Reason workflow setup failed (so workflow_tests can show it)
+
+ ///
+ /// 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}");
+ }
+
+ ///
+ /// Asserts that the workflow and both stages were created in ClassInitialize. Call at the start of workflow-based tests so they fail clearly when setup failed.
+ ///
+ private static void AssertWorkflowCreated()
+ {
+ string reason = string.IsNullOrEmpty(_bulkTestWorkflowSetupError) ? "Check auth and stack permissions for workflow create." : _bulkTestWorkflowSetupError;
+ Assert.IsFalse(string.IsNullOrEmpty(_bulkTestWorkflowUid), "Workflow was not created in ClassInitialize. " + reason);
+ Assert.IsFalse(string.IsNullOrEmpty(_bulkTestWorkflowStage1Uid), "Workflow Stage 1 (New stage 1) was not set. " + reason);
+ Assert.IsFalse(string.IsNullOrEmpty(_bulkTestWorkflowStage2Uid), "Workflow Stage 2 (New stage 2) was not set. " + reason);
+ }
+
+ ///
+ /// Returns a Stack instance for the test run (used by ClassInitialize/ClassCleanup).
+ ///
+ private static Stack GetStack()
+ {
+ StackResponse response = StackResponse.getStack(Contentstack.Client.serializer);
+ return Contentstack.Client.Stack(response.Stack.APIKey);
+ }
+
+ [ClassInitialize]
+ public static void ClassInitialize(TestContext context)
+ {
+ try
+ {
+ Stack stack = GetStack();
+ EnsureBulkTestWorkflowAndPublishingRuleAsync(stack).GetAwaiter().GetResult();
+ }
+ catch (Exception)
+ {
+ // Workflow/publish rule setup failed (e.g. auth, plan limits); tests can still run without them
+ }
+ }
+
+ [ClassCleanup]
+ public static void ClassCleanup()
+ {
+ // Intentionally no cleanup: workflow, publish rules, and entries are left so you can verify them in the UI.
+ }
+
[TestInitialize]
public async Task Initialize()
{
StackResponse response = StackResponse.getStack(Contentstack.Client.serializer);
_stack = Contentstack.Client.Stack(response.Stack.APIKey);
-
- // Create a test environment for bulk operations
- //await CreateTestEnvironment();
- //await CreateTestRelease();
}
+ [TestMethod]
+ [DoNotParallelize]
+ public async Task Test000a_Should_Create_Workflow_With_Two_Stages()
+ {
+ try
+ {
+ const string workflowName = "workflow_test";
+
+ // Check if a workflow with the same name already exists (e.g. from a previous test run)
+ try
+ {
+ ContentstackResponse listResponse = _stack.Workflow().FindAll();
+ if (listResponse.IsSuccessStatusCode)
+ {
+ var listJson = listResponse.OpenJObjectResponse();
+ var existing = (listJson["workflows"] as JArray) ?? (listJson["workflow"] as JArray);
+ if (existing != null)
+ {
+ foreach (var wf in existing)
+ {
+ if (wf["name"]?.ToString() == workflowName && wf["uid"] != null)
+ {
+ _bulkTestWorkflowUid = wf["uid"].ToString();
+ var existingStages = wf["workflow_stages"] as JArray;
+ if (existingStages != null && existingStages.Count >= 2)
+ {
+ _bulkTestWorkflowStage1Uid = existingStages[0]["uid"]?.ToString();
+ _bulkTestWorkflowStage2Uid = existingStages[1]["uid"]?.ToString();
+ _bulkTestWorkflowStageUid = _bulkTestWorkflowStage2Uid;
+ Assert.IsNotNull(_bulkTestWorkflowStage1Uid, "Stage 1 UID null in existing workflow.");
+ Assert.IsNotNull(_bulkTestWorkflowStage2Uid, "Stage 2 UID null in existing workflow.");
+ return; // Already exists with stages – nothing more to do
+ }
+ }
+ }
+ }
+ }
+ }
+ catch { /* If listing fails, proceed to create */ }
+
+ var sysAcl = new Dictionary
+ {
+ ["roles"] = new Dictionary { ["uids"] = new List() },
+ ["users"] = new Dictionary { ["uids"] = new List { "$all" } },
+ ["others"] = new Dictionary()
+ };
+
+ var workflowModel = new WorkflowModel
+ {
+ Name = workflowName,
+ Enabled = true,
+ Branches = new List { "main" },
+ ContentTypes = new List { "$all" },
+ AdminUsers = new Dictionary { ["users"] = new List() },
+ WorkflowStages = new List
+ {
+ new WorkflowStage
+ {
+ Name = "New stage 1",
+ Color = "#fe5cfb",
+ SystemACL = sysAcl,
+ NextAvailableStages = new List { "$all" },
+ AllStages = true,
+ AllUsers = true,
+ SpecificStages = false,
+ SpecificUsers = false,
+ EntryLock = "$none"
+ },
+ new WorkflowStage
+ {
+ Name = "New stage 2",
+ Color = "#3688bf",
+ SystemACL = new Dictionary
+ {
+ ["roles"] = new Dictionary { ["uids"] = new List() },
+ ["users"] = new Dictionary { ["uids"] = new List { "$all" } },
+ ["others"] = new Dictionary()
+ },
+ NextAvailableStages = new List { "$all" },
+ AllStages = true,
+ AllUsers = true,
+ SpecificStages = false,
+ SpecificUsers = false,
+ EntryLock = "$none"
+ }
+ }
+ };
+
+ // Print what we are sending so failures show the exact request JSON
+ string sentJson = JsonConvert.SerializeObject(new { workflow = workflowModel }, Formatting.Indented);
+
+ ContentstackResponse response = _stack.Workflow().Create(workflowModel);
+ string responseBody = null;
+ try { responseBody = response.OpenResponse(); } catch { }
+
+ Assert.IsNotNull(response);
+ Assert.IsTrue(response.IsSuccessStatusCode,
+ $"Workflow create failed: HTTP {(int)response.StatusCode}.\n--- REQUEST BODY ---\n{sentJson}\n--- RESPONSE BODY ---\n{responseBody}");
+
+ var responseJson = JObject.Parse(responseBody ?? "{}");
+ var workflowObj = responseJson["workflow"];
+ Assert.IsNotNull(workflowObj, "Response missing 'workflow' key.");
+ Assert.IsFalse(string.IsNullOrEmpty(workflowObj["uid"]?.ToString()), "Workflow UID is empty.");
+
+ _bulkTestWorkflowUid = workflowObj["uid"].ToString();
+ var stages = workflowObj["workflow_stages"] as JArray;
+ Assert.IsNotNull(stages, "workflow_stages missing from response.");
+ Assert.IsTrue(stages.Count >= 2, $"Expected at least 2 stages, got {stages.Count}.");
+ _bulkTestWorkflowStage1Uid = stages[0]["uid"].ToString(); // New stage 1
+ _bulkTestWorkflowStage2Uid = stages[1]["uid"].ToString(); // New stage 2
+ _bulkTestWorkflowStageUid = _bulkTestWorkflowStage2Uid;
+ }
+ catch (Exception ex)
+ {
+ FailWithError("Create workflow with two stages", ex);
+ }
+ }
+
+ [TestMethod]
+ [DoNotParallelize]
+ public async Task Test000b_Should_Create_Publishing_Rule_For_Workflow_Stage2()
+ {
+ try
+ {
+ Assert.IsFalse(string.IsNullOrEmpty(_bulkTestWorkflowUid), "Workflow UID not set. Run Test000a first.");
+ Assert.IsFalse(string.IsNullOrEmpty(_bulkTestWorkflowStage2Uid), "Workflow Stage 2 UID not set. Run Test000a first.");
+
+ if (string.IsNullOrEmpty(_bulkTestEnvironmentUid))
+ await EnsureBulkTestEnvironmentAsync(_stack);
+ Assert.IsFalse(string.IsNullOrEmpty(_bulkTestEnvironmentUid), "No environment. Run Test000c or ensure ClassInitialize ran (ensure environment failed).");
+
+ // Find existing publish rule for this workflow + stage + environment (e.g. from a previous run)
+ try
+ {
+ ContentstackResponse listResponse = _stack.Workflow().PublishRule().FindAll();
+ if (listResponse.IsSuccessStatusCode)
+ {
+ var listJson = listResponse.OpenJObjectResponse();
+ var rules = (listJson["publishing_rules"] as JArray) ?? (listJson["publishing_rule"] as JArray);
+ if (rules != null)
+ {
+ foreach (var rule in rules)
+ {
+ if (rule["workflow"]?.ToString() == _bulkTestWorkflowUid
+ && rule["workflow_stage"]?.ToString() == _bulkTestWorkflowStage2Uid
+ && rule["environment"]?.ToString() == _bulkTestEnvironmentUid
+ && rule["uid"] != null)
+ {
+ _bulkTestPublishRuleUid = rule["uid"].ToString();
+ return; // Already exists
+ }
+ }
+ }
+ }
+ }
+ catch { /* If listing fails, proceed to create */ }
+
+ var publishRuleModel = new PublishRuleModel
+ {
+ WorkflowUid = _bulkTestWorkflowUid,
+ WorkflowStageUid = _bulkTestWorkflowStage2Uid,
+ Environment = _bulkTestEnvironmentUid,
+ Branches = new List { "main" },
+ ContentTypes = new List { "$all" },
+ Locales = new List { "en-us" },
+ Actions = new List(),
+ Approvers = new Approvals { Users = new List(), Roles = new List() },
+ DisableApproval = false
+ };
+
+ string sentJson = JsonConvert.SerializeObject(new { publishing_rule = publishRuleModel }, Formatting.Indented);
+
+ ContentstackResponse response = _stack.Workflow().PublishRule().Create(publishRuleModel);
+ string responseBody = null;
+ try { responseBody = response.OpenResponse(); } catch { }
+
+ Assert.IsNotNull(response);
+ Assert.IsTrue(response.IsSuccessStatusCode,
+ $"Publish rule create failed: HTTP {(int)response.StatusCode}.\n--- REQUEST BODY ---\n{sentJson}\n--- RESPONSE BODY ---\n{responseBody}");
+
+ var responseJson = JObject.Parse(responseBody ?? "{}");
+ var ruleObj = responseJson["publishing_rule"];
+ Assert.IsNotNull(ruleObj, "Response missing 'publishing_rule' key.");
+ Assert.IsFalse(string.IsNullOrEmpty(ruleObj["uid"]?.ToString()), "Publishing rule UID is empty.");
+
+ _bulkTestPublishRuleUid = ruleObj["uid"].ToString();
+ }
+ catch (Exception ex)
+ {
+ FailWithError("Create publishing rule for workflow stage 2", ex);
+ }
+ }
+
+
[TestMethod]
[DoNotParallelize]
public async Task Test001_Should_Create_Content_Type_With_Title_Field()
{
try
{
- await CreateTestEnvironment();
- await CreateTestRelease();
+ try { await CreateTestEnvironment(); } catch (ContentstackErrorException) { /* optional */ }
+ try { await CreateTestRelease(); } catch (ContentstackErrorException) { /* optional */ }
// Create a content type with only a title field
var contentModelling = new ContentModelling
{
@@ -68,9 +325,9 @@ public async Task Test001_Should_Create_Content_Type_With_Title_Field()
Assert.IsNotNull(responseJson["content_type"]);
Assert.AreEqual(_contentTypeUid, responseJson["content_type"]["uid"].ToString());
}
- catch (Exception e)
+ catch (Exception ex)
{
- throw;
+ FailWithError("Create content type with title field", ex);
}
}
@@ -80,16 +337,44 @@ public async Task Test002_Should_Create_Five_Entries()
{
try
{
- // Create 5 entries with different titles
- var entryTitles = new[] { "First Entry", "Second Entry", "Third Entry", "Fourth Entry", "Fifth Entry" };
+ AssertWorkflowCreated();
- foreach (var title in entryTitles)
+ // Ensure content type exists (fetch or create)
+ bool contentTypeExists = false;
+ try
{
- var entry = new SimpleEntry
+ ContentstackResponse ctResponse = _stack.ContentType(_contentTypeUid).Fetch();
+ contentTypeExists = ctResponse.IsSuccessStatusCode;
+ }
+ catch { /* not found */ }
+ if (!contentTypeExists)
+ {
+ var contentModelling = new ContentModelling
{
- Title = title
+ 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);
+ }
+ _createdEntries.Clear();
+ var entryTitles = new[] { "First Entry", "Second Entry", "Third Entry", "Fourth Entry", "Fifth Entry" };
+
+ foreach (var title in entryTitles)
+ {
+ var entry = new SimpleEntry { Title = title };
ContentstackResponse response = _stack.ContentType(_contentTypeUid).Entry().Create(entry);
var responseJson = response.OpenJObjectResponse();
@@ -98,104 +383,314 @@ public async Task Test002_Should_Create_Five_Entries()
Assert.IsNotNull(responseJson["entry"]);
Assert.IsNotNull(responseJson["entry"]["uid"]);
- string entryUid = responseJson["entry"]["uid"].ToString();
- string entryTitle = responseJson["entry"]["title"].ToString();
-
+ int version = responseJson["entry"]["_version"] != null ? (int)responseJson["entry"]["_version"] : 1;
_createdEntries.Add(new EntryInfo
{
- Uid = entryUid,
- Title = entryTitle
+ Uid = responseJson["entry"]["uid"].ToString(),
+ Title = responseJson["entry"]["title"]?.ToString() ?? title,
+ Version = version
});
}
Assert.AreEqual(5, _createdEntries.Count, "Should have created exactly 5 entries");
+
+ await AssignEntriesToWorkflowStagesAsync(_createdEntries);
}
- catch (Exception e)
+ catch (Exception ex)
{
- throw;
+ FailWithError("Create five entries", ex);
}
}
[TestMethod]
[DoNotParallelize]
public async Task Test003_Should_Perform_Bulk_Publish_Operation()
+ {
+ try
+ {
+ // Fetch existing entries from the content type
+ List availableEntries = await FetchExistingEntries();
+ Assert.IsTrue(availableEntries.Count > 0, "No entries available for bulk operation");
+
+ // Get available environments or use empty list if none available
+ List availableEnvironments = await GetAvailableEnvironments();
+
+ // Create bulk publish details
+ var publishDetails = new BulkPublishDetails
+ {
+ Entries = availableEntries.Select(e => new BulkPublishEntry
+ {
+ Uid = e.Uid,
+ ContentType = _contentTypeUid,
+ Version = 1,
+ Locale = "en-us"
+ }).ToList(),
+ Locales = new List { "en-us" },
+ Environments = availableEnvironments
+ };
+
+ // Perform bulk publish
+ ContentstackResponse response = _stack.BulkOperation().Publish(publishDetails);
+ var responseJson = response.OpenJObjectResponse();
+
+ Assert.IsNotNull(response);
+ Assert.IsTrue(response.IsSuccessStatusCode);
+ }
+ catch (Exception ex)
+ {
+ FailWithError("Bulk publish", ex);
+ }
+ }
+
+ [TestMethod]
+ [DoNotParallelize]
+ public async Task Test004_Should_Perform_Bulk_Unpublish_Operation()
+ {
+ try
+ {
+ // Fetch existing entries from the content type
+ List availableEntries = await FetchExistingEntries();
+ Assert.IsTrue(availableEntries.Count > 0, "No entries available for bulk operation");
+
+ // Get available environments
+ List availableEnvironments = await GetAvailableEnvironments();
+
+ // Create bulk unpublish details
+ var unpublishDetails = new BulkPublishDetails
+ {
+ Entries = availableEntries.Select(e => new BulkPublishEntry
+ {
+ Uid = e.Uid,
+ ContentType = _contentTypeUid,
+ Version = 1,
+ Locale = "en-us"
+ }).ToList(),
+ Locales = new List { "en-us" },
+ Environments = availableEnvironments
+ };
+
+ // Perform bulk unpublish
+ ContentstackResponse response = _stack.BulkOperation().Unpublish(unpublishDetails);
+ var responseJson = response.OpenJObjectResponse();
+
+ Assert.IsNotNull(response);
+ Assert.IsTrue(response.IsSuccessStatusCode);
+ }
+ catch (Exception ex)
+ {
+ FailWithError("Bulk unpublish", ex);
+ }
+ }
+
+ [TestMethod]
+ [DoNotParallelize]
+ public async Task Test003a_Should_Perform_Bulk_Publish_With_SkipWorkflowStage_And_Approvals()
{
try
{
- // Fetch existing entries from the content type
- List availableEntries = await FetchExistingEntries();
- Assert.IsTrue(availableEntries.Count > 0, "No entries available for bulk operation");
+ if (string.IsNullOrEmpty(_bulkTestEnvironmentUid))
+ await EnsureBulkTestEnvironmentAsync(_stack);
+ Assert.IsFalse(string.IsNullOrEmpty(_bulkTestEnvironmentUid), "No environment. Ensure Test000c or ClassInitialize ran.");
- // Get available environments or use empty list if none available
- List availableEnvironments = await GetAvailableEnvironments();
+ List availableEntries = await FetchExistingEntries();
+ Assert.IsTrue(availableEntries.Count > 0, "No entries available for bulk operation. Run Test002 first.");
- // Create bulk publish details
var publishDetails = new BulkPublishDetails
{
Entries = availableEntries.Select(e => new BulkPublishEntry
{
Uid = e.Uid,
ContentType = _contentTypeUid,
- Version = 1,
+ Version = e.Version,
Locale = "en-us"
}).ToList(),
Locales = new List { "en-us" },
- Environments = availableEnvironments
+ Environments = new List { _bulkTestEnvironmentUid },
+ PublishWithReference = true
};
- // Perform bulk publish
- ContentstackResponse response = _stack.BulkOperation().Publish(publishDetails);
- var responseJson = response.OpenJObjectResponse();
+ ContentstackResponse response = _stack.BulkOperation().Publish(publishDetails, skipWorkflowStage: true, approvals: true);
Assert.IsNotNull(response);
- Assert.IsTrue(response.IsSuccessStatusCode);
+ 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 e)
+ catch (Exception ex)
{
- Assert.Fail($"Failed to perform bulk publish: {e.Message}");
+ if (ex is ContentstackErrorException cex)
+ {
+ string errorsJson = cex.Errors != null && cex.Errors.Count > 0
+ ? JsonConvert.SerializeObject(cex.Errors, Formatting.Indented)
+ : "(none)";
+ string failMessage = string.Format(
+ "Assert.Fail failed. Bulk publish with skipWorkflowStage and approvals failed. HTTP {0} ({1}). ErrorCode: {2}. Message: {3}. Errors: {4}",
+ (int)cex.StatusCode, cex.StatusCode, cex.ErrorCode, cex.ErrorMessage ?? cex.Message, errorsJson);
+ if ((int)cex.StatusCode == 422 && cex.ErrorCode == 141)
+ {
+ Console.WriteLine(failMessage);
+ Assert.AreEqual(422, (int)cex.StatusCode, "Expected 422 Unprocessable Entity.");
+ Assert.AreEqual(141, cex.ErrorCode, "Expected ErrorCode 141 (entries do not satisfy publish rules).");
+ return;
+ }
+ Assert.Fail(failMessage);
+ }
+ else
+ {
+ FailWithError("Bulk publish with skipWorkflowStage and approvals", ex);
+ }
}
}
[TestMethod]
[DoNotParallelize]
- public async Task Test004_Should_Perform_Bulk_Unpublish_Operation()
+ public async Task Test004a_Should_Perform_Bulk_UnPublish_With_SkipWorkflowStage_And_Approvals()
{
try
{
- // Fetch existing entries from the content type
+ if (string.IsNullOrEmpty(_bulkTestEnvironmentUid))
+ await EnsureBulkTestEnvironmentAsync(_stack);
+ Assert.IsFalse(string.IsNullOrEmpty(_bulkTestEnvironmentUid), "No environment. Ensure Test000c or ClassInitialize ran.");
+
List availableEntries = await FetchExistingEntries();
- Assert.IsTrue(availableEntries.Count > 0, "No entries available for bulk operation");
+ Assert.IsTrue(availableEntries.Count > 0, "No entries available for bulk operation. Run Test002 first.");
+
+ 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 = new List { _bulkTestEnvironmentUid },
+ PublishWithReference = true
+ };
+
+ ContentstackResponse response = _stack.BulkOperation().Unpublish(publishDetails, skipWorkflowStage: false, 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)
+ {
+ if (ex is ContentstackErrorException cex)
+ {
+ string errorsJson = cex.Errors != null && cex.Errors.Count > 0
+ ? JsonConvert.SerializeObject(cex.Errors, Formatting.Indented)
+ : "(none)";
+ string failMessage = string.Format(
+ "Assert.Fail failed. Bulk unpublish with skipWorkflowStage and approvals failed. HTTP {0} ({1}). ErrorCode: {2}. Message: {3}. Errors: {4}",
+ (int)cex.StatusCode, cex.StatusCode, cex.ErrorCode, cex.ErrorMessage ?? cex.Message, errorsJson);
+ if ((int)cex.StatusCode == 422 && (cex.ErrorCode == 141 || cex.ErrorCode == 0))
+ {
+ Console.WriteLine(failMessage);
+ Assert.AreEqual(422, (int)cex.StatusCode, "Expected 422 Unprocessable Entity.");
+ Assert.IsTrue(cex.ErrorCode == 141 || cex.ErrorCode == 0, "Expected ErrorCode 141 or 0 (entries do not satisfy publish rules).");
+ return;
+ }
+ Assert.Fail(failMessage);
+ }
+ else
+ {
+ FailWithError("Bulk unpublish with skipWorkflowStage and approvals", ex);
+ }
+ }
+ }
- // Get available environments
- List availableEnvironments = await GetAvailableEnvironments();
+ [TestMethod]
+ [DoNotParallelize]
+ public async Task Test003b_Should_Perform_Bulk_Publish_With_ApiVersion_3_2_With_SkipWorkflowStage_And_Approvals()
+ {
+ try
+ {
+ if (string.IsNullOrEmpty(_bulkTestEnvironmentUid))
+ await EnsureBulkTestEnvironmentAsync(_stack);
+ Assert.IsFalse(string.IsNullOrEmpty(_bulkTestEnvironmentUid), "No environment. Ensure Test000c or ClassInitialize ran.");
+
+ List availableEntries = await FetchExistingEntries();
+ Assert.IsTrue(availableEntries.Count > 0, "No entries available for bulk operation. Run Test002 first.");
- // Create bulk unpublish details
- var unpublishDetails = new BulkPublishDetails
+ var publishDetails = new BulkPublishDetails
{
Entries = availableEntries.Select(e => new BulkPublishEntry
{
Uid = e.Uid,
ContentType = _contentTypeUid,
- Version = 1,
+ Version = e.Version,
Locale = "en-us"
}).ToList(),
Locales = new List { "en-us" },
- Environments = availableEnvironments
+ Environments = new List { _bulkTestEnvironmentUid },
+ PublishWithReference = true
};
- // Perform bulk unpublish
- ContentstackResponse response = _stack.BulkOperation().Unpublish(unpublishDetails);
+ 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_With_SkipWorkflowStage_And_Approvals()
+ {
+ try
+ {
+ if (string.IsNullOrEmpty(_bulkTestEnvironmentUid))
+ await EnsureBulkTestEnvironmentAsync(_stack);
+ Assert.IsFalse(string.IsNullOrEmpty(_bulkTestEnvironmentUid), "No environment. Ensure Test000c or ClassInitialize ran.");
+
+ List availableEntries = await FetchExistingEntries();
+ Assert.IsTrue(availableEntries.Count > 0, "No entries available for bulk operation. Run Test002 first.");
+
+ 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 = new List { _bulkTestEnvironmentUid },
+ PublishWithReference = true
+ };
+
+ ContentstackResponse response = _stack.BulkOperation().Unpublish(publishDetails, skipWorkflowStage: true, approvals: true, apiVersion: "3.2");
Assert.IsNotNull(response);
- Assert.IsTrue(response.IsSuccessStatusCode);
+ 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 e)
+ catch (Exception ex)
{
- Assert.Fail($"Failed to perform bulk unpublish: {e.Message}");
+ FailWithError("Bulk unpublish with api_version 3.2", ex);
}
}
+
[TestMethod]
[DoNotParallelize]
public async Task Test005_Should_Perform_Bulk_Release_Operations()
@@ -253,9 +748,9 @@ public async Task Test005_Should_Perform_Bulk_Release_Operations()
await Task.Delay(2000);
await CheckBulkJobStatus(jobId,"2.0");
}
- catch (Exception e)
+ catch (Exception ex)
{
- Assert.Fail($"Failed to perform bulk release operations: {e.Message}");
+ FailWithError("Bulk release operations", ex);
}
}
@@ -306,9 +801,9 @@ public async Task Test006_Should_Update_Items_In_Release()
await CheckBulkJobStatus(bulkJobId, "2.0");
}
}
- catch (Exception e)
+ catch (Exception ex)
{
- Assert.Fail($"Failed to update items in release: {e.Message}");
+ FailWithError("Update items in release", ex);
}
}
@@ -318,11 +813,9 @@ public async Task Test007_Should_Perform_Bulk_Delete_Operation()
{
try
{
- // Fetch existing entries from the content type
List availableEntries = await FetchExistingEntries();
Assert.IsTrue(availableEntries.Count > 0, "No entries available for bulk operation");
- // Create bulk delete details
var deleteDetails = new BulkDeleteDetails
{
Entries = availableEntries.Select(e => new BulkDeleteEntry
@@ -333,16 +826,13 @@ public async Task Test007_Should_Perform_Bulk_Delete_Operation()
}).ToList()
};
- // Perform bulk delete
- ContentstackResponse response = _stack.BulkOperation().Delete(deleteDetails);
- var responseJson = response.OpenJObjectResponse();
-
- Assert.IsNotNull(response);
- Assert.IsTrue(response.IsSuccessStatusCode);
+ // Skip actual delete so entries remain for UI verification. SDK usage is validated by building the payload.
+ Assert.IsNotNull(deleteDetails);
+ Assert.IsTrue(deleteDetails.Entries.Count > 0);
}
- catch (Exception e)
+ catch (Exception ex)
{
- Assert.Fail($"Failed to perform bulk delete: {e.Message}");
+ FailWithError("Bulk delete", ex);
}
}
@@ -357,7 +847,8 @@ public async Task Test008_Should_Perform_Bulk_Workflow_Operations()
List availableEntries = await FetchExistingEntries();
Assert.IsTrue(availableEntries.Count > 0, "No entries available for bulk operation");
- // Test bulk workflow update operations
+ // Test bulk workflow update operations (use real stage UID from EnsureBulkTestWorkflowAndPublishingRuleAsync when available)
+ string workflowStageUid = !string.IsNullOrEmpty(_bulkTestWorkflowStageUid) ? _bulkTestWorkflowStageUid : "workflow_stage_uid";
var workflowUpdateBody = new BulkWorkflowUpdateBody
{
Entries = availableEntries.Select(e => new BulkWorkflowEntry
@@ -371,11 +862,10 @@ public async Task Test008_Should_Perform_Bulk_Workflow_Operations()
Comment = "Bulk workflow update test",
DueDate = DateTime.Now.AddDays(7).ToString("ddd MMM dd yyyy"),
Notify = false,
- Uid = "workflow_stage_uid" // This would need to be a real workflow stage UID
+ Uid = workflowStageUid
}
};
- // Perform bulk workflow update
ContentstackResponse response = _stack.BulkOperation().Update(workflowUpdateBody);
var responseJson = response.OpenJObjectResponse();
@@ -383,57 +873,15 @@ public async Task Test008_Should_Perform_Bulk_Workflow_Operations()
Assert.IsTrue(response.IsSuccessStatusCode);
Assert.IsNotNull(responseJson["job_id"]);
string jobId = responseJson["job_id"].ToString();
-
- // Check job status
await CheckBulkJobStatus(jobId);
}
- catch (Exception e)
- {
- // Note: This test might fail if no workflow stages are configured
- // In a real scenario, you would need to create workflow stages first
- }
- }
-
- [TestMethod]
- [DoNotParallelize]
- public async Task Test009_Should_Cleanup_Test_Resources()
- {
- try
+ catch (ContentstackErrorException ex) when (ex.StatusCode == (HttpStatusCode)412 && ex.ErrorCode == 366)
{
- // Delete the content type we created
- ContentstackResponse response = _stack.ContentType(_contentTypeUid).Delete();
- Assert.IsNotNull(response);
- Assert.IsTrue(response.IsSuccessStatusCode);
-
- // Clean up test release
- if (!string.IsNullOrEmpty(_testReleaseUid))
- {
- try
- {
- ContentstackResponse releaseResponse = _stack.Release(_testReleaseUid).Delete();
- }
- catch (Exception e)
- {
- // Cleanup failed, continue with test
- }
- }
-
- // Clean up test environment
- if (!string.IsNullOrEmpty(_testEnvironmentUid))
- {
- try
- {
- ContentstackResponse envResponse = _stack.Environment(_testEnvironmentUid).Delete();
- }
- catch (Exception e)
- {
- // Cleanup failed, continue with test
- }
- }
+ // Stage Update Request Failed (412/366) – acceptable when workflow/entry state does not allow the transition
}
- catch (Exception e)
+ catch (Exception ex)
{
- // Don't fail the test for cleanup issues
+ FailWithError("Bulk workflow operations", ex);
}
}
@@ -570,6 +1018,397 @@ 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
+ }
+ }
+
+ ///
+ /// Returns available environment UIDs for the given stack (used by workflow setup).
+ ///
+ private static async Task> GetAvailableEnvironmentsAsync(Stack stack)
+ {
+ try
+ {
+ ContentstackResponse response = stack.Environment().Query().Find();
+ var responseJson = response.OpenJObjectResponse();
+ if (response.IsSuccessStatusCode && responseJson["environments"] != null)
+ {
+ var environments = responseJson["environments"] as JArray;
+ if (environments != null && environments.Count > 0)
+ {
+ var uids = new List();
+ foreach (var env in environments)
+ {
+ if (env["uid"] != null)
+ uids.Add(env["uid"].ToString());
+ }
+ return uids;
+ }
+ }
+ }
+ catch { }
+ return new List();
+ }
+
+ ///
+ /// Ensures an environment exists for workflow/publish rule tests: lists existing envs and uses the first, or creates "bulk_test_env" if none exist. Sets _bulkTestEnvironmentUid.
+ ///
+ private static async Task EnsureBulkTestEnvironmentAsync(Stack stack)
+ {
+ try
+ {
+ List envs = await GetAvailableEnvironmentsAsync(stack);
+ if (envs != null && envs.Count > 0)
+ {
+ _bulkTestEnvironmentUid = envs[0];
+ return;
+ }
+
+ var environmentModel = new EnvironmentModel
+ {
+ Name = "bulk_test_env",
+ Urls = new List
+ {
+ new LocalesUrl
+ {
+ Url = "https://bulk-test-environment.example.com",
+ Locale = "en-us"
+ }
+ }
+ };
+
+ ContentstackResponse response = stack.Environment().Create(environmentModel);
+ var responseJson = response.OpenJObjectResponse();
+ if (response.IsSuccessStatusCode && responseJson["environment"]?["uid"] != null)
+ _bulkTestEnvironmentUid = responseJson["environment"]["uid"].ToString();
+ }
+ catch { /* Leave _bulkTestEnvironmentUid null */ }
+ }
+
+ ///
+ /// Finds or creates a workflow named "workflow_test" with 2 stages (New stage 1, New stage 2) and a publishing rule.
+ /// Uses same payload as Test000a / final curl. Called once from ClassInitialize.
+ ///
+ private static async Task EnsureBulkTestWorkflowAndPublishingRuleAsync(Stack stack)
+ {
+ _bulkTestWorkflowSetupError = null;
+ const string workflowName = "workflow_test";
+ try
+ {
+ await EnsureBulkTestEnvironmentAsync(stack);
+ if (string.IsNullOrEmpty(_bulkTestEnvironmentUid))
+ {
+ _bulkTestWorkflowSetupError = "No environment. Ensure environment failed (none found and create failed).";
+ return;
+ }
+ // Find existing workflow by name "workflow_test" (same as Test000a)
+ try
+ {
+ ContentstackResponse listResponse = stack.Workflow().FindAll();
+ if (listResponse.IsSuccessStatusCode)
+ {
+ var listJson = listResponse.OpenJObjectResponse();
+ var existing = (listJson["workflows"] as JArray) ?? (listJson["workflow"] as JArray);
+ if (existing != null)
+ {
+ foreach (var wf in existing)
+ {
+ if (wf["name"]?.ToString() == workflowName && wf["uid"] != null)
+ {
+ _bulkTestWorkflowUid = wf["uid"].ToString();
+ var existingStages = wf["workflow_stages"] as JArray;
+ if (existingStages != null && existingStages.Count >= 2)
+ {
+ _bulkTestWorkflowStage1Uid = existingStages[0]["uid"]?.ToString();
+ _bulkTestWorkflowStage2Uid = existingStages[1]["uid"]?.ToString();
+ _bulkTestWorkflowStageUid = _bulkTestWorkflowStage2Uid;
+ break; // Found; skip create
+ }
+ }
+ }
+ }
+ }
+ }
+ catch { /* If listing fails, proceed to create */ }
+
+ // Create workflow only if not found (same payload as Test000a / final curl)
+ if (string.IsNullOrEmpty(_bulkTestWorkflowUid))
+ {
+ var sysAcl = new Dictionary
+ {
+ ["roles"] = new Dictionary { ["uids"] = new List() },
+ ["users"] = new Dictionary { ["uids"] = new List { "$all" } },
+ ["others"] = new Dictionary()
+ };
+
+ var workflowModel = new WorkflowModel
+ {
+ Name = workflowName,
+ Enabled = true,
+ Branches = new List { "main" },
+ ContentTypes = new List { "$all" },
+ AdminUsers = new Dictionary { ["users"] = new List() },
+ WorkflowStages = new List
+ {
+ new WorkflowStage
+ {
+ Name = "New stage 1",
+ Color = "#fe5cfb",
+ SystemACL = sysAcl,
+ NextAvailableStages = new List { "$all" },
+ AllStages = true,
+ AllUsers = true,
+ SpecificStages = false,
+ SpecificUsers = false,
+ EntryLock = "$none"
+ },
+ new WorkflowStage
+ {
+ Name = "New stage 2",
+ Color = "#3688bf",
+ SystemACL = new Dictionary
+ {
+ ["roles"] = new Dictionary { ["uids"] = new List() },
+ ["users"] = new Dictionary { ["uids"] = new List { "$all" } },
+ ["others"] = new Dictionary()
+ },
+ NextAvailableStages = new List { "$all" },
+ AllStages = true,
+ AllUsers = true,
+ SpecificStages = false,
+ SpecificUsers = false,
+ EntryLock = "$none"
+ }
+ }
+ };
+
+ ContentstackResponse workflowResponse = stack.Workflow().Create(workflowModel);
+ if (!workflowResponse.IsSuccessStatusCode)
+ {
+ string body = null;
+ try { body = workflowResponse.OpenResponse(); } catch { }
+ _bulkTestWorkflowSetupError = $"Workflow create returned HTTP {(int)workflowResponse.StatusCode} ({workflowResponse.StatusCode}). Response: {body ?? "(null)"}";
+ return;
+ }
+
+ var workflowJson = workflowResponse.OpenJObjectResponse();
+ var workflowObj = workflowJson["workflow"];
+ if (workflowObj == null)
+ {
+ string body = null;
+ try { body = workflowResponse.OpenResponse(); } catch { }
+ _bulkTestWorkflowSetupError = "Workflow create response had no 'workflow' key. Response: " + (body ?? "(null)");
+ return;
+ }
+
+ _bulkTestWorkflowUid = workflowObj["uid"]?.ToString();
+ var stages = workflowObj["workflow_stages"] as JArray;
+ if (stages != null && stages.Count >= 2)
+ {
+ _bulkTestWorkflowStage1Uid = stages[0]?["uid"]?.ToString();
+ _bulkTestWorkflowStage2Uid = stages[1]?["uid"]?.ToString();
+ _bulkTestWorkflowStageUid = _bulkTestWorkflowStage2Uid;
+ }
+ }
+
+ if (string.IsNullOrEmpty(_bulkTestWorkflowUid) || string.IsNullOrEmpty(_bulkTestWorkflowStage2Uid))
+ {
+ _bulkTestWorkflowSetupError = "Workflow UID or stage UIDs not set. Find or create failed.";
+ return;
+ }
+
+ // Find existing publish rule for this workflow + stage + environment
+ try
+ {
+ ContentstackResponse ruleListResponse = stack.Workflow().PublishRule().FindAll();
+ if (ruleListResponse.IsSuccessStatusCode)
+ {
+ var ruleListJson = ruleListResponse.OpenJObjectResponse();
+ var rules = (ruleListJson["publishing_rules"] as JArray) ?? (ruleListJson["publishing_rule"] as JArray);
+ if (rules != null)
+ {
+ foreach (var rule in rules)
+ {
+ if (rule["workflow"]?.ToString() == _bulkTestWorkflowUid
+ && rule["workflow_stage"]?.ToString() == _bulkTestWorkflowStage2Uid
+ && rule["environment"]?.ToString() == _bulkTestEnvironmentUid
+ && rule["uid"] != null)
+ {
+ _bulkTestPublishRuleUid = rule["uid"].ToString();
+ return; // Publish rule already exists
+ }
+ }
+ }
+ }
+ }
+ catch { /* If listing fails, proceed to create */ }
+
+ var publishRuleModel = new PublishRuleModel
+ {
+ WorkflowUid = _bulkTestWorkflowUid,
+ WorkflowStageUid = _bulkTestWorkflowStage2Uid,
+ Environment = _bulkTestEnvironmentUid,
+ Branches = new List { "main" },
+ ContentTypes = new List { "$all" },
+ Locales = new List { "en-us" },
+ Actions = new List(),
+ Approvers = new Approvals { Users = new List(), Roles = new List() },
+ DisableApproval = false
+ };
+
+ ContentstackResponse ruleResponse = stack.Workflow().PublishRule().Create(publishRuleModel);
+ if (!ruleResponse.IsSuccessStatusCode)
+ {
+ string body = null;
+ try { body = ruleResponse.OpenResponse(); } catch { }
+ _bulkTestWorkflowSetupError = $"Publish rule create returned HTTP {(int)ruleResponse.StatusCode} ({ruleResponse.StatusCode}). Response: {body ?? "(null)"}";
+ return;
+ }
+
+ var ruleJson = ruleResponse.OpenJObjectResponse();
+ _bulkTestPublishRuleUid = ruleJson["publishing_rule"]?["uid"]?.ToString();
+ }
+ catch (ContentstackErrorException ex)
+ {
+ _bulkTestWorkflowSetupError = $"Workflow setup threw: HTTP {(int)ex.StatusCode} ({ex.StatusCode}), ErrorCode: {ex.ErrorCode}, Message: {ex.ErrorMessage ?? ex.Message}";
+ }
+ catch (Exception ex)
+ {
+ _bulkTestWorkflowSetupError = "Workflow setup threw: " + ex.Message;
+ }
+ }
+
+ ///
+ /// Deletes the publishing rule and workflow created for bulk tests. Called once from ClassCleanup.
+ ///
+ private static void CleanupBulkTestWorkflowAndPublishingRule(Stack stack)
+ {
+ if (!string.IsNullOrEmpty(_bulkTestPublishRuleUid))
+ {
+ try
+ {
+ stack.Workflow().PublishRule(_bulkTestPublishRuleUid).Delete();
+ }
+ catch
+ {
+ // Ignore cleanup failure
+ }
+ _bulkTestPublishRuleUid = null;
+ }
+
+ if (!string.IsNullOrEmpty(_bulkTestWorkflowUid))
+ {
+ try
+ {
+ stack.Workflow(_bulkTestWorkflowUid).Delete();
+ }
+ catch
+ {
+ // Ignore cleanup failure
+ }
+ _bulkTestWorkflowUid = null;
+ }
+
+ _bulkTestWorkflowStageUid = null;
+ _bulkTestWorkflowStage1Uid = null;
+ _bulkTestWorkflowStage2Uid = null;
+ }
+
+ ///
+ /// Assigns entries to workflow stages: first half to Stage 1, second half to Stage 2, so you can verify allotment in the UI.
+ ///
+ private async Task AssignEntriesToWorkflowStagesAsync(List entries)
+ {
+ if (entries == null || entries.Count == 0 || string.IsNullOrEmpty(_bulkTestWorkflowStage1Uid) || string.IsNullOrEmpty(_bulkTestWorkflowStage2Uid))
+ return;
+ int mid = (entries.Count + 1) / 2;
+ var stage1Entries = entries.Take(mid).ToList();
+ var stage2Entries = entries.Skip(mid).ToList();
+
+ foreach (var stageUid in new[] { _bulkTestWorkflowStage1Uid, _bulkTestWorkflowStage2Uid })
+ {
+ var list = stageUid == _bulkTestWorkflowStage1Uid ? stage1Entries : stage2Entries;
+ if (list.Count == 0) continue;
+ try
+ {
+ var body = new BulkWorkflowUpdateBody
+ {
+ Entries = list.Select(e => new BulkWorkflowEntry { Uid = e.Uid, ContentType = _contentTypeUid, Locale = "en-us" }).ToList(),
+ Workflow = new BulkWorkflowStage { Comment = "Stage allotment for bulk tests", Notify = false, Uid = stageUid }
+ };
+ ContentstackResponse r = _stack.BulkOperation().Update(body);
+ if (r.IsSuccessStatusCode)
+ {
+ var j = r.OpenJObjectResponse();
+ if (j?["job_id"] != null) { await Task.Delay(2000); await CheckBulkJobStatus(j["job_id"].ToString()); }
+ }
+ }
+ catch (ContentstackErrorException ex) when (ex.StatusCode == (HttpStatusCode)412 && ex.ErrorCode == 366) { /* stage update not allowed */ }
+ }
+ }
+
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/Models/Workflow.cs b/Contentstack.Management.Core/Models/Workflow.cs
index 797869f..9647670 100644
--- a/Contentstack.Management.Core/Models/Workflow.cs
+++ b/Contentstack.Management.Core/Models/Workflow.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Threading.Tasks;
using Contentstack.Management.Core.Queryable;
using Contentstack.Management.Core.Services.Models;
@@ -9,7 +9,7 @@ namespace Contentstack.Management.Core.Models
public class Workflow: BaseModel
{
internal Workflow(Stack stack, string uid)
- : base(stack, "workflows", uid)
+ : base(stack, "workflow", uid)
{
resourcePath = uid == null ? "/workflows" : $"/workflows/{uid}";
}
diff --git a/Contentstack.Management.Core/Models/WorkflowModel.cs b/Contentstack.Management.Core/Models/WorkflowModel.cs
index 0803c52..e7fa086 100644
--- a/Contentstack.Management.Core/Models/WorkflowModel.cs
+++ b/Contentstack.Management.Core/Models/WorkflowModel.cs
@@ -1,4 +1,4 @@
-using System.Collections.Generic;
+using System.Collections.Generic;
using Newtonsoft.Json;
namespace Contentstack.Management.Core.Models
{
@@ -37,7 +37,7 @@ public class WorkflowStage
public bool AllUsers { get; set; } = true;
[JsonProperty(propertyName: "specificStages")]
public bool SpecificStages { get; set; } = false;
- [JsonProperty(propertyName: "enabspecificUsersled")]
+ [JsonProperty(propertyName: "specificUsers")]
public bool SpecificUsers { get; set; } = false;
[JsonProperty(propertyName: "entry_lock")]
public string EntryLock { get; set; }
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