Skip to content

Commit 1fa79f0

Browse files
Merge pull request #147 from contentstack/staging
DX | 30-03-2026 | Release
2 parents f731bfa + 08b1f37 commit 1fa79f0

File tree

58 files changed

+8504
-881
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

58 files changed

+8504
-881
lines changed

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,7 @@ packages/
2424
*/mono**
2525
*/appSettings.json
2626
api_referece/*
27-
.sonarqube/
27+
.sonarqube/
28+
*.html
29+
*.cobertura.xml
30+
integration-test-report_*.html

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# Changelog
22

3+
## [v0.7.0](https://github.com/contentstack/contentstack-management-dotnet/tree/v0.7.0)
4+
- Feat
5+
- **Bulk publish/unpublish: query parameters (DX-3233)**
6+
- `skip_workflow_stage_check` and `approvals` are now sent as query parameters instead of headers for bulk publish and bulk unpublish
7+
- Unit tests updated to assert on `QueryResources` for these flags (BulkPublishServiceTest, BulkUnpublishServiceTest, BulkOperationServicesTest)
8+
- 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
9+
310
## [v0.6.1](https://github.com/contentstack/contentstack-management-dotnet/tree/v0.6.1) (2026-02-02)
411
- Fix
512
- Release DELETE request no longer includes Content-Type header to comply with API requirements
Lines changed: 55 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,55 @@
1-
<Project Sdk="Microsoft.NET.Sdk">
2-
3-
<PropertyGroup>
4-
<TargetFramework>net7.0</TargetFramework>
5-
6-
<IsPackable>false</IsPackable>
7-
<ReleaseVersion>$(Version)</ReleaseVersion>
8-
9-
<SignAssembly>true</SignAssembly>
10-
<AssemblyOriginatorKeyFile>../CSManagementSDK.snk</AssemblyOriginatorKeyFile>
11-
</PropertyGroup>
12-
13-
<ItemGroup>
14-
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.13.0" />
15-
<PackageReference Include="MSTest.TestAdapter" Version="3.8.2" />
16-
<PackageReference Include="MSTest.TestFramework" Version="3.8.2" />
17-
<PackageReference Include="coverlet.collector" Version="6.0.4"><IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
18-
<PrivateAssets>all</PrivateAssets>
19-
</PackageReference>
20-
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="9.0.2" />
21-
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="9.0.2" />
22-
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="9.0.2" />
23-
<PackageReference Include="AutoFixture" Version="4.18.1" />
24-
<PackageReference Include="AutoFixture.AutoMoq" Version="4.18.1" />
25-
<PackageReference Include="Moq" Version="4.20.72" />
26-
<PackageReference Include="Microsoft.AspNetCore.Http" Version="2.3.0" />
27-
</ItemGroup>
28-
29-
<ItemGroup>
30-
<Folder Include="IntegrationTest\" />
31-
<Folder Include="Model\" />
32-
<Folder Include="Mock\" />
33-
</ItemGroup>
34-
35-
<ItemGroup>
36-
<EmbeddedResource Include="Mock\*.json" />
37-
</ItemGroup>
38-
<ItemGroup>
39-
<Content Include="appsettings.json">
40-
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
41-
</Content>
42-
</ItemGroup>
43-
<ItemGroup>
44-
<None Remove="Microsoft.AspNetCore.Http" />
45-
</ItemGroup>
46-
<ItemGroup>
47-
<ProjectReference Include="..\Contentstack.Management.Core\contentstack.management.core.csproj" />
48-
</ItemGroup>
49-
</Project>
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net7.0</TargetFramework>
5+
6+
<IsPackable>false</IsPackable>
7+
<ReleaseVersion>$(Version)</ReleaseVersion>
8+
9+
<SignAssembly>true</SignAssembly>
10+
<AssemblyOriginatorKeyFile>../CSManagementSDK.snk</AssemblyOriginatorKeyFile>
11+
</PropertyGroup>
12+
13+
<ItemGroup>
14+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.13.0" />
15+
<PackageReference Include="MSTest.TestAdapter" Version="3.8.2" />
16+
<PackageReference Include="MSTest.TestFramework" Version="3.8.2" />
17+
<PackageReference Include="coverlet.collector" Version="6.0.4"><IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
18+
<PrivateAssets>all</PrivateAssets>
19+
</PackageReference>
20+
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="9.0.2" />
21+
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="9.0.2" />
22+
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="9.0.2" />
23+
<PackageReference Include="AutoFixture" Version="4.18.1" />
24+
<PackageReference Include="AutoFixture.AutoMoq" Version="4.18.1" />
25+
<PackageReference Include="Moq" Version="4.20.72" />
26+
<PackageReference Include="Microsoft.AspNetCore.Http" Version="2.3.0" />
27+
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
28+
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" />
29+
</ItemGroup>
30+
31+
<ItemGroup>
32+
<Folder Include="Helpers\" />
33+
<Folder Include="IntegrationTest\" />
34+
<Folder Include="Model\" />
35+
<Folder Include="Mock\" />
36+
</ItemGroup>
37+
38+
<ItemGroup>
39+
<EmbeddedResource Include="Mock\*.json" />
40+
</ItemGroup>
41+
42+
<ItemGroup>
43+
<Content Include="appSettings.json">
44+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
45+
</Content>
46+
</ItemGroup>
47+
48+
<ItemGroup>
49+
<None Remove="Microsoft.AspNetCore.Http" />
50+
</ItemGroup>
51+
52+
<ItemGroup>
53+
<ProjectReference Include="..\Contentstack.Management.Core\contentstack.management.core.csproj" />
54+
</ItemGroup>
55+
</Project>

Contentstack.Management.Core.Tests/Contentstack.cs

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1-
using System;
1+
using System;
22
using System.Collections.Generic;
33
using System.IO;
44
using System.Linq;
55
using System.Net;
6+
using System.Net.Http;
67
using System.Reflection;
8+
using Contentstack.Management.Core.Tests.Helpers;
79
using Contentstack.Management.Core.Tests.Model;
810
using Microsoft.Extensions.Configuration;
911
using Microsoft.Extensions.Options;
@@ -14,15 +16,6 @@ namespace Contentstack.Management.Core.Tests
1416
{
1517
public class Contentstack
1618
{
17-
private static readonly Lazy<ContentstackClient>
18-
client =
19-
new Lazy<ContentstackClient>(() =>
20-
{
21-
ContentstackClientOptions options = Config.GetSection("Contentstack").Get<ContentstackClientOptions>();
22-
return new ContentstackClient(new OptionsWrapper<ContentstackClientOptions>(options));
23-
});
24-
25-
2619
private static readonly Lazy<IConfigurationRoot>
2720
config =
2821
new Lazy<IConfigurationRoot>(() =>
@@ -42,13 +35,28 @@ private static readonly Lazy<IConfigurationRoot>
4235
return Config.GetSection("Contentstack:Organization").Get<OrganizationModel>();
4336
});
4437

45-
public static ContentstackClient Client { get { return client.Value; } }
4638
public static IConfigurationRoot Config{ get { return config.Value; } }
4739
public static NetworkCredential Credential { get { return credential.Value; } }
4840
public static OrganizationModel Organization { get { return organization.Value; } }
4941

5042
public static StackModel Stack { get; set; }
5143

44+
/// <summary>
45+
/// Creates a new ContentstackClient, logs in via the Login API (never from config),
46+
/// and returns the authenticated client. Callers are responsible for calling Logout()
47+
/// when done.
48+
/// </summary>
49+
public static ContentstackClient CreateAuthenticatedClient()
50+
{
51+
ContentstackClientOptions options = Config.GetSection("Contentstack").Get<ContentstackClientOptions>();
52+
options.Authtoken = null;
53+
var handler = new LoggingHttpHandler();
54+
var httpClient = new HttpClient(handler);
55+
var client = new ContentstackClient(httpClient, options);
56+
client.Login(Credential);
57+
return client;
58+
}
59+
5260
public static T serialize<T>(JsonSerializer serializer, string filePath)
5361
{
5462
string response = GetResourceText(filePath);
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
using System;
2+
using System.Linq;
3+
using System.Net;
4+
using System.Threading.Tasks;
5+
using Contentstack.Management.Core.Exceptions;
6+
using Microsoft.VisualStudio.TestTools.UnitTesting;
7+
8+
namespace Contentstack.Management.Core.Tests.Helpers
9+
{
10+
public static class AssertLogger
11+
{
12+
public static void IsNotNull(object value, string name = "")
13+
{
14+
bool passed = value != null;
15+
TestOutputLogger.LogAssertion($"IsNotNull({name})", "NotNull", value?.ToString() ?? "null", passed);
16+
Assert.IsNotNull(value);
17+
}
18+
19+
public static void IsNull(object value, string name = "")
20+
{
21+
bool passed = value == null;
22+
TestOutputLogger.LogAssertion($"IsNull({name})", "null", value?.ToString() ?? "null", passed);
23+
Assert.IsNull(value);
24+
}
25+
26+
public static void AreEqual<T>(T expected, T actual, string name = "")
27+
{
28+
bool passed = Equals(expected, actual);
29+
TestOutputLogger.LogAssertion($"AreEqual({name})", expected?.ToString() ?? "null", actual?.ToString() ?? "null", passed);
30+
Assert.AreEqual(expected, actual);
31+
}
32+
33+
public static void AreEqual<T>(T expected, T actual, string message, string name)
34+
{
35+
bool passed = Equals(expected, actual);
36+
TestOutputLogger.LogAssertion($"AreEqual({name})", expected?.ToString() ?? "null", actual?.ToString() ?? "null", passed);
37+
Assert.AreEqual(expected, actual, message);
38+
}
39+
40+
public static void IsTrue(bool condition, string name = "")
41+
{
42+
TestOutputLogger.LogAssertion($"IsTrue({name})", "True", condition.ToString(), condition);
43+
Assert.IsTrue(condition);
44+
}
45+
46+
public static void IsTrue(bool condition, string message, string name)
47+
{
48+
TestOutputLogger.LogAssertion($"IsTrue({name})", "True", condition.ToString(), condition);
49+
Assert.IsTrue(condition, message);
50+
}
51+
52+
public static void IsFalse(bool condition, string name = "")
53+
{
54+
TestOutputLogger.LogAssertion($"IsFalse({name})", "False", condition.ToString(), !condition);
55+
Assert.IsFalse(condition);
56+
}
57+
58+
public static void IsFalse(bool condition, string message, string name)
59+
{
60+
TestOutputLogger.LogAssertion($"IsFalse({name})", "False", condition.ToString(), !condition);
61+
Assert.IsFalse(condition, message);
62+
}
63+
64+
public static void IsInstanceOfType(object value, Type expectedType, string name = "")
65+
{
66+
bool passed = value != null && expectedType.IsInstanceOfType(value);
67+
TestOutputLogger.LogAssertion(
68+
$"IsInstanceOfType({name})",
69+
expectedType?.Name ?? "null",
70+
value?.GetType()?.Name ?? "null",
71+
passed);
72+
Assert.IsInstanceOfType(value, expectedType);
73+
}
74+
75+
public static T ThrowsException<T>(Action action, string name = "") where T : Exception
76+
{
77+
try
78+
{
79+
action();
80+
TestOutputLogger.LogAssertion($"ThrowsException<{typeof(T).Name}>({name})", typeof(T).Name, "NoException", false);
81+
throw new AssertFailedException($"Expected exception {typeof(T).Name} was not thrown.");
82+
}
83+
catch (T ex)
84+
{
85+
TestOutputLogger.LogAssertion($"ThrowsException<{typeof(T).Name}>({name})", typeof(T).Name, typeof(T).Name, true);
86+
return ex;
87+
}
88+
catch (AssertFailedException)
89+
{
90+
throw;
91+
}
92+
catch (Exception ex)
93+
{
94+
TestOutputLogger.LogAssertion($"ThrowsException<{typeof(T).Name}>({name})", typeof(T).Name, ex.GetType().Name, false);
95+
throw new AssertFailedException(
96+
$"Expected exception {typeof(T).Name} but got {ex.GetType().Name}: {ex.Message}", ex);
97+
}
98+
}
99+
100+
public static void Fail(string message)
101+
{
102+
TestOutputLogger.LogAssertion("Fail", "N/A", message ?? "", false);
103+
Assert.Fail(message);
104+
}
105+
106+
public static void Fail(string message, params object[] parameters)
107+
{
108+
TestOutputLogger.LogAssertion("Fail", "N/A", message ?? "", false);
109+
Assert.Fail(message, parameters);
110+
}
111+
112+
public static void Inconclusive(string message)
113+
{
114+
TestOutputLogger.LogAssertion("Inconclusive", "N/A", message ?? "", false);
115+
Assert.Inconclusive(message);
116+
}
117+
118+
/// <summary>
119+
/// Asserts a Contentstack API error with an HTTP status in the allowed set.
120+
/// </summary>
121+
public static ContentstackErrorException ThrowsContentstackError(Action action, string name, params HttpStatusCode[] acceptableStatuses)
122+
{
123+
var ex = ThrowsException<ContentstackErrorException>(action, name);
124+
IsTrue(
125+
acceptableStatuses.Contains(ex.StatusCode),
126+
$"Expected one of [{string.Join(", ", acceptableStatuses)}] but was {ex.StatusCode}",
127+
"statusCode");
128+
return ex;
129+
}
130+
131+
/// <summary>
132+
/// Async variant: runs the task and expects <see cref="ContentstackErrorException"/> with an allowed status.
133+
/// </summary>
134+
public static async Task<ContentstackErrorException> ThrowsContentstackErrorAsync(Func<Task> action, string name, params HttpStatusCode[] acceptableStatuses)
135+
{
136+
try
137+
{
138+
await action();
139+
TestOutputLogger.LogAssertion($"ThrowsContentstackErrorAsync({name})", "ContentstackErrorException", "NoException", false);
140+
throw new AssertFailedException($"Expected exception ContentstackErrorException was not thrown.");
141+
}
142+
catch (ContentstackErrorException ex)
143+
{
144+
IsTrue(
145+
acceptableStatuses.Contains(ex.StatusCode),
146+
$"Expected one of [{string.Join(", ", acceptableStatuses)}] but was {ex.StatusCode}",
147+
"statusCode");
148+
TestOutputLogger.LogAssertion($"ThrowsContentstackErrorAsync({name})", nameof(ContentstackErrorException), ex.StatusCode.ToString(), true);
149+
return ex;
150+
}
151+
catch (AssertFailedException)
152+
{
153+
throw;
154+
}
155+
catch (Exception ex)
156+
{
157+
TestOutputLogger.LogAssertion($"ThrowsContentstackErrorAsync({name})", nameof(ContentstackErrorException), ex.GetType().Name, false);
158+
throw new AssertFailedException(
159+
$"Expected exception ContentstackErrorException but got {ex.GetType().Name}: {ex.Message}", ex);
160+
}
161+
}
162+
}
163+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
using Contentstack.Management.Core.Models;
2+
using Newtonsoft.Json;
3+
using Newtonsoft.Json.Linq;
4+
5+
namespace Contentstack.Management.Core.Tests.Helpers
6+
{
7+
/// <summary>
8+
/// Loads embedded content-type JSON and assigns unique UIDs/titles for disposable integration tests.
9+
/// </summary>
10+
public static class ContentTypeFixtureLoader
11+
{
12+
public static ContentModelling LoadFromMock(JsonSerializer serializer, string embeddedFileName, string uidSuffix)
13+
{
14+
var text = Contentstack.GetResourceText(embeddedFileName);
15+
var jo = JObject.Parse(text);
16+
var baseUid = jo["uid"]?.Value<string>() ?? "ct";
17+
jo["uid"] = $"{baseUid}_{uidSuffix}";
18+
var title = jo["title"]?.Value<string>() ?? "CT";
19+
jo["title"] = $"{title} {uidSuffix}";
20+
return jo.ToObject<ContentModelling>(serializer);
21+
}
22+
}
23+
}

0 commit comments

Comments
 (0)