Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
023eb1e
Merge pull request #133 from contentstack/staging
harshithad0703 Feb 2, 2026
904fe3d
Merge pull request #134 from contentstack/main
harshithad0703 Feb 2, 2026
b5a3d77
Add AddQueryResource alternatives for bulk unpublish query params in …
OMpawar-21 Feb 25, 2026
a55088a
Add AddQueryResource alternatives for bulk unpublish query params in …
OMpawar-21 Feb 25, 2026
68132b9
feat(DX-3233): send bulk publish/unpublish flags as query params and …
OMpawar-21 Mar 2, 2026
4880712
Update Directory.Build.props
OMpawar-21 Mar 2, 2026
896c821
feat: bulk ops – add api_version 3.2 tests and robust status/error ha…
OMpawar-21 Mar 2, 2026
f731bfa
Update sca-scan.yml
dhavaljain999 Mar 3, 2026
8bf3133
Add workflow/publish-rule/env setup, bulk publish-unpublish tests (00…
OMpawar-21 Mar 4, 2026
2932c6c
feat(enhc/DX-3233) Improved the test case - Test004a_Should_Perform_B…
OMpawar-21 Mar 4, 2026
1161371
some small changes
OMpawar-21 Mar 6, 2026
999b55d
refactor(BulkOperationTest): improve test lifecycle, sync helpers, an…
OMpawar-21 Mar 9, 2026
d4ee350
refactor(BulkOperationTest): improve test lifecycle, sync helpers, an…
OMpawar-21 Mar 9, 2026
08e18f0
Merge pull request #136 from contentstack/enhc/DX-3233
OMpawar-21 Mar 10, 2026
130fbdd
feat: Add full Taxonomy and Terms API to .NET CMA SDK
OMpawar-21 Mar 10, 2026
5d8ebaa
Added the negative test flow in both integration and unit testcases.
OMpawar-21 Mar 10, 2026
c186af5
Merge pull request #140 from contentstack/main
cs-raj Mar 11, 2026
0c4c9c8
feat: taxonomy feature, test cases
OMpawar-21 Mar 11, 2026
52dc968
Merge pull request #138 from contentstack/enhc/DX-4914
OMpawar-21 Mar 11, 2026
58206b6
feat: Normal report push
OMpawar-21 Mar 13, 2026
5e0ad6d
feat: fixed taxonomy skipped test cases
OMpawar-21 Mar 13, 2026
ef50358
feat: Integration Test Report Generator
OMpawar-21 Mar 13, 2026
3aa868e
feat(5433): Retrieve auth token exclusively via Login API feat: Retri…
OMpawar-21 Mar 16, 2026
dc85c14
fix: resolve Snyk CWE-798 hardcoded-credentials false positive in Tes…
OMpawar-21 Mar 18, 2026
9752bcb
Revert "fix: resolve Snyk CWE-798 hardcoded-credentials false positiv…
OMpawar-21 Mar 18, 2026
51ae63f
feat: Added the negative path test cases for the terms support
OMpawar-21 Mar 18, 2026
fe30677
Revert "feat: Added the negative path test cases for the terms support"
OMpawar-21 Mar 18, 2026
7cee175
feat: Added Negative Path related test cases in terms support.
OMpawar-21 Mar 18, 2026
1ed7ec2
feat: Content types - field deserialization, models, and integration …
OMpawar-21 Mar 20, 2026
508fafa
Change summary: Snyk is now invoked with --json-file-output=snyk.json…
OMpawar-21 Mar 23, 2026
b8c74f9
Revert "Change summary: Snyk is now invoked with --json-file-output=s…
OMpawar-21 Mar 23, 2026
90989d0
fix: resolve Snyk Core parse error and ReDoS in test project dependen…
OMpawar-21 Mar 23, 2026
e2519f5
feat: Created synk.json report
OMpawar-21 Mar 23, 2026
f47a1b8
Revert "feat: Created synk.json report"
OMpawar-21 Mar 23, 2026
96475e4
feat: Created synk.json report
OMpawar-21 Mar 23, 2026
dd292c4
Merge pull request #141 from contentstack/enhc/DX-3908-enhanced-report
OMpawar-21 Mar 23, 2026
ea67577
fix: resolve Snyk Core parse error and ReDoS in test project dependen…
OMpawar-21 Mar 23, 2026
4d059bf
test(integration): add 012b negative cases for taxonomy field, extens…
OMpawar-21 Mar 24, 2026
4afa7ec
fix: resolve Snyk Core parse error and ReDoS in test project dependen…
OMpawar-21 Mar 24, 2026
625fcc2
fix: build error
OMpawar-21 Mar 24, 2026
9e883fb
fix: resolve Snyk Core parse error and ReDoS in test project dependen…
OMpawar-21 Mar 24, 2026
7a3026b
Merge branch 'development' into enhc/DX-5433
OMpawar-21 Mar 24, 2026
b4da510
Merge pull request #142 from contentstack/enhc/DX-5433
OMpawar-21 Mar 24, 2026
3b40b75
Merge branch 'development' into enhc/DX-5434
OMpawar-21 Mar 25, 2026
393a8ef
Merge pull request #144 from contentstack/enhc/DX-5434
OMpawar-21 Mar 25, 2026
46a0d84
feat: add asset API tests for locale query params
OMpawar-21 Mar 25, 2026
83ea7a3
Merge pull request #145 from contentstack/enhc/DX-4454
OMpawar-21 Mar 25, 2026
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
3 changes: 3 additions & 0 deletions .github/workflows/sca-scan.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,6 @@ jobs:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
with:
args: --file=Contentstack.Management.Core/obj/project.assets.json --fail-on=all
json: true
continue-on-error: true
- uses: contentstack/sca-policy@main
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,7 @@ packages/
*/mono**
*/appSettings.json
api_referece/*
.sonarqube/
.sonarqube/
*.html
*.cobertura.xml
integration-test-report_*.html
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -1,49 +1,55 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>

<IsPackable>false</IsPackable>
<ReleaseVersion>$(Version)</ReleaseVersion>

<SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile>../CSManagementSDK.snk</AssemblyOriginatorKeyFile>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.13.0" />
<PackageReference Include="MSTest.TestAdapter" Version="3.8.2" />
<PackageReference Include="MSTest.TestFramework" Version="3.8.2" />
<PackageReference Include="coverlet.collector" Version="6.0.4"><IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="9.0.2" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="9.0.2" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="9.0.2" />
<PackageReference Include="AutoFixture" Version="4.18.1" />
<PackageReference Include="AutoFixture.AutoMoq" Version="4.18.1" />
<PackageReference Include="Moq" Version="4.20.72" />
<PackageReference Include="Microsoft.AspNetCore.Http" Version="2.3.0" />
</ItemGroup>

<ItemGroup>
<Folder Include="IntegrationTest\" />
<Folder Include="Model\" />
<Folder Include="Mock\" />
</ItemGroup>

<ItemGroup>
<EmbeddedResource Include="Mock\*.json" />
</ItemGroup>
<ItemGroup>
<Content Include="appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<None Remove="Microsoft.AspNetCore.Http" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Contentstack.Management.Core\contentstack.management.core.csproj" />
</ItemGroup>
</Project>
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>

<IsPackable>false</IsPackable>
<ReleaseVersion>$(Version)</ReleaseVersion>

<SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile>../CSManagementSDK.snk</AssemblyOriginatorKeyFile>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.13.0" />
<PackageReference Include="MSTest.TestAdapter" Version="3.8.2" />
<PackageReference Include="MSTest.TestFramework" Version="3.8.2" />
<PackageReference Include="coverlet.collector" Version="6.0.4"><IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="9.0.2" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="9.0.2" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="9.0.2" />
<PackageReference Include="AutoFixture" Version="4.18.1" />
<PackageReference Include="AutoFixture.AutoMoq" Version="4.18.1" />
<PackageReference Include="Moq" Version="4.20.72" />
<PackageReference Include="Microsoft.AspNetCore.Http" Version="2.3.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" />
</ItemGroup>

<ItemGroup>
<Folder Include="Helpers\" />
<Folder Include="IntegrationTest\" />
<Folder Include="Model\" />
<Folder Include="Mock\" />
</ItemGroup>

<ItemGroup>
<EmbeddedResource Include="Mock\*.json" />
</ItemGroup>

<ItemGroup>
<Content Include="appSettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>

<ItemGroup>
<None Remove="Microsoft.AspNetCore.Http" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Contentstack.Management.Core\contentstack.management.core.csproj" />
</ItemGroup>
</Project>
30 changes: 19 additions & 11 deletions Contentstack.Management.Core.Tests/Contentstack.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
using System;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Reflection;
using Contentstack.Management.Core.Tests.Helpers;
using Contentstack.Management.Core.Tests.Model;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Options;
Expand All @@ -14,15 +16,6 @@ namespace Contentstack.Management.Core.Tests
{
public class Contentstack
{
private static readonly Lazy<ContentstackClient>
client =
new Lazy<ContentstackClient>(() =>
{
ContentstackClientOptions options = Config.GetSection("Contentstack").Get<ContentstackClientOptions>();
return new ContentstackClient(new OptionsWrapper<ContentstackClientOptions>(options));
});


private static readonly Lazy<IConfigurationRoot>
config =
new Lazy<IConfigurationRoot>(() =>
Expand All @@ -42,13 +35,28 @@ private static readonly Lazy<IConfigurationRoot>
return Config.GetSection("Contentstack:Organization").Get<OrganizationModel>();
});

public static ContentstackClient Client { get { return client.Value; } }
public static IConfigurationRoot Config{ get { return config.Value; } }
public static NetworkCredential Credential { get { return credential.Value; } }
public static OrganizationModel Organization { get { return organization.Value; } }

public static StackModel Stack { get; set; }

/// <summary>
/// Creates a new ContentstackClient, logs in via the Login API (never from config),
/// and returns the authenticated client. Callers are responsible for calling Logout()
/// when done.
/// </summary>
public static ContentstackClient CreateAuthenticatedClient()
{
ContentstackClientOptions options = Config.GetSection("Contentstack").Get<ContentstackClientOptions>();
options.Authtoken = null;
var handler = new LoggingHttpHandler();
var httpClient = new HttpClient(handler);
var client = new ContentstackClient(httpClient, options);
client.Login(Credential);
return client;
}

public static T serialize<T>(JsonSerializer serializer, string filePath)
{
string response = GetResourceText(filePath);
Expand Down
163 changes: 163 additions & 0 deletions Contentstack.Management.Core.Tests/Helpers/AssertLogger.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
using System;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using Contentstack.Management.Core.Exceptions;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace Contentstack.Management.Core.Tests.Helpers
{
public static class AssertLogger
{
public static void IsNotNull(object value, string name = "")
{
bool passed = value != null;
TestOutputLogger.LogAssertion($"IsNotNull({name})", "NotNull", value?.ToString() ?? "null", passed);
Assert.IsNotNull(value);
}

public static void IsNull(object value, string name = "")
{
bool passed = value == null;
TestOutputLogger.LogAssertion($"IsNull({name})", "null", value?.ToString() ?? "null", passed);
Assert.IsNull(value);
}

public static void AreEqual<T>(T expected, T actual, string name = "")
{
bool passed = Equals(expected, actual);
TestOutputLogger.LogAssertion($"AreEqual({name})", expected?.ToString() ?? "null", actual?.ToString() ?? "null", passed);
Assert.AreEqual(expected, actual);
}

public static void AreEqual<T>(T expected, T actual, string message, string name)
{
bool passed = Equals(expected, actual);
TestOutputLogger.LogAssertion($"AreEqual({name})", expected?.ToString() ?? "null", actual?.ToString() ?? "null", passed);
Assert.AreEqual(expected, actual, message);
}

public static void IsTrue(bool condition, string name = "")
{
TestOutputLogger.LogAssertion($"IsTrue({name})", "True", condition.ToString(), condition);
Assert.IsTrue(condition);
}

public static void IsTrue(bool condition, string message, string name)
{
TestOutputLogger.LogAssertion($"IsTrue({name})", "True", condition.ToString(), condition);
Assert.IsTrue(condition, message);
}

public static void IsFalse(bool condition, string name = "")
{
TestOutputLogger.LogAssertion($"IsFalse({name})", "False", condition.ToString(), !condition);
Assert.IsFalse(condition);
}

public static void IsFalse(bool condition, string message, string name)
{
TestOutputLogger.LogAssertion($"IsFalse({name})", "False", condition.ToString(), !condition);
Assert.IsFalse(condition, message);
}

public static void IsInstanceOfType(object value, Type expectedType, string name = "")
{
bool passed = value != null && expectedType.IsInstanceOfType(value);
TestOutputLogger.LogAssertion(
$"IsInstanceOfType({name})",
expectedType?.Name ?? "null",
value?.GetType()?.Name ?? "null",
passed);
Assert.IsInstanceOfType(value, expectedType);
}

public static T ThrowsException<T>(Action action, string name = "") where T : Exception
{
try
{
action();
TestOutputLogger.LogAssertion($"ThrowsException<{typeof(T).Name}>({name})", typeof(T).Name, "NoException", false);
throw new AssertFailedException($"Expected exception {typeof(T).Name} was not thrown.");
}
catch (T ex)
{
TestOutputLogger.LogAssertion($"ThrowsException<{typeof(T).Name}>({name})", typeof(T).Name, typeof(T).Name, true);
return ex;
}
catch (AssertFailedException)
{
throw;
}
catch (Exception ex)
{
TestOutputLogger.LogAssertion($"ThrowsException<{typeof(T).Name}>({name})", typeof(T).Name, ex.GetType().Name, false);
throw new AssertFailedException(
$"Expected exception {typeof(T).Name} but got {ex.GetType().Name}: {ex.Message}", ex);
}
}

public static void Fail(string message)
{
TestOutputLogger.LogAssertion("Fail", "N/A", message ?? "", false);
Assert.Fail(message);
}

public static void Fail(string message, params object[] parameters)
{
TestOutputLogger.LogAssertion("Fail", "N/A", message ?? "", false);
Assert.Fail(message, parameters);
}

public static void Inconclusive(string message)
{
TestOutputLogger.LogAssertion("Inconclusive", "N/A", message ?? "", false);
Assert.Inconclusive(message);
}

/// <summary>
/// Asserts a Contentstack API error with an HTTP status in the allowed set.
/// </summary>
public static ContentstackErrorException ThrowsContentstackError(Action action, string name, params HttpStatusCode[] acceptableStatuses)
{
var ex = ThrowsException<ContentstackErrorException>(action, name);
IsTrue(
acceptableStatuses.Contains(ex.StatusCode),
$"Expected one of [{string.Join(", ", acceptableStatuses)}] but was {ex.StatusCode}",
"statusCode");
return ex;
}

/// <summary>
/// Async variant: runs the task and expects <see cref="ContentstackErrorException"/> with an allowed status.
/// </summary>
public static async Task<ContentstackErrorException> ThrowsContentstackErrorAsync(Func<Task> action, string name, params HttpStatusCode[] acceptableStatuses)
{
try
{
await action();
TestOutputLogger.LogAssertion($"ThrowsContentstackErrorAsync({name})", "ContentstackErrorException", "NoException", false);
throw new AssertFailedException($"Expected exception ContentstackErrorException was not thrown.");
}
catch (ContentstackErrorException ex)
{
IsTrue(
acceptableStatuses.Contains(ex.StatusCode),
$"Expected one of [{string.Join(", ", acceptableStatuses)}] but was {ex.StatusCode}",
"statusCode");
TestOutputLogger.LogAssertion($"ThrowsContentstackErrorAsync({name})", nameof(ContentstackErrorException), ex.StatusCode.ToString(), true);
return ex;
}
catch (AssertFailedException)
{
throw;
}
catch (Exception ex)
{
TestOutputLogger.LogAssertion($"ThrowsContentstackErrorAsync({name})", nameof(ContentstackErrorException), ex.GetType().Name, false);
throw new AssertFailedException(
$"Expected exception ContentstackErrorException but got {ex.GetType().Name}: {ex.Message}", ex);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using Contentstack.Management.Core.Models;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace Contentstack.Management.Core.Tests.Helpers
{
/// <summary>
/// Loads embedded content-type JSON and assigns unique UIDs/titles for disposable integration tests.
/// </summary>
public static class ContentTypeFixtureLoader
{
public static ContentModelling LoadFromMock(JsonSerializer serializer, string embeddedFileName, string uidSuffix)
{
var text = Contentstack.GetResourceText(embeddedFileName);
var jo = JObject.Parse(text);
var baseUid = jo["uid"]?.Value<string>() ?? "ct";
jo["uid"] = $"{baseUid}_{uidSuffix}";
var title = jo["title"]?.Value<string>() ?? "CT";
jo["title"] = $"{title} {uidSuffix}";
return jo.ToObject<ContentModelling>(serializer);
}
}
}
Loading
Loading