Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## [v1.1.0](https://github.com/contentstack/contentstack-management-dotnet/tree/v1.1.0)(2026-07-13)

- **New**
- **Branch override for Entry Variants**: `Entry.Variant(uid?, branchUid?)` accepts an optional `branchUid` so a single variant call (`Find`, `Create`, `Update`, `Fetch`, `Delete`) can target a branch other than the `Stack`'s configured one, by overriding the `branch` request header. Passing `null`/whitespace falls back to the Stack's own branch.
- **Publish/Unpublish for Entry Variants**: `EntryVariant.Publish`/`PublishAsync` and `EntryVariant.Unpublish`/`UnpublishAsync`, matching the same `PublishUnpublishDetails` contract as `Entry.Publish`/`Unpublish`, and respecting the same branch override.

## [v1.0.0](https://github.com/contentstack/contentstack-management-dotnet/tree/v1.0.0)(2026-07-13)

- **Breaking Change**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3481,6 +3481,194 @@ await AssertLogger.ThrowsContentstackErrorAsync(async () =>
}

#endregion

#region L — Branch Override Tests

private const string BranchOverrideUid = "dotnet_variant_br";

private void TryDeleteBranch(string uid)
{
if (string.IsNullOrEmpty(uid)) return;
var force = new global::Contentstack.Management.Core.Queryable.ParameterCollection();
force.Add("force", true);
try { _stack.Branch(uid).Delete(force); } catch { }
}

[TestMethod]
[DoNotParallelize]
public void Test083_Should_Create_Branch_For_Variant_Override_Tests()
{
if (string.IsNullOrEmpty(_entryUid) || string.IsNullOrEmpty(_variantUid))
{
Assert.Inconclusive("Setup not completed. Ensure Test001 runs first.");
return;
}

TestOutputLogger.LogContext("TestScenario", "VariantBranchOverride_Setup");

TryDeleteBranch(BranchOverrideUid);

try
{
var model = new BranchModel { Uid = BranchOverrideUid, Source = "main" };
ContentstackResponse response = _stack.Branch().Create(model);
AssertLogger.IsNotNull(response.OpenJsonObjectResponse(), "response");
}
catch (ContentstackErrorException cex) when (
cex.StatusCode == HttpStatusCode.Conflict ||
cex.StatusCode == (HttpStatusCode)422)
{
// Branch already exists from a previous run — that is fine for our purposes.
Console.WriteLine($"Branch '{BranchOverrideUid}' already exists (HTTP {(int)cex.StatusCode}); continuing.");
}
catch (Exception ex)
{
Assert.Inconclusive("Could not create a branch for branch-override tests (branching may not be enabled on this stack): " + ex.Message);
}
}

[TestMethod]
[DoNotParallelize]
public void Test084_Should_Fetch_Variant_On_Explicit_Branch()
{
if (string.IsNullOrEmpty(_entryUid) || string.IsNullOrEmpty(_variantUid))
{
Assert.Inconclusive("Setup not completed. Ensure Test001 runs first.");
return;
}

TestOutputLogger.LogContext("TestScenario", "VariantBranchOverride_Fetch");

try
{
// The variant/entry may not have been synced onto the new branch yet, so we only
// assert that the SDK successfully issues the request with the branch override —
// not that the API necessarily has matching content on that branch.
var response = _stack.ContentType(_contentTypeUid).Entry(_entryUid).Variant(_variantUid, BranchOverrideUid).Fetch();
Console.WriteLine("Fetch on explicit branch response: " + response.OpenResponse());
AssertLogger.IsTrue(
response.IsSuccessStatusCode,
"Expected a 2xx response when fetching a variant with a valid branch override (errors would have thrown ContentstackErrorException)",
"FetchVariantOnExplicitBranch");
Comment thread
OMpawar-21 marked this conversation as resolved.
}
catch (ContentstackErrorException cex)
{
Console.WriteLine("Fetch on explicit branch failed (acceptable if content hasn't synced to the branch yet): " + cex.Message);
}
Comment thread
OMpawar-21 marked this conversation as resolved.
}

[TestMethod]
[DoNotParallelize]
public async Task Test085_Should_Fetch_Variant_On_Explicit_Branch_Async()
{
if (string.IsNullOrEmpty(_entryUid) || string.IsNullOrEmpty(_variantUid))
{
Assert.Inconclusive("Setup not completed. Ensure Test001 runs first.");
return;
}

TestOutputLogger.LogContext("TestScenario", "VariantBranchOverride_FetchAsync");

try
{
var response = await _stack.ContentType(_contentTypeUid).Entry(_entryUid).Variant(_variantUid, BranchOverrideUid).FetchAsync();
Console.WriteLine("FetchAsync on explicit branch response: " + response.OpenResponse());
AssertLogger.IsTrue(
response.IsSuccessStatusCode,
"Expected a 2xx response when fetching a variant with a valid branch override (errors would have thrown ContentstackErrorException)",
"FetchVariantOnExplicitBranchAsync");
Comment thread
OMpawar-21 marked this conversation as resolved.
}
catch (ContentstackErrorException cex)
{
Console.WriteLine("FetchAsync on explicit branch failed (acceptable if content hasn't synced to the branch yet): " + cex.Message);
}
Comment thread
OMpawar-21 marked this conversation as resolved.
}

[TestMethod]
[DoNotParallelize]
public async Task Test086_Should_Fail_To_Fetch_Variant_On_Invalid_Branch()
{
if (string.IsNullOrEmpty(_entryUid) || string.IsNullOrEmpty(_variantUid))
{
Assert.Inconclusive("Setup not completed. Ensure Test001 runs first.");
return;
}

TestOutputLogger.LogContext("TestScenario", "VariantBranchOverride_InvalidBranch");

await AssertLogger.ThrowsContentstackErrorAsync(async () =>
{
var response = await _stack.ContentType(_contentTypeUid).Entry(_entryUid).Variant(_variantUid, "definitely_invalid_branch").FetchAsync();
if (!response.IsSuccessStatusCode)
{
throw new ContentstackErrorException
{
StatusCode = response.StatusCode,
ErrorMessage = "Invalid branch UID"
};
}
}, "FetchVariantOnInvalidBranch", HttpStatusCode.NotFound, (HttpStatusCode)422, HttpStatusCode.BadRequest, HttpStatusCode.Unauthorized, HttpStatusCode.Forbidden);
}

[TestMethod]
[DoNotParallelize]
public async Task Test087_Should_Fallback_To_Stack_Branch_When_BranchUid_Is_Blank()
{
if (string.IsNullOrEmpty(_entryUid) || string.IsNullOrEmpty(_variantUid))
{
Assert.Inconclusive("Setup not completed. Ensure Test001 runs first.");
return;
}

TestOutputLogger.LogContext("TestScenario", "VariantBranchOverride_BlankFallback");

// A blank/whitespace branchUid must behave identically to omitting it entirely —
// i.e. fall back to the Stack's configured branch (main, in these tests).
var withoutOverride = await _stack.ContentType(_contentTypeUid).Entry(_entryUid).Variant().FindAsync();
var withBlankOverride = await _stack.ContentType(_contentTypeUid).Entry(_entryUid).Variant(branchUid: " ").FindAsync();

Assert.AreEqual(withoutOverride.IsSuccessStatusCode, withBlankOverride.IsSuccessStatusCode,
"A blank branchUid should fall back to the Stack's branch, matching the no-override request");
}

[TestMethod]
[DoNotParallelize]
public async Task Test088_Should_Publish_Variant_With_Explicit_Branch_Override()
{
if (string.IsNullOrEmpty(_entryUid) || string.IsNullOrEmpty(_variantUid))
{
Assert.Inconclusive("Setup not completed. Ensure Test001 runs first.");
return;
}

TestOutputLogger.LogContext("TestScenario", "VariantBranchOverride_Publish");

var publishDetails = new PublishUnpublishDetails
{
Locales = new List<string> { "en-us" },
Environments = new List<string> { "development" }
};

try
{
var response = await _stack.ContentType(_contentTypeUid).Entry(_entryUid).Variant(_variantUid, BranchOverrideUid).PublishAsync(publishDetails, "en-us");
Console.WriteLine("Publish with branch override response: " + response.OpenResponse());
}
catch (Exception ex)
{
Console.WriteLine("Publish with branch override failed (often due to missing 'development' environment on the branch). Continuing. Exception: " + ex.Message);
}
Comment thread
Copilot marked this conversation as resolved.
}

[TestMethod]
[DoNotParallelize]
public void Test089_Should_Cleanup_Branch_For_Variant_Override_Tests()
{
TestOutputLogger.LogContext("TestScenario", "VariantBranchOverride_Cleanup");
TryDeleteBranch(BranchOverrideUid);
}

#endregion
}

/// <summary>
Expand Down
165 changes: 164 additions & 1 deletion Contentstack.Management.Core.Unit.Tests/Models/EntryVariantTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,15 @@ public class EntryVariantTest
private Stack _stack;
private readonly IFixture _fixture = new Fixture();
private ContentstackResponse _contentstackResponse;
private MockHttpHandler _mockHttpHandler;

[TestInitialize]
public void initialize()
{
var client = new ContentstackClient();
_contentstackResponse = MockResponse.CreateContentstackResponse("MockResponse.txt");
client.ContentstackPipeline.ReplaceHandler(new MockHttpHandler(_contentstackResponse));
_mockHttpHandler = new MockHttpHandler(_contentstackResponse);
client.ContentstackPipeline.ReplaceHandler(_mockHttpHandler);
client.contentstackOptions.Authtoken = _fixture.Create<string>();
_stack = new Stack(client, _fixture.Create<string>());
}
Expand Down Expand Up @@ -147,5 +149,166 @@ public void Should_Delete_EntryVariant()

Assert.AreEqual(_contentstackResponse.OpenResponse(), response.OpenResponse());
}

[TestMethod]
public void Initialize_EntryVariant_With_BranchUid()
{
var ctUid = _fixture.Create<string>();
var entryUid = _fixture.Create<string>();
var branchUid = _fixture.Create<string>();

EntryVariant variant = new EntryVariant(_stack, ctUid, entryUid, branchUid: branchUid);

Assert.AreEqual(branchUid, variant.branchUid);
}

[TestMethod]
public void Should_Override_Branch_Header_On_Find_When_BranchUid_Provided()
{
var ctUid = _fixture.Create<string>();
var entryUid = _fixture.Create<string>();
var branchUid = _fixture.Create<string>();
EntryVariant variant = new EntryVariant(_stack, ctUid, entryUid, branchUid: branchUid);

variant.Find();

Assert.AreEqual(branchUid, _mockHttpHandler.LastRequestHeaders["branch"]);
}

[TestMethod]
public void Should_Override_Branch_Header_On_Create_When_BranchUid_Provided()
{
var ctUid = _fixture.Create<string>();
var entryUid = _fixture.Create<string>();
var uid = _fixture.Create<string>();
var branchUid = _fixture.Create<string>();
EntryVariant variant = new EntryVariant(_stack, ctUid, entryUid, uid, branchUid);
var model = new { entry = new { banner_color = "Navy Blue" } };

variant.Create(model);

Assert.AreEqual(branchUid, _mockHttpHandler.LastRequestHeaders["branch"]);
}

[TestMethod]
public void Should_Override_Branch_Header_On_Fetch_When_BranchUid_Provided()
{
var ctUid = _fixture.Create<string>();
var entryUid = _fixture.Create<string>();
var uid = _fixture.Create<string>();
var branchUid = _fixture.Create<string>();
EntryVariant variant = new EntryVariant(_stack, ctUid, entryUid, uid, branchUid);

variant.Fetch();

Assert.AreEqual(branchUid, _mockHttpHandler.LastRequestHeaders["branch"]);
}

[TestMethod]
public void Should_Override_Branch_Header_On_Delete_When_BranchUid_Provided()
{
var ctUid = _fixture.Create<string>();
var entryUid = _fixture.Create<string>();
var uid = _fixture.Create<string>();
var branchUid = _fixture.Create<string>();
EntryVariant variant = new EntryVariant(_stack, ctUid, entryUid, uid, branchUid);

variant.Delete();

Assert.AreEqual(branchUid, _mockHttpHandler.LastRequestHeaders["branch"]);
}

[TestMethod]
public void Should_Fallback_To_Stack_Branch_When_BranchUid_Is_Empty()
{
var stackBranchUid = _fixture.Create<string>();
var client = new ContentstackClient();
var mockHttpHandler = new MockHttpHandler(_contentstackResponse);
client.ContentstackPipeline.ReplaceHandler(mockHttpHandler);
client.contentstackOptions.Authtoken = _fixture.Create<string>();
var stack = new Stack(client, _fixture.Create<string>(), branchUid: stackBranchUid);

var ctUid = _fixture.Create<string>();
var entryUid = _fixture.Create<string>();
EntryVariant variant = new EntryVariant(stack, ctUid, entryUid, branchUid: " ");

variant.Find();

Assert.AreEqual(stackBranchUid, mockHttpHandler.LastRequestHeaders["branch"]);
}

[TestMethod]
public void Should_Publish_EntryVariant()
{
var ctUid = _fixture.Create<string>();
var entryUid = _fixture.Create<string>();
var uid = _fixture.Create<string>();
EntryVariant variant = new EntryVariant(_stack, ctUid, entryUid, uid);
var details = new PublishUnpublishDetails { Locales = new System.Collections.Generic.List<string> { "en-us" }, Version = 1 };

ContentstackResponse response = variant.Publish(details);

Assert.AreEqual(_contentstackResponse.OpenResponse(), response.OpenResponse());
Assert.IsTrue(details.Variants.Exists(v => v.Uid == uid));
}

[TestMethod]
public async System.Threading.Tasks.Task Should_Publish_EntryVariant_Async()
{
var ctUid = _fixture.Create<string>();
var entryUid = _fixture.Create<string>();
var uid = _fixture.Create<string>();
EntryVariant variant = new EntryVariant(_stack, ctUid, entryUid, uid);
var details = new PublishUnpublishDetails();

ContentstackResponse response = await variant.PublishAsync(details);

Assert.AreEqual(_contentstackResponse.OpenResponse(), response.OpenResponse());
Assert.IsTrue(details.Variants.Exists(v => v.Uid == uid));
}

[TestMethod]
public void Should_Unpublish_EntryVariant()
{
var ctUid = _fixture.Create<string>();
var entryUid = _fixture.Create<string>();
var uid = _fixture.Create<string>();
EntryVariant variant = new EntryVariant(_stack, ctUid, entryUid, uid);
var details = new PublishUnpublishDetails();

ContentstackResponse response = variant.Unpublish(details);

Assert.AreEqual(_contentstackResponse.OpenResponse(), response.OpenResponse());
Assert.IsTrue(details.Variants.Exists(v => v.Uid == uid));
}

[TestMethod]
public async System.Threading.Tasks.Task Should_Unpublish_EntryVariant_Async()
{
var ctUid = _fixture.Create<string>();
var entryUid = _fixture.Create<string>();
var uid = _fixture.Create<string>();
EntryVariant variant = new EntryVariant(_stack, ctUid, entryUid, uid);
var details = new PublishUnpublishDetails();

ContentstackResponse response = await variant.UnpublishAsync(details);

Assert.AreEqual(_contentstackResponse.OpenResponse(), response.OpenResponse());
Assert.IsTrue(details.Variants.Exists(v => v.Uid == uid));
}

[TestMethod]
public void Should_Override_Branch_Header_On_Publish_When_BranchUid_Provided()
{
var ctUid = _fixture.Create<string>();
var entryUid = _fixture.Create<string>();
var uid = _fixture.Create<string>();
var branchUid = _fixture.Create<string>();
EntryVariant variant = new EntryVariant(_stack, ctUid, entryUid, uid, branchUid);

variant.Publish(new PublishUnpublishDetails());

Assert.AreEqual(branchUid, _mockHttpHandler.LastRequestHeaders["branch"]);
}
}
}
Loading
Loading