From 175768f5b424d6ea6d2d69610ba3580b8961c82a Mon Sep 17 00:00:00 2001 From: sunil-lakshman <104969541+sunil-lakshman@users.noreply.github.com> Date: Fri, 29 May 2026 13:34:52 +0530 Subject: [PATCH 1/2] Added branches support --- CHANGELOG.md | 11 + .../Contentstack005_BranchTest.cs | 469 ++++++++++++++++++ .../Models/BranchTest.cs | 68 +++ Contentstack.Management.Core/Models/Branch.cs | 127 +++++ .../Models/BranchModel.cs | 36 ++ Contentstack.Management.Core/Models/Stack.cs | 31 ++ .../Models/WebhookModel.cs | 18 +- .../Models/WorkflowModel.cs | 22 +- 8 files changed, 762 insertions(+), 20 deletions(-) create mode 100644 Contentstack.Management.Core.Tests/IntegrationTest/Contentstack005_BranchTest.cs create mode 100644 Contentstack.Management.Core.Unit.Tests/Models/BranchTest.cs create mode 100644 Contentstack.Management.Core/Models/Branch.cs create mode 100644 Contentstack.Management.Core/Models/BranchModel.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index 43055c9..8d7b888 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,17 @@ # Changelog ## [v1.0.0-beta.1](https://github.com/contentstack/contentstack-management-dotnet/tree/v1.0.0-beta.1) + - **Feat** + - **Branch support** + - Added `Branch` model with `Create`, `CreateAsync`, `Fetch`, `FetchAsync`, `Delete`, `DeleteAsync`, and `Query` operations + - Added `BranchModel` and `BranchAliasModel` with System.Text.Json serialization + - `Stack.Branch(uid?)` accessor follows the same pattern as other stack resources + - Added unit tests (`BranchTest`) covering resource path, SDK guard conditions, and query parameter construction + - Added integration tests (`Contentstack005_BranchTest`) covering CRUD lifecycle, query with pagination, SDK validation errors, and API error scenarios + - **Webhook migration to System.Text.Json** + - Migrated `WebhookModel` to use `System.Text.Json` attributes + - **Workflow migration to System.Text.Json** + - Migrated `WorkflowModel` to use `System.Text.Json` attributes - **Breaking Change** - **System.Text.Json Migration (Beta)** - Migrated core serialization from Newtonsoft.Json to System.Text.Json diff --git a/Contentstack.Management.Core.Tests/IntegrationTest/Contentstack005_BranchTest.cs b/Contentstack.Management.Core.Tests/IntegrationTest/Contentstack005_BranchTest.cs new file mode 100644 index 0000000..9fa4c6a --- /dev/null +++ b/Contentstack.Management.Core.Tests/IntegrationTest/Contentstack005_BranchTest.cs @@ -0,0 +1,469 @@ +using System; +using System.Net; +using System.Threading.Tasks; +using Contentstack.Management.Core.Exceptions; +using Contentstack.Management.Core.Models; +using Contentstack.Management.Core.Tests.Helpers; +using Contentstack.Management.Core.Tests.Model; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Contentstack.Management.Core.Tests.IntegrationTest +{ + [TestClass] + public class Contentstack005_BranchTest + { + private static ContentstackClient _client; + private static Stack _stack; + + private static string _createdBranchUid; + private static string _createdBranchUidAsync; + + private const string SourceBranch = "main"; + private const string TestBranchUid = "dotnet-sdk-int-test-branch"; + private const string TestBranchUidAsync = "dotnet-sdk-int-test-branch-async"; + private const string NonExistentBranchUid = "blt00000000nonexistent00"; + + [ClassInitialize] + public static void ClassInitialize(TestContext context) + { + _client = Contentstack.CreateAuthenticatedClient(); + StackResponse response = StackResponse.getStack(_client.serializer); + _stack = _client.Stack(response.Stack.APIKey); + + // Best-effort cleanup of any branches left over from a previous test run + TryDeleteBranch(TestBranchUid); + TryDeleteBranch(TestBranchUidAsync); + } + + [ClassCleanup] + public static void ClassCleanup() + { + TryDeleteBranch(_createdBranchUid); + TryDeleteBranch(_createdBranchUidAsync); + + try { _client?.Logout(); } catch { } + _client = null; + } + + private static void TryDeleteBranch(string uid) + { + if (string.IsNullOrEmpty(uid)) return; + try { _stack.Branch(uid).Delete(); } catch { } + } + + // ---- Query tests ------------------------------------------------------- + + [TestMethod] + [DoNotParallelize] + public void Test001_Should_Query_All_Branches() + { + TestOutputLogger.LogContext("TestScenario", "Branch_QueryAll_Sync"); + try + { + ContentstackResponse response = _stack.Branch().Query().Find(); + var json = response.OpenJsonObjectResponse(); + AssertLogger.IsNotNull(json, "response"); + } + catch (Exception e) + { + AssertLogger.Fail(e.Message); + } + } + + [TestMethod] + [DoNotParallelize] + public async Task Test002_Should_Query_All_Branches_Async() + { + TestOutputLogger.LogContext("TestScenario", "Branch_QueryAll_Async"); + try + { + ContentstackResponse response = await _stack.Branch().Query().FindAsync(); + var json = response.OpenJsonObjectResponse(); + AssertLogger.IsNotNull(json, "response"); + } + catch (Exception e) + { + AssertLogger.Fail(e.Message); + } + } + + [TestMethod] + [DoNotParallelize] + public void Test003_Should_Query_Branches_With_Limit_Skip_And_IncludeCount() + { + TestOutputLogger.LogContext("TestScenario", "Branch_QueryWithParams_Sync"); + try + { + ContentstackResponse response = _stack.Branch().Query() + .Limit(5).Skip(0).IncludeCount().Find(); + var json = response.OpenJsonObjectResponse(); + AssertLogger.IsNotNull(json, "response"); + } + catch (Exception e) + { + AssertLogger.Fail(e.Message); + } + } + + // ---- Create tests ------------------------------------------------------ + + [TestMethod] + [DoNotParallelize] + public void Test004_Should_Create_Branch() + { + TestOutputLogger.LogContext("TestScenario", "Branch_Create_Sync"); + try + { + var model = new BranchModel { Uid = TestBranchUid, Source = SourceBranch }; + ContentstackResponse response = _stack.Branch().Create(model); + var json = response.OpenJsonObjectResponse(); + AssertLogger.IsNotNull(json, "response"); + _createdBranchUid = TestBranchUid; + } + catch (Exception e) + { + AssertLogger.Fail(e.Message); + } + } + + [TestMethod] + [DoNotParallelize] + public async Task Test005_Should_Create_Branch_Async() + { + TestOutputLogger.LogContext("TestScenario", "Branch_Create_Async"); + try + { + var model = new BranchModel { Uid = TestBranchUidAsync, Source = SourceBranch }; + ContentstackResponse response = await _stack.Branch().CreateAsync(model); + var json = response.OpenJsonObjectResponse(); + AssertLogger.IsNotNull(json, "response"); + _createdBranchUidAsync = TestBranchUidAsync; + } + catch (Exception e) + { + AssertLogger.Fail(e.Message); + } + } + + // ---- Fetch tests ------------------------------------------------------- + + [TestMethod] + [DoNotParallelize] + public void Test006_Should_Fetch_Main_Branch() + { + TestOutputLogger.LogContext("TestScenario", "Branch_Fetch_Main_Sync"); + try + { + ContentstackResponse response = _stack.Branch(SourceBranch).Fetch(); + var json = response.OpenJsonObjectResponse(); + AssertLogger.IsNotNull(json, "response"); + } + catch (Exception e) + { + AssertLogger.Fail(e.Message); + } + } + + [TestMethod] + [DoNotParallelize] + public async Task Test007_Should_Fetch_Main_Branch_Async() + { + TestOutputLogger.LogContext("TestScenario", "Branch_Fetch_Main_Async"); + try + { + ContentstackResponse response = await _stack.Branch(SourceBranch).FetchAsync(); + var json = response.OpenJsonObjectResponse(); + AssertLogger.IsNotNull(json, "response"); + } + catch (Exception e) + { + AssertLogger.Fail(e.Message); + } + } + + [TestMethod] + [DoNotParallelize] + public void Test008_Should_Fetch_Created_Branch() + { + TestOutputLogger.LogContext("TestScenario", "Branch_Fetch_Created_Sync"); + if (string.IsNullOrEmpty(_createdBranchUid)) + Assert.Inconclusive("Test004 did not create branch — skipping."); + try + { + ContentstackResponse response = _stack.Branch(_createdBranchUid).Fetch(); + var json = response.OpenJsonObjectResponse(); + AssertLogger.IsNotNull(json, "response"); + } + catch (Exception e) + { + AssertLogger.Fail(e.Message); + } + } + + [TestMethod] + [DoNotParallelize] + public async Task Test009_Should_Fetch_Created_Branch_Async() + { + TestOutputLogger.LogContext("TestScenario", "Branch_Fetch_Created_Async"); + if (string.IsNullOrEmpty(_createdBranchUidAsync)) + Assert.Inconclusive("Test005 did not create branch — skipping."); + try + { + ContentstackResponse response = await _stack.Branch(_createdBranchUidAsync).FetchAsync(); + var json = response.OpenJsonObjectResponse(); + AssertLogger.IsNotNull(json, "response"); + } + catch (Exception e) + { + AssertLogger.Fail(e.Message); + } + } + + // ---- Delete tests ------------------------------------------------------ + + [TestMethod] + [DoNotParallelize] + public void Test010_Should_Delete_Branch() + { + TestOutputLogger.LogContext("TestScenario", "Branch_Delete_Sync"); + if (string.IsNullOrEmpty(_createdBranchUid)) + Assert.Inconclusive("Test004 did not create branch — skipping."); + try + { + ContentstackResponse response = _stack.Branch(_createdBranchUid).Delete(); + var json = response.OpenJsonObjectResponse(); + AssertLogger.IsNotNull(json, "response"); + _createdBranchUid = null; + } + catch (Exception e) + { + AssertLogger.Fail(e.Message); + } + } + + [TestMethod] + [DoNotParallelize] + public async Task Test011_Should_Delete_Branch_Async() + { + TestOutputLogger.LogContext("TestScenario", "Branch_Delete_Async"); + if (string.IsNullOrEmpty(_createdBranchUidAsync)) + Assert.Inconclusive("Test005 did not create branch — skipping."); + try + { + ContentstackResponse response = await _stack.Branch(_createdBranchUidAsync).DeleteAsync(); + var json = response.OpenJsonObjectResponse(); + AssertLogger.IsNotNull(json, "response"); + _createdBranchUidAsync = null; + } + catch (Exception e) + { + AssertLogger.Fail(e.Message); + } + } + + // ---- SDK validation tests (no API call) -------------------------------- + + [TestMethod] + [DoNotParallelize] + public void Test012_Should_Throw_When_Query_Called_With_Uid() + { + TestOutputLogger.LogContext("TestScenario", "Branch_Query_WithUid_Throws"); + Assert.ThrowsException(() => + _stack.Branch("some-uid").Query()); + } + + [TestMethod] + [DoNotParallelize] + public void Test013_Should_Throw_When_Fetch_Called_Without_Uid() + { + TestOutputLogger.LogContext("TestScenario", "Branch_Fetch_NoUid_Throws_Sync"); + Assert.ThrowsException(() => + _stack.Branch().Fetch()); + } + + [TestMethod] + [DoNotParallelize] + public async Task Test014_Should_Throw_When_FetchAsync_Called_Without_Uid() + { + TestOutputLogger.LogContext("TestScenario", "Branch_Fetch_NoUid_Throws_Async"); + await Assert.ThrowsExceptionAsync(() => + _stack.Branch().FetchAsync()); + } + + [TestMethod] + [DoNotParallelize] + public void Test015_Should_Throw_When_Delete_Called_Without_Uid() + { + TestOutputLogger.LogContext("TestScenario", "Branch_Delete_NoUid_Throws_Sync"); + Assert.ThrowsException(() => + _stack.Branch().Delete()); + } + + [TestMethod] + [DoNotParallelize] + public async Task Test016_Should_Throw_When_DeleteAsync_Called_Without_Uid() + { + TestOutputLogger.LogContext("TestScenario", "Branch_Delete_NoUid_Throws_Async"); + await Assert.ThrowsExceptionAsync(() => + _stack.Branch().DeleteAsync()); + } + + [TestMethod] + [DoNotParallelize] + public void Test017_Should_Throw_When_Create_Called_With_Uid() + { + TestOutputLogger.LogContext("TestScenario", "Branch_Create_WithUid_Throws_Sync"); + Assert.ThrowsException(() => + _stack.Branch("some-uid").Create(new BranchModel())); + } + + [TestMethod] + [DoNotParallelize] + public async Task Test018_Should_Throw_When_CreateAsync_Called_With_Uid() + { + TestOutputLogger.LogContext("TestScenario", "Branch_Create_WithUid_Throws_Async"); + await Assert.ThrowsExceptionAsync(() => + _stack.Branch("some-uid").CreateAsync(new BranchModel())); + } + + // ---- API error tests --------------------------------------------------- + + [TestMethod] + [DoNotParallelize] + public void Test019_Should_Fail_Fetch_NonExistent_Branch() + { + TestOutputLogger.LogContext("TestScenario", "Branch_Fetch_NonExistent_Sync"); + try + { + _stack.Branch(NonExistentBranchUid).Fetch(); + AssertLogger.Fail("Expected API error for non-existent branch fetch"); + } + catch (ContentstackErrorException ex) + { + AssertBranchApiError(ex, "Branch_Fetch_NonExistent"); + } + } + + [TestMethod] + [DoNotParallelize] + public async Task Test020_Should_Fail_FetchAsync_NonExistent_Branch() + { + TestOutputLogger.LogContext("TestScenario", "Branch_Fetch_NonExistent_Async"); + try + { + await _stack.Branch(NonExistentBranchUid).FetchAsync(); + AssertLogger.Fail("Expected API error for non-existent branch fetch"); + } + catch (ContentstackErrorException ex) + { + AssertBranchApiError(ex, "Branch_FetchAsync_NonExistent"); + } + } + + [TestMethod] + [DoNotParallelize] + public void Test021_Should_Fail_Delete_NonExistent_Branch() + { + TestOutputLogger.LogContext("TestScenario", "Branch_Delete_NonExistent_Sync"); + try + { + _stack.Branch(NonExistentBranchUid).Delete(); + AssertLogger.Fail("Expected API error for non-existent branch delete"); + } + catch (ContentstackErrorException ex) + { + AssertBranchApiError(ex, "Branch_Delete_NonExistent"); + } + } + + [TestMethod] + [DoNotParallelize] + public async Task Test022_Should_Fail_DeleteAsync_NonExistent_Branch() + { + TestOutputLogger.LogContext("TestScenario", "Branch_Delete_NonExistent_Async"); + try + { + await _stack.Branch(NonExistentBranchUid).DeleteAsync(); + AssertLogger.Fail("Expected API error for non-existent branch delete"); + } + catch (ContentstackErrorException ex) + { + AssertBranchApiError(ex, "Branch_DeleteAsync_NonExistent"); + } + } + + [TestMethod] + [DoNotParallelize] + public void Test023_Should_Fail_Create_With_Invalid_Source_Branch() + { + TestOutputLogger.LogContext("TestScenario", "Branch_Create_InvalidSource_Sync"); + var model = new BranchModel + { + Uid = "dotnet-sdk-invalid-src-test", + Source = "non_existent_source_branch_99999" + }; + try + { + _stack.Branch().Create(model); + AssertLogger.Fail("Expected API error for invalid source branch"); + } + catch (ContentstackErrorException ex) + { + AssertBranchApiError(ex, "Branch_Create_InvalidSource"); + } + } + + [TestMethod] + [DoNotParallelize] + public async Task Test024_Should_Fail_CreateAsync_With_Invalid_Source_Branch() + { + TestOutputLogger.LogContext("TestScenario", "Branch_Create_InvalidSource_Async"); + var model = new BranchModel + { + Uid = "dotnet-sdk-invalid-src-async", + Source = "non_existent_source_branch_88888" + }; + try + { + await _stack.Branch().CreateAsync(model); + AssertLogger.Fail("Expected API error for invalid source branch"); + } + catch (ContentstackErrorException ex) + { + AssertBranchApiError(ex, "Branch_CreateAsync_InvalidSource"); + } + } + + [TestMethod] + [DoNotParallelize] + public void Test025_Should_Fail_Create_Duplicate_Branch() + { + TestOutputLogger.LogContext("TestScenario", "Branch_Create_Duplicate_Sync"); + // main always exists; creating it again must fail + var model = new BranchModel { Uid = SourceBranch, Source = SourceBranch }; + try + { + _stack.Branch().Create(model); + AssertLogger.Fail("Expected API error for duplicate branch creation"); + } + catch (ContentstackErrorException ex) + { + AssertBranchApiError(ex, "Branch_Create_Duplicate"); + } + } + + // ---- Helper methods --------------------------------------------------- + + private static void AssertBranchApiError(ContentstackErrorException ex, string assertionName) + { + AssertLogger.IsTrue( + ex.StatusCode == HttpStatusCode.NotFound || + ex.StatusCode == HttpStatusCode.BadRequest || + ex.StatusCode == (HttpStatusCode)422 || + ex.StatusCode == HttpStatusCode.Conflict || + ex.StatusCode == HttpStatusCode.Forbidden, + $"Expected branch API error, got {(int)ex.StatusCode} ({ex.StatusCode})", + assertionName); + } + } +} diff --git a/Contentstack.Management.Core.Unit.Tests/Models/BranchTest.cs b/Contentstack.Management.Core.Unit.Tests/Models/BranchTest.cs new file mode 100644 index 0000000..f958c36 --- /dev/null +++ b/Contentstack.Management.Core.Unit.Tests/Models/BranchTest.cs @@ -0,0 +1,68 @@ +using System; +using AutoFixture; +using Contentstack.Management.Core.Models; +using Contentstack.Management.Core.Queryable; +using Contentstack.Management.Core.Unit.Tests.Mokes; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Contentstack.Management.Core.Unit.Tests.Models +{ + [TestClass] + public class BranchTest + { + private Stack _stack; + private MockHttpHandler _mockHandler; + private readonly IFixture _fixture = new Fixture(); + + [TestInitialize] + public void initialize() + { + var client = new ContentstackClient(); + var contentstackResponse = MockResponse.CreateContentstackResponse("MockResponse.txt"); + _mockHandler = new MockHttpHandler(contentstackResponse); + client.ContentstackPipeline.ReplaceHandler(_mockHandler); + client.contentstackOptions.Authtoken = _fixture.Create(); + _stack = new Stack(client, _fixture.Create()); + } + + [TestMethod] + public void Initialize_Branch() + { + Branch branch = new Branch(_stack); + + Assert.IsNull(branch.Uid); + Assert.AreEqual("/stacks/branches", branch.resourcePath); + Assert.ThrowsException(() => branch.Fetch()); + Assert.ThrowsExceptionAsync(() => branch.FetchAsync()); + Assert.ThrowsException(() => branch.Delete()); + Assert.ThrowsExceptionAsync(() => branch.DeleteAsync()); + Assert.AreEqual(branch.Query().GetType(), typeof(Query)); + } + + [TestMethod] + public void Initialize_Branch_With_Uid() + { + string uid = _fixture.Create(); + Branch branch = new Branch(_stack, uid); + + Assert.AreEqual(uid, branch.Uid); + Assert.AreEqual($"/stacks/branches/{branch.Uid}", branch.resourcePath); + Assert.ThrowsException(() => branch.Create(_fixture.Create())); + Assert.ThrowsExceptionAsync(() => branch.CreateAsync(_fixture.Create())); + Assert.ThrowsException(() => branch.Query()); + } + + [TestMethod] + public void Should_Query_Branches_With_Params() + { + _stack.Branch().Query().Limit(2).Skip(2).IncludeCount().Find(); + + Assert.IsNotNull(_mockHandler.LastRequestUri); + StringAssert.Contains(_mockHandler.LastRequestUri.AbsoluteUri, "/v3/stacks/branches"); + StringAssert.Contains(_mockHandler.LastRequestUri.Query, "include_count=true"); + StringAssert.Contains(_mockHandler.LastRequestUri.Query, "limit=2"); + StringAssert.Contains(_mockHandler.LastRequestUri.Query, "skip=2"); + } + } +} + diff --git a/Contentstack.Management.Core/Models/Branch.cs b/Contentstack.Management.Core/Models/Branch.cs new file mode 100644 index 0000000..1fb91c9 --- /dev/null +++ b/Contentstack.Management.Core/Models/Branch.cs @@ -0,0 +1,127 @@ +using System.Threading.Tasks; +using Contentstack.Management.Core.Queryable; + +namespace Contentstack.Management.Core.Models +{ + /// + /// Branches allow you to isolate and manage "in-progress" work from your stable production work. + /// + public class Branch : BaseModel + { + internal Branch(Stack stack, string? uid = null) + : base(stack, "branch", uid) + { + resourcePath = uid == null ? "/stacks/branches" : "/stacks/branches/" + uid; + } + + /// + /// The Query on Branch fetches the list of all branches available in a stack. + /// + /// + ///

+        /// ContentstackClient client = new ContentstackClient("<AUTHTOKEN>", "<API_HOST>");
+        /// ContentstackResponse contentstackResponse = client.Stack("<API_KEY>").Branch().Query().Find();
+        /// 
+ ///
+ /// The + public Query Query() + { + ThrowIfUidNotEmpty(); + return new Query(stack, resourcePath); + } + + /// + /// The Create branch call creates a new branch in a stack. + /// + /// + ///

+        /// ContentstackClient client = new ContentstackClient("<AUTHTOKEN>", "<API_HOST>");
+        /// BranchModel model = new BranchModel() { Uid = "my-branch", Source = "main" };
+        /// ContentstackResponse contentstackResponse = client.Stack("<API_KEY>").Branch().Create(model);
+        /// 
+ ///
+ /// BranchModel for creating Branch. + /// The . + public override ContentstackResponse Create(BranchModel model, ParameterCollection? collection = null) + { + return base.Create(model, collection); + } + + /// + /// The Create branch call creates a new branch in a stack. + /// + /// + ///

+        /// ContentstackClient client = new ContentstackClient("<AUTHTOKEN>", "<API_HOST>");
+        /// BranchModel model = new BranchModel() { Uid = "my-branch", Source = "main" };
+        /// ContentstackResponse contentstackResponse = await client.Stack("<API_KEY>").Branch().CreateAsync(model);
+        /// 
+ ///
+ /// BranchModel for creating Branch. + /// The Task. + public override Task CreateAsync(BranchModel model, ParameterCollection? collection = null) + { + return base.CreateAsync(model, collection); + } + + /// + /// The Fetch branch call returns information about a specific branch of a stack. + /// + /// + ///

+        /// ContentstackClient client = new ContentstackClient("<AUTHTOKEN>", "<API_HOST>");
+        /// ContentstackResponse contentstackResponse = client.Stack("<API_KEY>").Branch("<BRANCH_UID>").Fetch();
+        /// 
+ ///
+ /// The . + public override ContentstackResponse Fetch(ParameterCollection? collection = null) + { + return base.Fetch(collection); + } + + /// + /// The Fetch branch call returns information about a specific branch of a stack. + /// + /// + ///

+        /// ContentstackClient client = new ContentstackClient("<AUTHTOKEN>", "<API_HOST>");
+        /// ContentstackResponse contentstackResponse = await client.Stack("<API_KEY>").Branch("<BRANCH_UID>").FetchAsync();
+        /// 
+ ///
+ /// The Task. + public override Task FetchAsync(ParameterCollection? collection = null) + { + return base.FetchAsync(collection); + } + + /// + /// The Delete branch call deletes a specific branch of a stack. + /// + /// + ///

+        /// ContentstackClient client = new ContentstackClient("<AUTHTOKEN>", "<API_HOST>");
+        /// ContentstackResponse contentstackResponse = client.Stack("<API_KEY>").Branch("<BRANCH_UID>").Delete();
+        /// 
+ ///
+ /// The . + public override ContentstackResponse Delete(ParameterCollection? collection = null) + { + return base.Delete(collection); + } + + /// + /// The Delete branch call deletes a specific branch of a stack. + /// + /// + ///

+        /// ContentstackClient client = new ContentstackClient("<AUTHTOKEN>", "<API_HOST>");
+        /// ContentstackResponse contentstackResponse = await client.Stack("<API_KEY>").Branch("<BRANCH_UID>").DeleteAsync();
+        /// 
+ ///
+ /// The Task. + public override Task DeleteAsync(ParameterCollection? collection = null) + { + return base.DeleteAsync(collection); + } + } +} diff --git a/Contentstack.Management.Core/Models/BranchModel.cs b/Contentstack.Management.Core/Models/BranchModel.cs new file mode 100644 index 0000000..3d95542 --- /dev/null +++ b/Contentstack.Management.Core/Models/BranchModel.cs @@ -0,0 +1,36 @@ +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace Contentstack.Management.Core.Models +{ + public class BranchAliasModel + { + [JsonPropertyName("uid")] + public string? Uid { get; set; } + } + + public class BranchModel + { + [JsonPropertyName("uid")] + public string? Uid { get; set; } + + [JsonPropertyName("source")] + public string? Source { get; set; } + + [JsonPropertyName("created_by")] + public string? CreatedBy { get; set; } + + [JsonPropertyName("updated_by")] + public string? UpdatedBy { get; set; } + + [JsonPropertyName("created_at")] + public string? CreatedAt { get; set; } + + [JsonPropertyName("updated_at")] + public string? UpdatedAt { get; set; } + + [JsonPropertyName("alias")] + public List? Alias { get; set; } + } +} + diff --git a/Contentstack.Management.Core/Models/Stack.cs b/Contentstack.Management.Core/Models/Stack.cs index 21ddf63..0bdab97 100644 --- a/Contentstack.Management.Core/Models/Stack.cs +++ b/Contentstack.Management.Core/Models/Stack.cs @@ -746,8 +746,39 @@ public Environment Environment(string? uid = null) return new Environment(this, uid); } + /// + /// Branches allow you to isolate and easily manage your “in-progress” work from your stable, live work. + /// + /// Optional, branch uid. + /// + ///

+        /// ContentstackClient client = new ContentstackClient("", "");
+        /// Stack stack = client.Stack("");
+        /// ContentstackResponse response = stack.Branch().Query().Limit(2).Skip(2).Find();
+        /// 
+ ///
+ /// The + public Branch Branch(string? uid = null) + { + ThrowIfNotLoggedIn(); + ThrowIfAPIKeyEmpty(); + + return new Branch(this, uid); + } + /* /// + /// Branches allow you to isolate and easily manage your “in-progress” work from your stable work. + /// + /// Optional, branch uid. + /// + ///

+        /// ContentstackClient client = new ContentstackClient("", "");
+        /// Stack stack = client.Stack("");
+        /// ContentstackResponse response = stack.Branch().Query().Limit(2).Skip(2).Find();
+        /// 
+ ///
+ /// /// You can use to authenticate Content Delivery API (CDA) requests and retrieve the published content of an environment. /// /// Optional, delivery token uid. diff --git a/Contentstack.Management.Core/Models/WebhookModel.cs b/Contentstack.Management.Core/Models/WebhookModel.cs index 5c419ca..3ae20be 100644 --- a/Contentstack.Management.Core/Models/WebhookModel.cs +++ b/Contentstack.Management.Core/Models/WebhookModel.cs @@ -5,15 +5,15 @@ namespace Contentstack.Management.Core.Models public class WebhookModel { [JsonPropertyName("name")] - public string Name { get; set; } + public string? Name { get; set; } [JsonPropertyName("destinations")] - public List destinations { get; set; } + public List? destinations { get; set; } [JsonPropertyName("channels")] - public List Channels { get; set; } + public List? Channels { get; set; } [JsonPropertyName("branches")] - public List Branches { get; set; } + public List? Branches { get; set; } [JsonPropertyName("retry_policy")] - public string RetryPolicy { get; set; } + public string? RetryPolicy { get; set; } [JsonPropertyName("disabled")] public bool Disabled { get; set; } = false; [JsonPropertyName("concise_payload")] @@ -23,12 +23,12 @@ public class WebhookModel public class WebhookTarget { [JsonPropertyName("target_url")] - public string TargetUrl { get; set; } + public string? TargetUrl { get; set; } [JsonPropertyName("http_basic_auth")] - public string HttpBasicAuth { get; set; } + public string? HttpBasicAuth { get; set; } [JsonPropertyName("http_basic_password")] - public string HttpBasicPassword { get; set; } + public string? HttpBasicPassword { get; set; } [JsonPropertyName("custom_header")] - public List> CustomHeader { get; set; } + public List>? CustomHeader { get; set; } } } diff --git a/Contentstack.Management.Core/Models/WorkflowModel.cs b/Contentstack.Management.Core/Models/WorkflowModel.cs index 1eeef42..e5d0fd8 100644 --- a/Contentstack.Management.Core/Models/WorkflowModel.cs +++ b/Contentstack.Management.Core/Models/WorkflowModel.cs @@ -5,30 +5,30 @@ namespace Contentstack.Management.Core.Models public class WorkflowModel { [JsonPropertyName("name")] - public string Name { get; set; } + public string? Name { get; set; } [JsonPropertyName("enabled")] public bool Enabled { get; set; } = true; [JsonPropertyName("branches")] - public List Branches { get; set; } + public List? Branches { get; set; } [JsonPropertyName("content_types")] - public List ContentTypes { get; set; } + public List? ContentTypes { get; set; } [JsonPropertyName("admin_users")] - public Dictionary AdminUsers { get; set; } + public Dictionary? AdminUsers { get; set; } [JsonPropertyName("workflow_stages")] - public List WorkflowStages { get; set; } + public List? WorkflowStages { get; set; } } public class WorkflowStage { [JsonPropertyName("uid")] - public string Uid { get; set; } + public string? Uid { get; set; } [JsonPropertyName("color")] - public string Color { get; set; } + public string? Color { get; set; } [JsonPropertyName("SYS_ACL")] - public Dictionary SystemACL { get; set; } + public Dictionary? SystemACL { get; set; } [JsonPropertyName("next_available_stages")] - public List NextAvailableStages { get; set; } + public List? NextAvailableStages { get; set; } [JsonPropertyName("allStages")] public bool AllStages { get; set; } = true; [JsonPropertyName("allUsers")] @@ -38,8 +38,8 @@ public class WorkflowStage [JsonPropertyName("specificUsers")] public bool SpecificUsers { get; set; } = false; [JsonPropertyName("entry_lock")] - public string EntryLock { get; set; } + public string? EntryLock { get; set; } [JsonPropertyName("name")] - public string Name { get; set; } + public string? Name { get; set; } } } From 92fffdd07dfc4a7e26bc2a72713c5dda8accdf4d Mon Sep 17 00:00:00 2001 From: OMpawar-21 Date: Sun, 31 May 2026 22:57:53 +0530 Subject: [PATCH 2/2] feat: Added some fixes for test cases. --- .../Contentstack005_BranchTest.cs | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/Contentstack.Management.Core.Tests/IntegrationTest/Contentstack005_BranchTest.cs b/Contentstack.Management.Core.Tests/IntegrationTest/Contentstack005_BranchTest.cs index 9fa4c6a..6f0722e 100644 --- a/Contentstack.Management.Core.Tests/IntegrationTest/Contentstack005_BranchTest.cs +++ b/Contentstack.Management.Core.Tests/IntegrationTest/Contentstack005_BranchTest.cs @@ -3,6 +3,7 @@ using System.Threading.Tasks; using Contentstack.Management.Core.Exceptions; using Contentstack.Management.Core.Models; +using Contentstack.Management.Core.Queryable; using Contentstack.Management.Core.Tests.Helpers; using Contentstack.Management.Core.Tests.Model; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -19,9 +20,9 @@ public class Contentstack005_BranchTest private static string _createdBranchUidAsync; private const string SourceBranch = "main"; - private const string TestBranchUid = "dotnet-sdk-int-test-branch"; - private const string TestBranchUidAsync = "dotnet-sdk-int-test-branch-async"; - private const string NonExistentBranchUid = "blt00000000nonexistent00"; + private const string TestBranchUid = "dotnet_int_br"; // max 15 chars, alphanumeric+underscore + private const string TestBranchUidAsync = "dotnet_int_br_a"; // max 15 chars, alphanumeric+underscore + private const string NonExistentBranchUid = "dotnet_no_br"; [ClassInitialize] public static void ClassInitialize(TestContext context) @@ -48,7 +49,9 @@ public static void ClassCleanup() private static void TryDeleteBranch(string uid) { if (string.IsNullOrEmpty(uid)) return; - try { _stack.Branch(uid).Delete(); } catch { } + var force = new ParameterCollection(); + force.Add("force", true); + try { _stack.Branch(uid).Delete(force); } catch { } } // ---- Query tests ------------------------------------------------------- @@ -230,7 +233,9 @@ public void Test010_Should_Delete_Branch() Assert.Inconclusive("Test004 did not create branch — skipping."); try { - ContentstackResponse response = _stack.Branch(_createdBranchUid).Delete(); + var force = new ParameterCollection(); + force.Add("force", true); + ContentstackResponse response = _stack.Branch(_createdBranchUid).Delete(force); var json = response.OpenJsonObjectResponse(); AssertLogger.IsNotNull(json, "response"); _createdBranchUid = null; @@ -250,7 +255,9 @@ public async Task Test011_Should_Delete_Branch_Async() Assert.Inconclusive("Test005 did not create branch — skipping."); try { - ContentstackResponse response = await _stack.Branch(_createdBranchUidAsync).DeleteAsync(); + var force = new ParameterCollection(); + force.Add("force", true); + ContentstackResponse response = await _stack.Branch(_createdBranchUidAsync).DeleteAsync(force); var json = response.OpenJsonObjectResponse(); AssertLogger.IsNotNull(json, "response"); _createdBranchUidAsync = null; @@ -399,7 +406,7 @@ public void Test023_Should_Fail_Create_With_Invalid_Source_Branch() TestOutputLogger.LogContext("TestScenario", "Branch_Create_InvalidSource_Sync"); var model = new BranchModel { - Uid = "dotnet-sdk-invalid-src-test", + Uid = "dotnet_inv_src", Source = "non_existent_source_branch_99999" }; try @@ -420,7 +427,7 @@ public async Task Test024_Should_Fail_CreateAsync_With_Invalid_Source_Branch() TestOutputLogger.LogContext("TestScenario", "Branch_Create_InvalidSource_Async"); var model = new BranchModel { - Uid = "dotnet-sdk-invalid-src-async", + Uid = "dotnet_inv_a", Source = "non_existent_source_branch_88888" }; try