Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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,187 @@ 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 (Exception ex)
{
Assert.Inconclusive("Could not create a branch for branch-override tests (may already exist or stack may not support branching): " + 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 on lines +3542 to +3545
}
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 on lines +3569 to +3572
}
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