diff --git a/.github/workflows/sca-scan.yml b/.github/workflows/sca-scan.yml
index 986c6e9..30d262e 100644
--- a/.github/workflows/sca-scan.yml
+++ b/.github/workflows/sca-scan.yml
@@ -16,7 +16,6 @@ jobs:
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
with:
- args: --file=Contentstack.Management.Core/obj/project.assets.json --fail-on=all
- json: true
+ args: --file=Contentstack.Management.Core/obj/project.assets.json --fail-on=all --json-file-output=snyk.json
continue-on-error: true
- uses: contentstack/sca-policy@main
diff --git a/Contentstack.Management.Core.Tests/Helpers/PersonalizeTestHelper.cs b/Contentstack.Management.Core.Tests/Helpers/PersonalizeTestHelper.cs
new file mode 100644
index 0000000..1b7bbe3
--- /dev/null
+++ b/Contentstack.Management.Core.Tests/Helpers/PersonalizeTestHelper.cs
@@ -0,0 +1,170 @@
+using System;
+using System.Net.Http;
+using System.Net.Http.Headers;
+using System.Text;
+using System.Text.Json;
+using System.Text.Json.Nodes;
+using System.Threading.Tasks;
+using Contentstack.Management.Core.Endpoints;
+
+namespace Contentstack.Management.Core.Tests.Helpers
+{
+ ///
+ /// Raw-HTTP client for the Contentstack Personalize Management API, used only by
+ /// integration tests. The core SDK has no Project/Audience/Experience wrapper classes
+ /// (Personalize is a separate product surface from Content Management), so this helper
+ /// drives Personalize directly to auto-provision real Variant Group/Variant data on a
+ /// stack instead of relying on a hardcoded variant UID.
+ ///
+ /// NOTE(verify at implementation time): exact endpoint paths, payload shapes, and
+ /// required headers below are best-effort based on Contentstack's published Personalize
+ /// API conventions and have not been exercised against a live org in this change.
+ /// Confirm against https://www.contentstack.com/docs/developers/apis/personalize-management-api
+ /// before relying on this in CI, and adjust the marked TODO(verify) spots as needed.
+ ///
+ internal class PersonalizeTestHelper
+ {
+ private readonly HttpClient _httpClient;
+ private readonly string _baseUrl;
+ private readonly string _organizationUid;
+
+ internal PersonalizeTestHelper(string authtoken, string organizationUid, string region = "na")
+ {
+ if (string.IsNullOrEmpty(authtoken))
+ {
+ throw new ArgumentException("authtoken is required", nameof(authtoken));
+ }
+ if (string.IsNullOrEmpty(organizationUid))
+ {
+ throw new ArgumentException("organizationUid is required", nameof(organizationUid));
+ }
+
+ _organizationUid = organizationUid;
+ _baseUrl = Endpoint.GetContentstackEndpoint(region, "personalizeManagement").TrimEnd('/');
+
+ _httpClient = new HttpClient(new LoggingHttpHandler())
+ {
+ Timeout = TimeSpan.FromSeconds(30)
+ };
+ _httpClient.DefaultRequestHeaders.Add("authtoken", authtoken);
+ _httpClient.DefaultRequestHeaders.Add("organization_uid", organizationUid);
+ }
+
+ ///
+ /// Creates a Personalize project connected to the given stack, so that Audiences/
+ /// Experiences created within it can auto-provision Variant Groups on that stack.
+ /// TODO(verify): confirm path is "/projects" and payload shape against Personalize docs.
+ ///
+ internal async Task CreateProjectAsync(string stackApiKey, string projectName)
+ {
+ var payload = new JsonObject
+ {
+ ["name"] = projectName,
+ ["connectedStackApiKey"] = stackApiKey
+ };
+
+ var project = await PostAsync("/projects", payload, null);
+ return project?["uid"]?.ToString();
+ }
+
+ ///
+ /// Returns the uid of an existing audience on the project if one is found, otherwise
+ /// creates a new default audience and returns its uid.
+ /// TODO(verify): confirm paths "/audiences" (GET list / POST create) and payload shape.
+ ///
+ internal async Task GetOrCreateDefaultAudienceAsync(string projectUid, string audienceName)
+ {
+ var existing = await GetAsync("/audiences", projectUid);
+ var audiences = existing?["audiences"]?.AsArray();
+ if (audiences != null && audiences.Count > 0)
+ {
+ return audiences[0]?["uid"]?.ToString();
+ }
+
+ var payload = new JsonObject
+ {
+ ["name"] = audienceName,
+ ["definition"] = new JsonObject
+ {
+ ["rules"] = new JsonArray(),
+ ["operator"] = "and"
+ }
+ };
+
+ var audience = await PostAsync("/audiences", payload, projectUid);
+ return audience?["uid"]?.ToString();
+ }
+
+ ///
+ /// Creates an Experience linked to the given audience. In the real product, creating
+ /// an Experience on a stack-connected project auto-provisions a Variant Group (and its
+ /// Variants) on the Content Management side of that stack.
+ /// TODO(verify): confirm path "/experiences" and payload shape (variant count/short_uids).
+ ///
+ internal async Task CreateExperienceAsync(string projectUid, string audienceUid, string experienceName)
+ {
+ var payload = new JsonObject
+ {
+ ["name"] = experienceName,
+ ["audiences"] = new JsonArray(audienceUid),
+ ["variants"] = new JsonArray(new JsonObject { ["name"] = "Variant A" })
+ };
+
+ var experience = await PostAsync("/experiences", payload, projectUid);
+ return experience?["uid"]?.ToString();
+ }
+
+ /// Best-effort teardown of an experience created for a test run. Failures are non-fatal.
+ internal async Task DeleteExperienceAsync(string projectUid, string experienceUid)
+ {
+ await DeleteAsync($"/experiences/{experienceUid}", projectUid);
+ }
+
+ /// Best-effort teardown of a project created for a test run. Failures are non-fatal.
+ internal async Task DeleteProjectAsync(string projectUid)
+ {
+ await DeleteAsync($"/projects/{projectUid}", null);
+ }
+
+ private async Task PostAsync(string path, JsonObject payload, string projectUid)
+ {
+ using var request = new HttpRequestMessage(HttpMethod.Post, _baseUrl + path);
+ ApplyProjectHeader(request, projectUid);
+ request.Content = new StringContent(payload.ToJsonString(), Encoding.UTF8, "application/json");
+
+ using var response = await _httpClient.SendAsync(request);
+ string body = await response.Content.ReadAsStringAsync();
+ response.EnsureSuccessStatusCode();
+
+ return string.IsNullOrEmpty(body) ? null : JsonNode.Parse(body)?.AsObject();
+ }
+
+ private async Task GetAsync(string path, string projectUid)
+ {
+ using var request = new HttpRequestMessage(HttpMethod.Get, _baseUrl + path);
+ ApplyProjectHeader(request, projectUid);
+
+ using var response = await _httpClient.SendAsync(request);
+ string body = await response.Content.ReadAsStringAsync();
+ response.EnsureSuccessStatusCode();
+
+ return string.IsNullOrEmpty(body) ? null : JsonNode.Parse(body)?.AsObject();
+ }
+
+ private async Task DeleteAsync(string path, string projectUid)
+ {
+ using var request = new HttpRequestMessage(HttpMethod.Delete, _baseUrl + path);
+ ApplyProjectHeader(request, projectUid);
+ using var response = await _httpClient.SendAsync(request);
+ response.EnsureSuccessStatusCode();
+ }
+
+ private static void ApplyProjectHeader(HttpRequestMessage request, string projectUid)
+ {
+ if (!string.IsNullOrEmpty(projectUid))
+ {
+ request.Headers.TryAddWithoutValidation("x-project-uid", projectUid);
+ }
+ }
+ }
+}
diff --git a/Contentstack.Management.Core.Tests/IntegrationTest/Contentstack003_StackTest.cs b/Contentstack.Management.Core.Tests/IntegrationTest/Contentstack003_StackTest.cs
index a9d5a4a..d0e9ac9 100644
--- a/Contentstack.Management.Core.Tests/IntegrationTest/Contentstack003_StackTest.cs
+++ b/Contentstack.Management.Core.Tests/IntegrationTest/Contentstack003_StackTest.cs
@@ -6,6 +6,7 @@
using Contentstack.Management.Core;
using Contentstack.Management.Core.Exceptions;
using Contentstack.Management.Core.Models;
+using Contentstack.Management.Core.Models.Token;
using Contentstack.Management.Core.Tests.Helpers;
using Contentstack.Management.Core.Tests.Model;
using Microsoft.VisualStudio.TestTools.UnitTesting;
@@ -1191,5 +1192,71 @@ private static void AssertStackAuthError(Exception ex, string assertionName)
}
#endregion
+
+ #region Global management token (shared infrastructure for variant/personalize tests)
+
+ [TestMethod]
+ [DoNotParallelize]
+ public void Test065_Should_Create_Management_Token()
+ {
+ TestOutputLogger.LogContext("TestScenario", "CreateGlobalManagementToken");
+ AssertStackApiKeyOrInconclusive();
+ try
+ {
+ Stack stack = _client.Stack(Contentstack.Stack.APIKey);
+ var model = new ManagementTokenModel
+ {
+ Name = "DotNet SDK Integration Test Token",
+ Description = "Shared management token used by the .NET SDK integration test suite",
+ Scope = new List
+ {
+ new TokenScope
+ {
+ Module = "content_type",
+ ACL = new Dictionary { { "read", "true" }, { "write", "true" } }
+ },
+ new TokenScope
+ {
+ Module = "entry",
+ ACL = new Dictionary { { "read", "true" }, { "write", "true" } }
+ },
+ new TokenScope
+ {
+ Module = "asset",
+ ACL = new Dictionary { { "read", "true" }, { "write", "true" } }
+ },
+ new TokenScope
+ {
+ Module = "environment",
+ ACL = new Dictionary { { "read", "true" }, { "write", "true" } }
+ },
+ new TokenScope
+ {
+ Module = "taxonomy",
+ ACL = new Dictionary { { "read", "true" }, { "write", "true" } }
+ }
+ }
+ };
+
+ ContentstackResponse contentstackResponse = stack.ManagementTokens().Create(model);
+
+ AssertLogger.IsTrue(contentstackResponse.IsSuccessStatusCode, $"Create management token failed: {contentstackResponse.OpenResponse()}", "CreateManagementTokenSuccess");
+
+ File.WriteAllText("./managementTokenInfo.txt", contentstackResponse.OpenResponse());
+
+ ManagementTokenResponse tokenResponse = contentstackResponse.OpenTResponse();
+ AssertLogger.IsNotNull(tokenResponse.Token, "tokenResponse.Token");
+ AssertLogger.IsNotNull(tokenResponse.Token.Uid, "tokenResponse.Token.Uid");
+ AssertLogger.IsNotNull(tokenResponse.Token.Token, "tokenResponse.Token.Token");
+
+ TestOutputLogger.LogContext("ManagementTokenUid", tokenResponse.Token.Uid ?? "");
+ }
+ catch (Exception e)
+ {
+ AssertLogger.Fail(e.Message);
+ }
+ }
+
+ #endregion
}
}
diff --git a/Contentstack.Management.Core.Tests/IntegrationTest/Contentstack021_EntryVariantTest.cs b/Contentstack.Management.Core.Tests/IntegrationTest/Contentstack021_EntryVariantTest.cs
index afa6012..8cfa149 100644
--- a/Contentstack.Management.Core.Tests/IntegrationTest/Contentstack021_EntryVariantTest.cs
+++ b/Contentstack.Management.Core.Tests/IntegrationTest/Contentstack021_EntryVariantTest.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Net;
+using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Contentstack.Management.Core.Exceptions;
@@ -38,6 +39,9 @@ public class Contentstack021_EntryVariantTest
private static string _variantUid;
private static string _variantGroupUid;
+ private const int VariantGroupPollTimeoutSeconds = 90;
+ private const int VariantGroupPollIntervalSeconds = 5;
+
#region Helper Methods
///
@@ -235,17 +239,25 @@ public static void ClassCleanup()
[TestInitialize]
public void TestInitialize()
{
- // Read the API key from appSettings.json
- string apiKey = Contentstack.Config["Contentstack:Stack:api_key"];
-
- // Optional: Fallback to stackApiKey.txt if it's missing in appSettings.json
- if (string.IsNullOrEmpty(apiKey))
+ // Always use the dynamically-created stack shared by the rest of the suite
+ // (matches Contentstack012_ContentTypeTest.cs and friends) rather than the
+ // static Contentstack:Stack:api_key from appSettings.json.
+ StackResponse response = StackResponse.getStack(_client.serializer);
+ string apiKey = response.Stack.APIKey;
+
+ string managementToken = null;
+ try
{
- StackResponse response = StackResponse.getStack(_client.serializer);
- apiKey = response.Stack.APIKey;
+ managementToken = ManagementTokenResponse.getManagementToken(_client.serializer)?.Token?.Token;
}
-
- _stack = _client.Stack(apiKey);
+ catch
+ {
+ // managementTokenInfo.txt missing (e.g. isolated test run) — fall back to session-token-only stack.
+ }
+
+ _stack = string.IsNullOrEmpty(managementToken)
+ ? _client.Stack(apiKey)
+ : _client.Stack(apiKey, managementToken);
}
[TestMethod]
@@ -254,21 +266,74 @@ public async System.Threading.Tasks.Task Test001_Ensure_Setup_Data()
{
TestOutputLogger.LogContext("TestScenario", "ProductBannerVariantLifecycle_Setup");
- // 1. Ensure Variant Group exists
+ // 1. Ensure a Variant Group (with real Variants) exists on the dynamic stack.
var collection = new global::Contentstack.Management.Core.Queryable.ParameterCollection();
collection.Add("include_variant_info", "true");
collection.Add("include_variant_count", "true");
- var vgResponse = await _stack.VariantGroup().FindAsync(collection);
- Console.WriteLine("Variant Groups Response: " + vgResponse.OpenResponse());
+ async Task FindVariantGroupsAsync()
+ {
+ var response = await _stack.VariantGroup().FindAsync(collection);
+ Console.WriteLine("Variant Groups Response: " + response.OpenResponse());
+ return response.OpenJsonObjectResponse()["variant_groups"]?.AsArray();
+ }
- var vgJsonObject = vgResponse.OpenJsonObjectResponse();
- var groups = vgJsonObject["variant_groups"]?.AsArray();
+ var groups = await FindVariantGroupsAsync();
if (groups == null || groups.Count == 0)
{
- Assert.Inconclusive("No variant groups found in the stack. Create one to run EntryVariant tests. Response was: " + vgResponse.OpenResponse());
- return;
+ // No pre-existing variant group — drive the Personalize API to create a
+ // project/audience/experience on this stack, which auto-provisions a real
+ // Variant Group + Variants on the Content Management side.
+ try
+ {
+ var personalize = new PersonalizeTestHelper(_client.contentstackOptions.Authtoken, Contentstack.Organization.Uid);
+ long runId = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
+
+ string projectUid = await personalize.CreateProjectAsync(_stack.APIKey, $"DotNet SDK Integration Test Project {runId}");
+ string audienceUid = await personalize.GetOrCreateDefaultAudienceAsync(projectUid, $"DotNet SDK Test Audience {runId}");
+ string experienceUid = await personalize.CreateExperienceAsync(projectUid, audienceUid, $"DotNet SDK Test Experience {runId}");
+
+ TestOutputLogger.LogContext("PersonalizeProjectUid", projectUid ?? "");
+ TestOutputLogger.LogContext("PersonalizeAudienceUid", audienceUid ?? "");
+ TestOutputLogger.LogContext("PersonalizeExperienceUid", experienceUid ?? "");
+
+ var pollDeadline = DateTime.UtcNow.AddSeconds(VariantGroupPollTimeoutSeconds);
+ while (DateTime.UtcNow < pollDeadline)
+ {
+ groups = await FindVariantGroupsAsync();
+ if (groups != null && groups.Count > 0)
+ {
+ break;
+ }
+ await Task.Delay(TimeSpan.FromSeconds(VariantGroupPollIntervalSeconds));
+ }
+
+ if (groups == null || groups.Count == 0)
+ {
+ // Personalize setup succeeded but the CMS side never produced a
+ // Variant Group in time — a real product/integration bug, not an
+ // environment precondition, so fail loudly instead of silently
+ // degrading to a hardcoded UID like this test used to.
+ Assert.Fail(
+ $"Personalize project/audience/experience were created (project={projectUid}, " +
+ $"audience={audienceUid}, experience={experienceUid}) but no Variant Group appeared " +
+ $"on the stack within {VariantGroupPollTimeoutSeconds}s.");
+ return;
+ }
+ }
+ catch (HttpRequestException ex) when (ex.StatusCode == HttpStatusCode.Forbidden || ex.StatusCode == HttpStatusCode.Unauthorized)
+ {
+ Assert.Inconclusive("Personalize is not enabled/entitled for this organization; cannot drive dynamic variant setup. " + ex.Message);
+ return;
+ }
+ catch (Exception ex)
+ {
+ // Environment/transient issue (network, unexpected schema, etc.) — don't
+ // block the rest of the suite on this, but make it loudly diagnosable.
+ Assert.Inconclusive("Personalize-driven variant setup failed unexpectedly: " + ex.Message);
+ return;
+ }
}
_variantGroupUid = groups[0]?["uid"]?.ToString();
@@ -289,9 +354,8 @@ public async System.Threading.Tasks.Task Test001_Ensure_Setup_Data()
if (string.IsNullOrEmpty(_variantUid))
{
- // Fallback to demo UIDs if none are returned by the API so the test doesn't skip
- _variantUid = "cs372c03252b23f623";
- Console.WriteLine("Warning: The variant group had no variants. Using a hardcoded variant UID for testing: " + _variantUid);
+ Assert.Fail("Variant Group was found/created but contains no variants.");
+ return;
}
TestOutputLogger.LogContext("VariantGroup", _variantGroupUid);
diff --git a/Contentstack.Management.Core.Tests/IntegrationTest/Contentstack022_VariantGroupTest.cs b/Contentstack.Management.Core.Tests/IntegrationTest/Contentstack022_VariantGroupTest.cs
index c17aabf..3d617d2 100644
--- a/Contentstack.Management.Core.Tests/IntegrationTest/Contentstack022_VariantGroupTest.cs
+++ b/Contentstack.Management.Core.Tests/IntegrationTest/Contentstack022_VariantGroupTest.cs
@@ -194,13 +194,24 @@ public static async Task ClassInitialize(TestContext context)
// Test001 / Test003 happened to run after the guard was evaluated.
try
{
- string apiKey = Contentstack.Config["Contentstack:Stack:api_key"];
- if (string.IsNullOrEmpty(apiKey))
+ // Always use the dynamically-created stack shared by the rest of the suite,
+ // rather than the static Contentstack:Stack:api_key from appSettings.json.
+ StackResponse stackResp = StackResponse.getStack(_client.serializer);
+ string apiKey = stackResp.Stack.APIKey;
+
+ string managementToken = null;
+ try
+ {
+ managementToken = ManagementTokenResponse.getManagementToken(_client.serializer)?.Token?.Token;
+ }
+ catch
{
- StackResponse stackResp = StackResponse.getStack(_client.serializer);
- apiKey = stackResp.Stack.APIKey;
+ // managementTokenInfo.txt missing (e.g. isolated test run) — fall back to session-token-only stack.
}
- var setupStack = _client.Stack(apiKey);
+
+ var setupStack = string.IsNullOrEmpty(managementToken)
+ ? _client.Stack(apiKey)
+ : _client.Stack(apiKey, managementToken);
// Variant groups
var vgResponse = await setupStack.VariantGroup().FindAsync();
@@ -245,17 +256,25 @@ public static void ClassCleanup()
[TestInitialize]
public void TestInitialize()
{
- // Read the API key from appSettings.json
- string apiKey = Contentstack.Config["Contentstack:Stack:api_key"];
-
- // Optional: Fallback to stackApiKey.txt if it's missing in appSettings.json
- if (string.IsNullOrEmpty(apiKey))
+ // Always use the dynamically-created stack shared by the rest of the suite
+ // (matches Contentstack012_ContentTypeTest.cs and friends) rather than the
+ // static Contentstack:Stack:api_key from appSettings.json.
+ StackResponse response = StackResponse.getStack(_client.serializer);
+ string apiKey = response.Stack.APIKey;
+
+ string managementToken = null;
+ try
{
- StackResponse response = StackResponse.getStack(_client.serializer);
- apiKey = response.Stack.APIKey;
+ managementToken = ManagementTokenResponse.getManagementToken(_client.serializer)?.Token?.Token;
}
-
- _stack = _client.Stack(apiKey);
+ catch
+ {
+ // managementTokenInfo.txt missing (e.g. isolated test run) — fall back to session-token-only stack.
+ }
+
+ _stack = string.IsNullOrEmpty(managementToken)
+ ? _client.Stack(apiKey)
+ : _client.Stack(apiKey, managementToken);
}
#region Positive Test Cases
diff --git a/Contentstack.Management.Core.Tests/IntegrationTest/Contentstack024_ManagementTokenTest.cs b/Contentstack.Management.Core.Tests/IntegrationTest/Contentstack024_ManagementTokenTest.cs
new file mode 100644
index 0000000..9b3d05a
--- /dev/null
+++ b/Contentstack.Management.Core.Tests/IntegrationTest/Contentstack024_ManagementTokenTest.cs
@@ -0,0 +1,489 @@
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Threading.Tasks;
+using Contentstack.Management.Core.Exceptions;
+using Contentstack.Management.Core.Models;
+using Contentstack.Management.Core.Models.Token;
+using Contentstack.Management.Core.Tests.Helpers;
+using Contentstack.Management.Core.Tests.Model;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using System.Text.Json.Nodes;
+
+namespace Contentstack.Management.Core.Tests.IntegrationTest
+{
+ [TestClass]
+ public class Contentstack024_ManagementTokenTest
+ {
+ private static ContentstackClient _client;
+ private Stack _stack;
+ private string _managementTokenUid;
+ private ManagementTokenModel _testTokenModel;
+
+ private const string NonExistentTokenUid = "blt00000000000000000000";
+
+ [ClassInitialize]
+ public static void ClassInitialize(TestContext context)
+ {
+ _client = Contentstack.CreateAuthenticatedClient();
+ }
+
+ [ClassCleanup]
+ public static void ClassCleanup()
+ {
+ try { _client?.Logout(); } catch { }
+ _client = null;
+ }
+
+ [TestInitialize]
+ public void Initialize()
+ {
+ StackResponse response = StackResponse.getStack(_client.serializer);
+ _stack = _client.Stack(response.Stack.APIKey);
+
+ _testTokenModel = BuildValidTokenModel("Test Management Token");
+ }
+
+ private static ManagementTokenModel BuildValidTokenModel(string name)
+ {
+ return new ManagementTokenModel
+ {
+ Name = name,
+ Description = "Integration test management token",
+ Scope = new List
+ {
+ new TokenScope
+ {
+ Module = "content_type",
+ ACL = new Dictionary { { "read", "true" }, { "write", "true" } }
+ },
+ new TokenScope
+ {
+ Module = "entry",
+ ACL = new Dictionary { { "read", "true" }, { "write", "true" } }
+ }
+ }
+ };
+ }
+
+ [TestMethod]
+ [DoNotParallelize]
+ public void Test001_Should_Create_Management_Token()
+ {
+ TestOutputLogger.LogContext("TestScenario", "Test001_Should_Create_Management_Token");
+ try
+ {
+ ContentstackResponse response = _stack.ManagementTokens().Create(_testTokenModel);
+
+ AssertLogger.IsTrue(response.IsSuccessStatusCode, $"Create management token failed: {response.OpenResponse()}", "CreateManagementTokenSuccess");
+
+ var responseObject = response.OpenJsonObjectResponse();
+ AssertLogger.IsNotNull(responseObject["token"], "Response should contain token object");
+
+ var tokenData = responseObject["token"] as JsonObject;
+ AssertLogger.IsNotNull(tokenData["uid"], "Token should have UID");
+ AssertLogger.AreEqual(_testTokenModel.Name, tokenData["name"]?.ToString(), "Token name should match", "TokenName");
+
+ // Proves the JSON-casing fix: a "Scope"-cased payload would previously have been
+ // silently dropped by the API, leaving this field empty/missing.
+ var scope = tokenData["scope"] as JsonArray;
+ AssertLogger.IsNotNull(scope, "Token should have scope");
+ AssertLogger.IsTrue(scope.Count == _testTokenModel.Scope.Count, "Scope module count should round-trip", "ScopeRoundTrip");
+
+ _managementTokenUid = tokenData["uid"]?.ToString();
+ AssertLogger.IsNotNull(_managementTokenUid, "Management token UID should not be null");
+
+ TestOutputLogger.LogContext("ManagementTokenUid", _managementTokenUid ?? "");
+ }
+ catch (Exception ex)
+ {
+ AssertLogger.Fail("Create management token test failed", ex.Message);
+ }
+ }
+
+ [TestMethod]
+ [DoNotParallelize]
+ public async Task Test002_Should_Create_Management_Token_Async()
+ {
+ TestOutputLogger.LogContext("TestScenario", "Test002_Should_Create_Management_Token_Async");
+ try
+ {
+ var model = BuildValidTokenModel("Async Test Management Token");
+ ContentstackResponse response = await _stack.ManagementTokens().CreateAsync(model);
+
+ AssertLogger.IsTrue(response.IsSuccessStatusCode, $"Async create management token failed: {response.OpenResponse()}", "AsyncCreateSuccess");
+
+ var responseObject = response.OpenJsonObjectResponse();
+ var tokenData = responseObject["token"] as JsonObject;
+ AssertLogger.IsNotNull(tokenData["uid"], "Token should have UID");
+
+ string asyncTokenUid = tokenData["uid"]?.ToString();
+ TestOutputLogger.LogContext("AsyncCreatedTokenUid", asyncTokenUid ?? "");
+
+ if (!string.IsNullOrEmpty(asyncTokenUid))
+ {
+ await _stack.ManagementTokens(asyncTokenUid).DeleteAsync();
+ }
+ }
+ catch (Exception ex)
+ {
+ AssertLogger.Fail("Async create management token test failed", ex.Message);
+ }
+ }
+
+ [TestMethod]
+ [DoNotParallelize]
+ public void Test003_Should_Fetch_Management_Token()
+ {
+ TestOutputLogger.LogContext("TestScenario", "Test003_Should_Fetch_Management_Token");
+ try
+ {
+ if (string.IsNullOrEmpty(_managementTokenUid))
+ {
+ Test001_Should_Create_Management_Token();
+ }
+
+ ContentstackResponse response = _stack.ManagementTokens(_managementTokenUid).Fetch();
+
+ AssertLogger.IsTrue(response.IsSuccessStatusCode, $"Fetch management token failed: {response.OpenResponse()}", "FetchSuccess");
+
+ var responseObject = response.OpenJsonObjectResponse();
+ var tokenData = responseObject["token"] as JsonObject;
+ AssertLogger.AreEqual(_managementTokenUid, tokenData["uid"]?.ToString(), "Token UID should match", "TokenUid");
+ }
+ catch (Exception ex)
+ {
+ AssertLogger.Fail($"Fetch management token test failed: {ex.Message}");
+ }
+ }
+
+ [TestMethod]
+ [DoNotParallelize]
+ public async Task Test004_Should_Fetch_Management_Token_Async()
+ {
+ TestOutputLogger.LogContext("TestScenario", "Test004_Should_Fetch_Management_Token_Async");
+ try
+ {
+ if (string.IsNullOrEmpty(_managementTokenUid))
+ {
+ Test001_Should_Create_Management_Token();
+ }
+
+ ContentstackResponse response = await _stack.ManagementTokens(_managementTokenUid).FetchAsync();
+
+ AssertLogger.IsTrue(response.IsSuccessStatusCode, $"Async fetch management token failed: {response.OpenResponse()}", "AsyncFetchSuccess");
+
+ var responseObject = response.OpenJsonObjectResponse();
+ var tokenData = responseObject["token"] as JsonObject;
+ AssertLogger.AreEqual(_managementTokenUid, tokenData["uid"]?.ToString(), "Token UID should match", "TokenUid");
+ }
+ catch (Exception ex)
+ {
+ AssertLogger.Fail($"Async fetch management token test failed: {ex.Message}");
+ }
+ }
+
+ [TestMethod]
+ [DoNotParallelize]
+ public void Test005_Should_Update_Management_Token()
+ {
+ TestOutputLogger.LogContext("TestScenario", "Test005_Should_Update_Management_Token");
+ try
+ {
+ if (string.IsNullOrEmpty(_managementTokenUid))
+ {
+ Test001_Should_Create_Management_Token();
+ }
+
+ var updateModel = BuildValidTokenModel("Updated Test Management Token");
+ ContentstackResponse response = _stack.ManagementTokens(_managementTokenUid).Update(updateModel);
+
+ AssertLogger.IsTrue(response.IsSuccessStatusCode, $"Update management token failed: {response.OpenResponse()}", "UpdateSuccess");
+
+ var responseObject = response.OpenJsonObjectResponse();
+ var tokenData = responseObject["token"] as JsonObject;
+ AssertLogger.AreEqual(_managementTokenUid, tokenData["uid"]?.ToString(), "Token UID should match", "TokenUid");
+ AssertLogger.AreEqual(updateModel.Name, tokenData["name"]?.ToString(), "Updated token name should match", "UpdatedTokenName");
+ }
+ catch (Exception ex)
+ {
+ AssertLogger.Fail($"Update management token test failed: {ex.Message}");
+ }
+ }
+
+ [TestMethod]
+ [DoNotParallelize]
+ public async Task Test006_Should_Update_Management_Token_Async()
+ {
+ TestOutputLogger.LogContext("TestScenario", "Test006_Should_Update_Management_Token_Async");
+ try
+ {
+ if (string.IsNullOrEmpty(_managementTokenUid))
+ {
+ Test001_Should_Create_Management_Token();
+ }
+
+ var updateModel = BuildValidTokenModel("Async Updated Test Management Token");
+ ContentstackResponse response = await _stack.ManagementTokens(_managementTokenUid).UpdateAsync(updateModel);
+
+ AssertLogger.IsTrue(response.IsSuccessStatusCode, $"Async update management token failed: {response.OpenResponse()}", "AsyncUpdateSuccess");
+
+ var responseObject = response.OpenJsonObjectResponse();
+ var tokenData = responseObject["token"] as JsonObject;
+ AssertLogger.AreEqual(updateModel.Name, tokenData["name"]?.ToString(), "Updated token name should match", "UpdatedTokenName");
+ }
+ catch (Exception ex)
+ {
+ AssertLogger.Fail($"Async update management token test failed: {ex.Message}");
+ }
+ }
+
+ [TestMethod]
+ [DoNotParallelize]
+ public void Test007_Should_Query_All_Management_Tokens()
+ {
+ TestOutputLogger.LogContext("TestScenario", "Test007_Should_Query_All_Management_Tokens");
+ try
+ {
+ if (string.IsNullOrEmpty(_managementTokenUid))
+ {
+ Test001_Should_Create_Management_Token();
+ }
+
+ ContentstackResponse response = _stack.ManagementTokens().Query().Find();
+
+ AssertLogger.IsTrue(response.IsSuccessStatusCode, $"Query management tokens failed: {response.OpenResponse()}", "QuerySuccess");
+
+ var responseObject = response.OpenJsonObjectResponse();
+ AssertLogger.IsNotNull(responseObject["tokens"], "Response should contain tokens array");
+
+ var tokens = responseObject["tokens"] as JsonArray;
+ AssertLogger.IsTrue(tokens.Count > 0, "Should have at least one management token", "TokensCountGreaterThanZero");
+
+ bool foundTestToken = false;
+ foreach (var token in tokens)
+ {
+ if (token["uid"]?.ToString() == _managementTokenUid)
+ {
+ foundTestToken = true;
+ break;
+ }
+ }
+
+ AssertLogger.IsTrue(foundTestToken, "Test token should be found in query results", "TestTokenFoundInQuery");
+ }
+ catch (Exception ex)
+ {
+ AssertLogger.Fail($"Query management tokens test failed: {ex.Message}");
+ }
+ }
+
+ [TestMethod]
+ [DoNotParallelize]
+ public async Task Test008_Should_Query_All_Management_Tokens_Async()
+ {
+ TestOutputLogger.LogContext("TestScenario", "Test008_Should_Query_All_Management_Tokens_Async");
+ try
+ {
+ if (string.IsNullOrEmpty(_managementTokenUid))
+ {
+ Test001_Should_Create_Management_Token();
+ }
+
+ ContentstackResponse response = await _stack.ManagementTokens().Query().FindAsync();
+
+ AssertLogger.IsTrue(response.IsSuccessStatusCode, $"Async query management tokens failed: {response.OpenResponse()}", "AsyncQuerySuccess");
+
+ var responseObject = response.OpenJsonObjectResponse();
+ var tokens = responseObject["tokens"] as JsonArray;
+ AssertLogger.IsTrue(tokens.Count > 0, "Should have at least one management token", "AsyncTokensCount");
+ }
+ catch (Exception ex)
+ {
+ AssertLogger.Fail($"Async query management tokens test failed: {ex.Message}");
+ }
+ }
+
+ [TestMethod]
+ [DoNotParallelize]
+ public void Test009_Should_Delete_Management_Token()
+ {
+ TestOutputLogger.LogContext("TestScenario", "Test009_Should_Delete_Management_Token");
+ try
+ {
+ ContentstackResponse createResponse = _stack.ManagementTokens().Create(BuildValidTokenModel("Delete Test Management Token"));
+ AssertLogger.IsTrue(createResponse.IsSuccessStatusCode, "Setup create for delete test failed");
+ var tokenData = createResponse.OpenJsonObjectResponse()["token"] as JsonObject;
+ string tokenUidToDelete = tokenData["uid"]?.ToString();
+ AssertLogger.IsNotNull(tokenUidToDelete, "Should have a valid token UID to delete");
+
+ ContentstackResponse response = _stack.ManagementTokens(tokenUidToDelete).Delete();
+
+ AssertLogger.IsTrue(response.IsSuccessStatusCode, $"Delete management token failed: {response.OpenResponse()}", "DeleteSuccess");
+
+ AssertLogger.ThrowsContentstackError(
+ () => _stack.ManagementTokens(tokenUidToDelete).Fetch(),
+ "FetchDeletedManagementToken",
+ HttpStatusCode.NotFound,
+ (HttpStatusCode)422);
+ }
+ catch (Exception ex)
+ {
+ AssertLogger.Fail("Delete management token test failed", ex.Message);
+ }
+ }
+
+ [TestMethod]
+ [DoNotParallelize]
+ public async Task Test010_Should_Delete_Management_Token_Async()
+ {
+ TestOutputLogger.LogContext("TestScenario", "Test010_Should_Delete_Management_Token_Async");
+ try
+ {
+ ContentstackResponse createResponse = await _stack.ManagementTokens().CreateAsync(BuildValidTokenModel("Async Delete Test Management Token"));
+ AssertLogger.IsTrue(createResponse.IsSuccessStatusCode, "Setup create for async delete test failed");
+ var tokenData = createResponse.OpenJsonObjectResponse()["token"] as JsonObject;
+ string tokenUidToDelete = tokenData["uid"]?.ToString();
+ AssertLogger.IsNotNull(tokenUidToDelete, "Should have a valid token UID to delete");
+
+ ContentstackResponse response = await _stack.ManagementTokens(tokenUidToDelete).DeleteAsync();
+
+ AssertLogger.IsTrue(response.IsSuccessStatusCode, $"Async delete management token failed: {response.OpenResponse()}", "AsyncDeleteSuccess");
+
+ await AssertLogger.ThrowsContentstackErrorAsync(
+ async () => await _stack.ManagementTokens(tokenUidToDelete).FetchAsync(),
+ "AsyncFetchDeletedManagementToken",
+ HttpStatusCode.NotFound,
+ (HttpStatusCode)422);
+ }
+ catch (Exception ex)
+ {
+ AssertLogger.Fail("Async delete management token test failed", ex.Message);
+ }
+ }
+
+ #region Negative-path tests
+
+ [TestMethod]
+ [DoNotParallelize]
+ public void Test011_Should_Fail_Create_With_Null_Model()
+ {
+ TestOutputLogger.LogContext("TestScenario", "Test011_Should_Fail_Create_With_Null_Model");
+ AssertLogger.ThrowsException(
+ () => _stack.ManagementTokens().Create(null),
+ "CreateWithNullModel");
+ }
+
+ [TestMethod]
+ [DoNotParallelize]
+ public async Task Test012_Should_Fail_Create_With_Null_Model_Async()
+ {
+ TestOutputLogger.LogContext("TestScenario", "Test012_Should_Fail_Create_With_Null_Model_Async");
+ await AssertLogger.ThrowsExceptionAsync(
+ () => _stack.ManagementTokens().CreateAsync(null),
+ "CreateWithNullModelAsync");
+ }
+
+ [TestMethod]
+ [DoNotParallelize]
+ public void Test013_Should_Fail_Fetch_With_Null_Uid()
+ {
+ TestOutputLogger.LogContext("TestScenario", "Test013_Should_Fail_Fetch_With_Null_Uid");
+ AssertLogger.ThrowsException(
+ () => _stack.ManagementTokens(null).Fetch(),
+ "FetchWithNullUid");
+ }
+
+ [TestMethod]
+ [DoNotParallelize]
+ public void Test014_Should_Fail_Fetch_With_Empty_Uid()
+ {
+ TestOutputLogger.LogContext("TestScenario", "Test014_Should_Fail_Fetch_With_Empty_Uid");
+ AssertLogger.ThrowsException(
+ () => _stack.ManagementTokens("").Fetch(),
+ "FetchWithEmptyUid");
+ }
+
+ [TestMethod]
+ [DoNotParallelize]
+ public void Test015_Should_Fail_Fetch_NonExistent_Token()
+ {
+ TestOutputLogger.LogContext("TestScenario", "Test015_Should_Fail_Fetch_NonExistent_Token");
+ AssertLogger.ThrowsContentstackError(
+ () => _stack.ManagementTokens(NonExistentTokenUid).Fetch(),
+ "FetchNonExistentToken",
+ HttpStatusCode.NotFound,
+ (HttpStatusCode)422);
+ }
+
+ [TestMethod]
+ [DoNotParallelize]
+ public async Task Test016_Should_Fail_Fetch_NonExistent_Token_Async()
+ {
+ TestOutputLogger.LogContext("TestScenario", "Test016_Should_Fail_Fetch_NonExistent_Token_Async");
+ await AssertLogger.ThrowsContentstackErrorAsync(
+ async () => await _stack.ManagementTokens(NonExistentTokenUid).FetchAsync(),
+ "FetchNonExistentTokenAsync",
+ HttpStatusCode.NotFound,
+ (HttpStatusCode)422);
+ }
+
+ [TestMethod]
+ [DoNotParallelize]
+ public void Test017_Should_Fail_Update_With_Null_Model()
+ {
+ TestOutputLogger.LogContext("TestScenario", "Test017_Should_Fail_Update_With_Null_Model");
+ AssertLogger.ThrowsException(
+ () => _stack.ManagementTokens(NonExistentTokenUid).Update(null),
+ "UpdateWithNullModel");
+ }
+
+ [TestMethod]
+ [DoNotParallelize]
+ public void Test018_Should_Fail_Update_NonExistent_Token()
+ {
+ TestOutputLogger.LogContext("TestScenario", "Test018_Should_Fail_Update_NonExistent_Token");
+ var model = BuildValidTokenModel("Update Non Existent");
+ AssertLogger.ThrowsContentstackError(
+ () => _stack.ManagementTokens(NonExistentTokenUid).Update(model),
+ "UpdateNonExistentToken",
+ HttpStatusCode.NotFound,
+ (HttpStatusCode)422);
+ }
+
+ [TestMethod]
+ [DoNotParallelize]
+ public void Test019_Should_Fail_Delete_NonExistent_Token()
+ {
+ TestOutputLogger.LogContext("TestScenario", "Test019_Should_Fail_Delete_NonExistent_Token");
+ AssertLogger.ThrowsContentstackError(
+ () => _stack.ManagementTokens(NonExistentTokenUid).Delete(),
+ "DeleteNonExistentToken",
+ HttpStatusCode.NotFound,
+ (HttpStatusCode)422);
+ }
+
+ [TestMethod]
+ [DoNotParallelize]
+ public void Test020_Should_Fail_When_Stack_Api_Key_Missing()
+ {
+ TestOutputLogger.LogContext("TestScenario", "Test020_Should_Fail_When_Stack_Api_Key_Missing");
+ var stackWithNoApiKey = _client.Stack();
+ AssertLogger.ThrowsException(
+ () => stackWithNoApiKey.ManagementTokens(),
+ "ManagementTokensWithMissingApiKey");
+ }
+
+ [TestMethod]
+ [DoNotParallelize]
+ public void Test021_Should_Fail_Delete_With_Empty_Uid()
+ {
+ TestOutputLogger.LogContext("TestScenario", "Test021_Should_Fail_Delete_With_Empty_Uid");
+ AssertLogger.ThrowsException(
+ () => _stack.ManagementTokens("").Delete(),
+ "DeleteWithEmptyUid");
+ }
+
+ #endregion
+ }
+}
diff --git a/Contentstack.Management.Core.Tests/IntegrationTest/Contentstack999_LogoutTest.cs b/Contentstack.Management.Core.Tests/IntegrationTest/Contentstack999_LogoutTest.cs
index 8e1614e..e59b29d 100644
--- a/Contentstack.Management.Core.Tests/IntegrationTest/Contentstack999_LogoutTest.cs
+++ b/Contentstack.Management.Core.Tests/IntegrationTest/Contentstack999_LogoutTest.cs
@@ -3,14 +3,254 @@
using System.Net.Http;
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;
+using System.Text.Json.Nodes;
namespace Contentstack.Management.Core.Tests.IntegrationTest
{
[TestClass]
public class Contentstack999_LogoutTest
{
+ ///
+ /// Wipes all content from the dynamically-created stack before the Logout-behavior
+ /// tests below run, using a dedicated client so it can't interfere with those tests'
+ /// assertions about Authtoken state. The stack itself is kept (no Stack.Delete() exists
+ /// in this SDK) — only its content is removed, via query-and-delete-everything-found
+ /// since content here was created by many independent test classes with no shared
+ /// tracked-UID list.
+ ///
+ [ClassInitialize]
+ public static async Task ClassInitialize(TestContext context)
+ {
+ ContentstackClient cleanupClient = null;
+ try
+ {
+ cleanupClient = Contentstack.CreateAuthenticatedClient();
+
+ StackResponse stackResponse = StackResponse.getStack(cleanupClient.serializer);
+ string apiKey = stackResponse.Stack.APIKey;
+
+ string managementToken = null;
+ try
+ {
+ managementToken = ManagementTokenResponse.getManagementToken(cleanupClient.serializer)?.Token?.Token;
+ }
+ catch
+ {
+ // managementTokenInfo.txt missing — fall back to session-token-only stack.
+ }
+
+ Stack stack = string.IsNullOrEmpty(managementToken)
+ ? cleanupClient.Stack(apiKey)
+ : cleanupClient.Stack(apiKey, managementToken);
+
+ // Order matters: content types before taxonomies (taxonomies may be referenced
+ // by content type schema fields); management token deleted last (in case any
+ // step above ends up authenticated via it).
+ await CleanupAllContentTypes(stack);
+ await CleanupAllTaxonomies(stack);
+ await LogUncleanableVariantGroups(stack);
+ await CleanupAllAssets(stack);
+ await CleanupAllEnvironments(stack);
+ await CleanupAllReleases(stack);
+ await CleanupAllDeliveryTokens(stack);
+ await CleanupManagementToken(stack, managementToken);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"Stack-wide cleanup failed unexpectedly: {ex.Message}");
+ }
+ finally
+ {
+ try { cleanupClient?.Logout(); } catch { }
+ }
+ }
+
+ private static async Task CleanupAllContentTypes(Stack stack)
+ {
+ try
+ {
+ // ContentType.Delete() cascade-deletes the content type's entries too, so a
+ // separate entry-deletion pass is unnecessary.
+ var response = await stack.ContentType().Query().FindAsync();
+ var contentTypes = response.OpenJsonObjectResponse()["content_types"]?.AsArray();
+ if (contentTypes == null) return;
+
+ foreach (var ct in contentTypes)
+ {
+ string uid = ct?["uid"]?.ToString();
+ if (string.IsNullOrEmpty(uid)) continue;
+ try { await stack.ContentType(uid).DeleteAsync(); }
+ catch (Exception ex) { Console.WriteLine($"Cleanup: failed to delete content type '{uid}': {ex.Message}"); }
+ }
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"Cleanup: failed to query content types: {ex.Message}");
+ }
+ }
+
+ private static async Task CleanupAllTaxonomies(Stack stack)
+ {
+ try
+ {
+ var response = await stack.Taxonomy().Query().FindAsync();
+ var taxonomies = response.OpenJsonObjectResponse()["taxonomies"]?.AsArray();
+ if (taxonomies == null) return;
+
+ foreach (var taxonomy in taxonomies)
+ {
+ string uid = taxonomy?["uid"]?.ToString();
+ if (string.IsNullOrEmpty(uid)) continue;
+ try { await stack.Taxonomy(uid).DeleteAsync(); }
+ catch (Exception ex) { Console.WriteLine($"Cleanup: failed to delete taxonomy '{uid}': {ex.Message}"); }
+ }
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"Cleanup: failed to query taxonomies: {ex.Message}");
+ }
+ }
+
+ ///
+ /// VariantGroup has no Delete API in this SDK (only Find/LinkContentTypes/
+ /// UnlinkContentTypes) — these groups are provisioned by Personalize Experiences,
+ /// and this SDK has no Personalize wrapper either. Full cleanup of Personalize-
+ /// provisioned variant groups is a known limitation; this just logs what's left
+ /// behind so it's visible rather than silently incomplete.
+ ///
+ private static async Task LogUncleanableVariantGroups(Stack stack)
+ {
+ try
+ {
+ var response = await stack.VariantGroup().FindAsync();
+ var groups = response.OpenJsonObjectResponse()["variant_groups"]?.AsArray();
+ if (groups == null || groups.Count == 0) return;
+
+ Console.WriteLine(
+ $"Cleanup: {groups.Count} variant group(s) remain on the stack and could not be " +
+ "deleted (VariantGroup has no Delete API in this SDK; these are Personalize-provisioned).");
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"Cleanup: failed to query variant groups: {ex.Message}");
+ }
+ }
+
+ private static async Task CleanupAllAssets(Stack stack)
+ {
+ try
+ {
+ var response = await stack.Asset().Query().FindAsync();
+ var assets = response.OpenJsonObjectResponse()["assets"]?.AsArray();
+ if (assets == null) return;
+
+ foreach (var asset in assets)
+ {
+ string uid = asset?["uid"]?.ToString();
+ if (string.IsNullOrEmpty(uid)) continue;
+ try { await stack.Asset(uid).DeleteAsync(); }
+ catch (Exception ex) { Console.WriteLine($"Cleanup: failed to delete asset '{uid}': {ex.Message}"); }
+ }
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"Cleanup: failed to query assets: {ex.Message}");
+ }
+ }
+
+ private static async Task CleanupAllEnvironments(Stack stack)
+ {
+ try
+ {
+ var response = await stack.Environment().Query().FindAsync();
+ var environments = response.OpenJsonObjectResponse()["environments"]?.AsArray();
+ if (environments == null) return;
+
+ foreach (var environment in environments)
+ {
+ string uid = environment?["uid"]?.ToString();
+ if (string.IsNullOrEmpty(uid)) continue;
+ try { await stack.Environment(uid).DeleteAsync(); }
+ catch (Exception ex) { Console.WriteLine($"Cleanup: failed to delete environment '{uid}': {ex.Message}"); }
+ }
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"Cleanup: failed to query environments: {ex.Message}");
+ }
+ }
+
+ private static async Task CleanupAllReleases(Stack stack)
+ {
+ try
+ {
+ var response = await stack.Release().Query().FindAsync();
+ var releases = response.OpenJsonObjectResponse()["releases"]?.AsArray();
+ if (releases == null) return;
+
+ foreach (var release in releases)
+ {
+ string uid = release?["uid"]?.ToString();
+ if (string.IsNullOrEmpty(uid)) continue;
+ try { await stack.Release(uid).DeleteAsync(); }
+ catch (Exception ex) { Console.WriteLine($"Cleanup: failed to delete release '{uid}': {ex.Message}"); }
+ }
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"Cleanup: failed to query releases: {ex.Message}");
+ }
+ }
+
+ private static async Task CleanupAllDeliveryTokens(Stack stack)
+ {
+ try
+ {
+ var response = await stack.DeliveryToken().Query().FindAsync();
+ var tokens = response.OpenJsonObjectResponse()["tokens"]?.AsArray();
+ if (tokens == null) return;
+
+ foreach (var token in tokens)
+ {
+ string uid = token?["uid"]?.ToString();
+ if (string.IsNullOrEmpty(uid)) continue;
+ try { await stack.DeliveryToken(uid).DeleteAsync(); }
+ catch (Exception ex) { Console.WriteLine($"Cleanup: failed to delete delivery token '{uid}': {ex.Message}"); }
+ }
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"Cleanup: failed to query delivery tokens: {ex.Message}");
+ }
+ }
+
+ ///
+ /// Deletes the one global management token created for this run (see
+ /// Contentstack003_StackTest.Test065_Should_Create_Management_Token), last — in case
+ /// any prior cleanup step above ends up authenticated via this same token.
+ ///
+ private static async Task CleanupManagementToken(Stack stack, string managementToken)
+ {
+ if (string.IsNullOrEmpty(managementToken)) return;
+
+ try
+ {
+ var tokenInfo = ManagementTokenResponse.getManagementToken(stack.client.SerializerOptions);
+ string uid = tokenInfo?.Token?.Uid;
+ if (string.IsNullOrEmpty(uid)) return;
+
+ await stack.ManagementTokens(uid).DeleteAsync();
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"Cleanup: failed to delete management token: {ex.Message}");
+ }
+ }
+
private static ContentstackClient CreateClientWithLogging()
{
var handler = new LoggingHttpHandler();
diff --git a/Contentstack.Management.Core.Tests/Model/ManagementTokenTestModel.cs b/Contentstack.Management.Core.Tests/Model/ManagementTokenTestModel.cs
new file mode 100644
index 0000000..191358a
--- /dev/null
+++ b/Contentstack.Management.Core.Tests/Model/ManagementTokenTestModel.cs
@@ -0,0 +1,29 @@
+using System.IO;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace Contentstack.Management.Core.Tests.Model
+{
+ public class ManagementTokenInfo
+ {
+ [JsonPropertyName("uid")]
+ public string Uid { get; set; }
+
+ [JsonPropertyName("token")]
+ public string Token { get; set; }
+
+ [JsonPropertyName("name")]
+ public string Name { get; set; }
+ }
+
+ public class ManagementTokenResponse
+ {
+ public ManagementTokenInfo Token { get; set; }
+
+ public static ManagementTokenResponse getManagementToken(JsonSerializerOptions options)
+ {
+ string response = File.ReadAllText("./managementTokenInfo.txt");
+ return JsonSerializer.Deserialize(response, options);
+ }
+ }
+}
diff --git a/Contentstack.Management.Core/Models/Token/DeliveryTokenModel.cs b/Contentstack.Management.Core/Models/Token/DeliveryTokenModel.cs
index 763e0c1..6d9fe53 100644
--- a/Contentstack.Management.Core/Models/Token/DeliveryTokenModel.cs
+++ b/Contentstack.Management.Core/Models/Token/DeliveryTokenModel.cs
@@ -10,7 +10,7 @@ public class ManagementTokenModel
public string Name { get; set; }
[JsonPropertyName("description")]
public string Description { get; set; }
- [JsonPropertyName("Scope")]
+ [JsonPropertyName("scope")]
public List Scope { get; set; }
[JsonPropertyName("expires_on")]
public string ExpiresOn { get; set; }
diff --git a/Contentstack.Management.Core/Models/Token/ManagementToken.cs b/Contentstack.Management.Core/Models/Token/ManagementToken.cs
index 83f51b9..a1e5530 100644
--- a/Contentstack.Management.Core/Models/Token/ManagementToken.cs
+++ b/Contentstack.Management.Core/Models/Token/ManagementToken.cs
@@ -8,7 +8,7 @@ public class ManagementToken : BaseModel
internal ManagementToken(Stack stack, string? uid = null)
: base(stack, "token", uid)
{
- resourcePath = uid == null ? "/delivery_tokens" : $"/delivery_tokens/{uid}";
+ resourcePath = uid == null ? "stacks/management_tokens" : $"stacks/management_tokens/{uid}";
}
///
diff --git a/snyk.json b/snyk.json
deleted file mode 100644
index 1c9c23a..0000000
--- a/snyk.json
+++ /dev/null
@@ -1,650 +0,0 @@
-[
- {
- "vulnerabilities": [],
- "ok": true,
- "dependencyCount": 67,
- "org": "contentstack-devex",
- "policy": "# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities.\nversion: v1.25.1\nignore: {}\npatch: {}\n",
- "isPrivate": true,
- "licensesPolicy": {
- "severities": {},
- "orgLicenseRules": {
- "AGPL-1.0": {
- "licenseType": "AGPL-1.0",
- "severity": "high",
- "instructions": ""
- },
- "AGPL-3.0": {
- "licenseType": "AGPL-3.0",
- "severity": "high",
- "instructions": ""
- },
- "Artistic-1.0": {
- "licenseType": "Artistic-1.0",
- "severity": "medium",
- "instructions": ""
- },
- "Artistic-2.0": {
- "licenseType": "Artistic-2.0",
- "severity": "medium",
- "instructions": ""
- },
- "CDDL-1.0": {
- "licenseType": "CDDL-1.0",
- "severity": "medium",
- "instructions": ""
- },
- "CPOL-1.02": {
- "licenseType": "CPOL-1.02",
- "severity": "high",
- "instructions": ""
- },
- "EPL-1.0": {
- "licenseType": "EPL-1.0",
- "severity": "medium",
- "instructions": ""
- },
- "GPL-2.0": {
- "licenseType": "GPL-2.0",
- "severity": "high",
- "instructions": ""
- },
- "GPL-3.0": {
- "licenseType": "GPL-3.0",
- "severity": "high",
- "instructions": ""
- },
- "LGPL-2.0": {
- "licenseType": "LGPL-2.0",
- "severity": "medium",
- "instructions": ""
- },
- "LGPL-2.1": {
- "licenseType": "LGPL-2.1",
- "severity": "medium",
- "instructions": ""
- },
- "LGPL-3.0": {
- "licenseType": "LGPL-3.0",
- "severity": "medium",
- "instructions": ""
- },
- "MPL-1.1": {
- "licenseType": "MPL-1.1",
- "severity": "medium",
- "instructions": ""
- },
- "MPL-2.0": {
- "licenseType": "MPL-2.0",
- "severity": "medium",
- "instructions": ""
- },
- "MS-RL": {
- "licenseType": "MS-RL",
- "severity": "medium",
- "instructions": ""
- },
- "SimPL-2.0": {
- "licenseType": "SimPL-2.0",
- "severity": "high",
- "instructions": ""
- }
- }
- },
- "packageManager": "nuget",
- "ignoreSettings": {
- "adminOnly": false,
- "reasonRequired": false,
- "disregardFilesystemIgnores": false
- },
- "summary": "No known vulnerabilities",
- "filesystemPolicy": false,
- "uniqueCount": 0,
- "targetFile": "Contentstack.Management.ASPNETCore/obj/project.assets.json",
- "projectName": "contentstack-management-dotnet",
- "foundProjectCount": 5,
- "displayTargetFile": "Contentstack.Management.ASPNETCore/obj/project.assets.json",
- "hasUnknownVersions": false,
- "path": "/Users/om.pawar/Desktop/SDKs/contentstack-management-dotnet"
- },
- {
- "vulnerabilities": [],
- "ok": true,
- "dependencyCount": 121,
- "org": "contentstack-devex",
- "policy": "# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities.\nversion: v1.25.1\nignore: {}\npatch: {}\n",
- "isPrivate": true,
- "licensesPolicy": {
- "severities": {},
- "orgLicenseRules": {
- "AGPL-1.0": {
- "licenseType": "AGPL-1.0",
- "severity": "high",
- "instructions": ""
- },
- "AGPL-3.0": {
- "licenseType": "AGPL-3.0",
- "severity": "high",
- "instructions": ""
- },
- "Artistic-1.0": {
- "licenseType": "Artistic-1.0",
- "severity": "medium",
- "instructions": ""
- },
- "Artistic-2.0": {
- "licenseType": "Artistic-2.0",
- "severity": "medium",
- "instructions": ""
- },
- "CDDL-1.0": {
- "licenseType": "CDDL-1.0",
- "severity": "medium",
- "instructions": ""
- },
- "CPOL-1.02": {
- "licenseType": "CPOL-1.02",
- "severity": "high",
- "instructions": ""
- },
- "EPL-1.0": {
- "licenseType": "EPL-1.0",
- "severity": "medium",
- "instructions": ""
- },
- "GPL-2.0": {
- "licenseType": "GPL-2.0",
- "severity": "high",
- "instructions": ""
- },
- "GPL-3.0": {
- "licenseType": "GPL-3.0",
- "severity": "high",
- "instructions": ""
- },
- "LGPL-2.0": {
- "licenseType": "LGPL-2.0",
- "severity": "medium",
- "instructions": ""
- },
- "LGPL-2.1": {
- "licenseType": "LGPL-2.1",
- "severity": "medium",
- "instructions": ""
- },
- "LGPL-3.0": {
- "licenseType": "LGPL-3.0",
- "severity": "medium",
- "instructions": ""
- },
- "MPL-1.1": {
- "licenseType": "MPL-1.1",
- "severity": "medium",
- "instructions": ""
- },
- "MPL-2.0": {
- "licenseType": "MPL-2.0",
- "severity": "medium",
- "instructions": ""
- },
- "MS-RL": {
- "licenseType": "MS-RL",
- "severity": "medium",
- "instructions": ""
- },
- "SimPL-2.0": {
- "licenseType": "SimPL-2.0",
- "severity": "high",
- "instructions": ""
- }
- }
- },
- "packageManager": "nuget",
- "ignoreSettings": {
- "adminOnly": false,
- "reasonRequired": false,
- "disregardFilesystemIgnores": false
- },
- "summary": "No known vulnerabilities",
- "filesystemPolicy": false,
- "uniqueCount": 0,
- "targetFile": "Contentstack.Management.Core.Tests/obj/project.assets.json",
- "projectName": "contentstack-management-dotnet",
- "foundProjectCount": 5,
- "displayTargetFile": "Contentstack.Management.Core.Tests/obj/project.assets.json",
- "hasUnknownVersions": false,
- "path": "/Users/om.pawar/Desktop/SDKs/contentstack-management-dotnet"
- },
- {
- "vulnerabilities": [],
- "ok": true,
- "dependencyCount": 109,
- "org": "contentstack-devex",
- "policy": "# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities.\nversion: v1.25.1\nignore: {}\npatch: {}\n",
- "isPrivate": true,
- "licensesPolicy": {
- "severities": {},
- "orgLicenseRules": {
- "AGPL-1.0": {
- "licenseType": "AGPL-1.0",
- "severity": "high",
- "instructions": ""
- },
- "AGPL-3.0": {
- "licenseType": "AGPL-3.0",
- "severity": "high",
- "instructions": ""
- },
- "Artistic-1.0": {
- "licenseType": "Artistic-1.0",
- "severity": "medium",
- "instructions": ""
- },
- "Artistic-2.0": {
- "licenseType": "Artistic-2.0",
- "severity": "medium",
- "instructions": ""
- },
- "CDDL-1.0": {
- "licenseType": "CDDL-1.0",
- "severity": "medium",
- "instructions": ""
- },
- "CPOL-1.02": {
- "licenseType": "CPOL-1.02",
- "severity": "high",
- "instructions": ""
- },
- "EPL-1.0": {
- "licenseType": "EPL-1.0",
- "severity": "medium",
- "instructions": ""
- },
- "GPL-2.0": {
- "licenseType": "GPL-2.0",
- "severity": "high",
- "instructions": ""
- },
- "GPL-3.0": {
- "licenseType": "GPL-3.0",
- "severity": "high",
- "instructions": ""
- },
- "LGPL-2.0": {
- "licenseType": "LGPL-2.0",
- "severity": "medium",
- "instructions": ""
- },
- "LGPL-2.1": {
- "licenseType": "LGPL-2.1",
- "severity": "medium",
- "instructions": ""
- },
- "LGPL-3.0": {
- "licenseType": "LGPL-3.0",
- "severity": "medium",
- "instructions": ""
- },
- "MPL-1.1": {
- "licenseType": "MPL-1.1",
- "severity": "medium",
- "instructions": ""
- },
- "MPL-2.0": {
- "licenseType": "MPL-2.0",
- "severity": "medium",
- "instructions": ""
- },
- "MS-RL": {
- "licenseType": "MS-RL",
- "severity": "medium",
- "instructions": ""
- },
- "SimPL-2.0": {
- "licenseType": "SimPL-2.0",
- "severity": "high",
- "instructions": ""
- }
- }
- },
- "packageManager": "nuget",
- "ignoreSettings": {
- "adminOnly": false,
- "reasonRequired": false,
- "disregardFilesystemIgnores": false
- },
- "summary": "No known vulnerabilities",
- "filesystemPolicy": false,
- "uniqueCount": 0,
- "targetFile": "Contentstack.Management.Core.Unit.Tests/obj/project.assets.json",
- "projectName": "contentstack-management-dotnet",
- "foundProjectCount": 5,
- "displayTargetFile": "Contentstack.Management.Core.Unit.Tests/obj/project.assets.json",
- "hasUnknownVersions": false,
- "path": "/Users/om.pawar/Desktop/SDKs/contentstack-management-dotnet"
- },
- {
- "vulnerabilities": [],
- "ok": true,
- "dependencyCount": 63,
- "org": "contentstack-devex",
- "policy": "# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities.\nversion: v1.25.1\nignore: {}\npatch: {}\n",
- "isPrivate": true,
- "licensesPolicy": {
- "severities": {},
- "orgLicenseRules": {
- "AGPL-1.0": {
- "licenseType": "AGPL-1.0",
- "severity": "high",
- "instructions": ""
- },
- "AGPL-3.0": {
- "licenseType": "AGPL-3.0",
- "severity": "high",
- "instructions": ""
- },
- "Artistic-1.0": {
- "licenseType": "Artistic-1.0",
- "severity": "medium",
- "instructions": ""
- },
- "Artistic-2.0": {
- "licenseType": "Artistic-2.0",
- "severity": "medium",
- "instructions": ""
- },
- "CDDL-1.0": {
- "licenseType": "CDDL-1.0",
- "severity": "medium",
- "instructions": ""
- },
- "CPOL-1.02": {
- "licenseType": "CPOL-1.02",
- "severity": "high",
- "instructions": ""
- },
- "EPL-1.0": {
- "licenseType": "EPL-1.0",
- "severity": "medium",
- "instructions": ""
- },
- "GPL-2.0": {
- "licenseType": "GPL-2.0",
- "severity": "high",
- "instructions": ""
- },
- "GPL-3.0": {
- "licenseType": "GPL-3.0",
- "severity": "high",
- "instructions": ""
- },
- "LGPL-2.0": {
- "licenseType": "LGPL-2.0",
- "severity": "medium",
- "instructions": ""
- },
- "LGPL-2.1": {
- "licenseType": "LGPL-2.1",
- "severity": "medium",
- "instructions": ""
- },
- "LGPL-3.0": {
- "licenseType": "LGPL-3.0",
- "severity": "medium",
- "instructions": ""
- },
- "MPL-1.1": {
- "licenseType": "MPL-1.1",
- "severity": "medium",
- "instructions": ""
- },
- "MPL-2.0": {
- "licenseType": "MPL-2.0",
- "severity": "medium",
- "instructions": ""
- },
- "MS-RL": {
- "licenseType": "MS-RL",
- "severity": "medium",
- "instructions": ""
- },
- "SimPL-2.0": {
- "licenseType": "SimPL-2.0",
- "severity": "high",
- "instructions": ""
- }
- }
- },
- "packageManager": "nuget",
- "ignoreSettings": {
- "adminOnly": false,
- "reasonRequired": false,
- "disregardFilesystemIgnores": false
- },
- "summary": "No known vulnerabilities",
- "filesystemPolicy": false,
- "uniqueCount": 0,
- "targetFile": "Contentstack.Management.Core/obj/project.assets.json",
- "projectName": "contentstack-management-dotnet",
- "foundProjectCount": 5,
- "displayTargetFile": "Contentstack.Management.Core/obj/project.assets.json",
- "hasUnknownVersions": false,
- "path": "/Users/om.pawar/Desktop/SDKs/contentstack-management-dotnet"
- },
- {
- "vulnerabilities": [],
- "ok": true,
- "dependencyCount": 0,
- "org": "contentstack-devex",
- "policy": "# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities.\nversion: v1.25.1\nignore: {}\npatch: {}\n",
- "isPrivate": true,
- "licensesPolicy": {
- "severities": {},
- "orgLicenseRules": {
- "AGPL-1.0": {
- "licenseType": "AGPL-1.0",
- "severity": "high",
- "instructions": ""
- },
- "AGPL-3.0": {
- "licenseType": "AGPL-3.0",
- "severity": "high",
- "instructions": ""
- },
- "Artistic-1.0": {
- "licenseType": "Artistic-1.0",
- "severity": "medium",
- "instructions": ""
- },
- "Artistic-2.0": {
- "licenseType": "Artistic-2.0",
- "severity": "medium",
- "instructions": ""
- },
- "CDDL-1.0": {
- "licenseType": "CDDL-1.0",
- "severity": "medium",
- "instructions": ""
- },
- "CPOL-1.02": {
- "licenseType": "CPOL-1.02",
- "severity": "high",
- "instructions": ""
- },
- "EPL-1.0": {
- "licenseType": "EPL-1.0",
- "severity": "medium",
- "instructions": ""
- },
- "GPL-2.0": {
- "licenseType": "GPL-2.0",
- "severity": "high",
- "instructions": ""
- },
- "GPL-3.0": {
- "licenseType": "GPL-3.0",
- "severity": "high",
- "instructions": ""
- },
- "LGPL-2.0": {
- "licenseType": "LGPL-2.0",
- "severity": "medium",
- "instructions": ""
- },
- "LGPL-2.1": {
- "licenseType": "LGPL-2.1",
- "severity": "medium",
- "instructions": ""
- },
- "LGPL-3.0": {
- "licenseType": "LGPL-3.0",
- "severity": "medium",
- "instructions": ""
- },
- "MPL-1.1": {
- "licenseType": "MPL-1.1",
- "severity": "medium",
- "instructions": ""
- },
- "MPL-2.0": {
- "licenseType": "MPL-2.0",
- "severity": "medium",
- "instructions": ""
- },
- "MS-RL": {
- "licenseType": "MS-RL",
- "severity": "medium",
- "instructions": ""
- },
- "SimPL-2.0": {
- "licenseType": "SimPL-2.0",
- "severity": "high",
- "instructions": ""
- }
- }
- },
- "packageManager": "nuget",
- "ignoreSettings": {
- "adminOnly": false,
- "reasonRequired": false,
- "disregardFilesystemIgnores": false
- },
- "summary": "No known vulnerabilities",
- "filesystemPolicy": false,
- "uniqueCount": 0,
- "targetFile": "tools/EnhancedTestReport/obj/project.assets.json",
- "projectName": "contentstack-management-dotnet",
- "foundProjectCount": 5,
- "displayTargetFile": "tools/EnhancedTestReport/obj/project.assets.json",
- "hasUnknownVersions": false,
- "path": "/Users/om.pawar/Desktop/SDKs/contentstack-management-dotnet"
- },
- {
- "vulnerabilities": [],
- "ok": true,
- "dependencyCount": 0,
- "org": "contentstack-devex",
- "policy": "# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities.\nversion: v1.25.1\nignore: {}\npatch: {}\n",
- "isPrivate": true,
- "licensesPolicy": {
- "severities": {},
- "orgLicenseRules": {
- "AGPL-1.0": {
- "licenseType": "AGPL-1.0",
- "severity": "high",
- "instructions": ""
- },
- "AGPL-3.0": {
- "licenseType": "AGPL-3.0",
- "severity": "high",
- "instructions": ""
- },
- "Artistic-1.0": {
- "licenseType": "Artistic-1.0",
- "severity": "medium",
- "instructions": ""
- },
- "Artistic-2.0": {
- "licenseType": "Artistic-2.0",
- "severity": "medium",
- "instructions": ""
- },
- "CDDL-1.0": {
- "licenseType": "CDDL-1.0",
- "severity": "medium",
- "instructions": ""
- },
- "CPOL-1.02": {
- "licenseType": "CPOL-1.02",
- "severity": "high",
- "instructions": ""
- },
- "EPL-1.0": {
- "licenseType": "EPL-1.0",
- "severity": "medium",
- "instructions": ""
- },
- "GPL-2.0": {
- "licenseType": "GPL-2.0",
- "severity": "high",
- "instructions": ""
- },
- "GPL-3.0": {
- "licenseType": "GPL-3.0",
- "severity": "high",
- "instructions": ""
- },
- "LGPL-2.0": {
- "licenseType": "LGPL-2.0",
- "severity": "medium",
- "instructions": ""
- },
- "LGPL-2.1": {
- "licenseType": "LGPL-2.1",
- "severity": "medium",
- "instructions": ""
- },
- "LGPL-3.0": {
- "licenseType": "LGPL-3.0",
- "severity": "medium",
- "instructions": ""
- },
- "MPL-1.1": {
- "licenseType": "MPL-1.1",
- "severity": "medium",
- "instructions": ""
- },
- "MPL-2.0": {
- "licenseType": "MPL-2.0",
- "severity": "medium",
- "instructions": ""
- },
- "MS-RL": {
- "licenseType": "MS-RL",
- "severity": "medium",
- "instructions": ""
- },
- "SimPL-2.0": {
- "licenseType": "SimPL-2.0",
- "severity": "high",
- "instructions": ""
- }
- }
- },
- "packageManager": "nuget",
- "ignoreSettings": {
- "adminOnly": false,
- "reasonRequired": false,
- "disregardFilesystemIgnores": false
- },
- "summary": "No known vulnerabilities",
- "filesystemPolicy": false,
- "uniqueCount": 0,
- "targetFile": "tools/IntegrationTestReportGenerator/obj/project.assets.json",
- "projectName": "contentstack-management-dotnet",
- "foundProjectCount": 5,
- "displayTargetFile": "tools/IntegrationTestReportGenerator/obj/project.assets.json",
- "hasUnknownVersions": false,
- "path": "/Users/om.pawar/Desktop/SDKs/contentstack-management-dotnet"
- }
-]