diff --git a/.github/workflows/unit-test.yml b/.github/workflows/unit-test.yml index bfa8945..a5200f7 100644 --- a/.github/workflows/unit-test.yml +++ b/.github/workflows/unit-test.yml @@ -20,4 +20,10 @@ jobs: name: DotNet unit Tests path: ./Contentstack.Management.Core.Unit.Tests/TestResults/Report-Contentstack-DotNet-Test-Case.trx reporter: dotnet-trx - fail-on-error: true \ No newline at end of file + fail-on-error: true + - name: Upload enhanced test report + uses: actions/upload-artifact@v4 + if: success() || failure() + with: + name: enhanced-test-report + path: Contentstack.Management.Core.Unit.Tests/TestResults/EnhancedReport-Contentstack-DotNet-Test-Case.html \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index ee2230a..62ca689 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/Contentstack.Management.Core.Tests/Contentstack.Management.Core.Tests.csproj b/Contentstack.Management.Core.Tests/Contentstack.Management.Core.Tests.csproj index f8be953..4ee1a12 100644 --- a/Contentstack.Management.Core.Tests/Contentstack.Management.Core.Tests.csproj +++ b/Contentstack.Management.Core.Tests/Contentstack.Management.Core.Tests.csproj @@ -4,7 +4,7 @@ net7.0 false - $(Version) + 0.1.3 true ../CSManagementSDK.snk @@ -24,6 +24,7 @@ + @@ -35,11 +36,6 @@ - - - PreserveNewest - - diff --git a/Contentstack.Management.Core.Tests/IntegrationTest/Contentstack001_LoginTest.cs b/Contentstack.Management.Core.Tests/IntegrationTest/Contentstack001_LoginTest.cs index 16aaed8..6b4eda0 100644 --- a/Contentstack.Management.Core.Tests/IntegrationTest/Contentstack001_LoginTest.cs +++ b/Contentstack.Management.Core.Tests/IntegrationTest/Contentstack001_LoginTest.cs @@ -1,4 +1,5 @@ -using System; +using System; +using System.Diagnostics; using System.Net; using Contentstack.Management.Core.Exceptions; using Contentstack.Management.Core.Models; @@ -15,23 +16,51 @@ namespace Contentstack.Management.Core.Tests.IntegrationTest public class Contentstack001_LoginTest { private readonly IConfigurationRoot _configuration = new ConfigurationBuilder().AddJsonFile("appsettings.json").Build(); + + private static string _host => Contentstack.Client.contentstackOptions.Host; + [TestMethod] [DoNotParallelize] public void Test001_Should_Return_Failuer_On_Wrong_Login_Credentials() { - ContentstackClient client = new ContentstackClient(); - NetworkCredential credentials = new NetworkCredential("mock_user", "mock_pasword"); - + TestReportHelper.Begin(); + var sw = Stopwatch.StartNew(); try { - ContentstackResponse contentstackResponse = client.Login(credentials); - } catch (Exception e) + ContentstackClient client = new ContentstackClient(); + NetworkCredential credentials = new NetworkCredential("mock_user", "mock_pasword"); + + TestReportHelper.LogRequest("client.Login()", "POST", + $"https://{_host}/v3/user-session"); + try + { + ContentstackResponse contentstackResponse = client.Login(credentials); + } + catch (Exception e) + { + sw.Stop(); + ContentstackErrorException errorException = e as ContentstackErrorException; + TestReportHelper.LogAssertion(errorException?.StatusCode == HttpStatusCode.UnprocessableEntity, + "Status code is UnprocessableEntity", + expected: "UnprocessableEntity", actual: errorException?.StatusCode.ToString(), type: "AreEqual"); + TestReportHelper.LogAssertion(errorException?.ErrorCode == 104, + "Error code is 104", + expected: "104", actual: errorException?.ErrorCode.ToString(), type: "AreEqual"); + Assert.AreEqual(HttpStatusCode.UnprocessableEntity, errorException.StatusCode); + Assert.AreEqual("Looks like your email or password is invalid. Please try again or reset your password.", errorException.Message); + Assert.AreEqual("Looks like your email or password is invalid. Please try again or reset your password.", errorException.ErrorMessage); + Assert.AreEqual(104, errorException.ErrorCode); + } + } + catch (Exception e) { - ContentstackErrorException errorException = e as ContentstackErrorException; - Assert.AreEqual(HttpStatusCode.UnprocessableEntity, errorException.StatusCode); - Assert.AreEqual("Looks like your email or password is invalid. Please try again or reset your password.", errorException.Message); - Assert.AreEqual("Looks like your email or password is invalid. Please try again or reset your password.", errorException.ErrorMessage); - Assert.AreEqual(104, errorException.ErrorCode); + sw.Stop(); + TestReportHelper.LogAssertion(false, $"Unexpected exception: {e.GetType().Name}", type: "Fail"); + Assert.Fail(e.Message); + } + finally + { + TestReportHelper.Flush(); } } @@ -39,132 +68,232 @@ public void Test001_Should_Return_Failuer_On_Wrong_Login_Credentials() [DoNotParallelize] public void Test002_Should_Return_Failuer_On_Wrong_Async_Login_Credentials() { - ContentstackClient client = new ContentstackClient(); - NetworkCredential credentials = new NetworkCredential("mock_user", "mock_pasword"); - var response = client.LoginAsync(credentials); - - response.ContinueWith((t) => + TestReportHelper.Begin(); + var sw = Stopwatch.StartNew(); + try { - if (t.IsCompleted && t.Status == System.Threading.Tasks.TaskStatus.Faulted) + ContentstackClient client = new ContentstackClient(); + NetworkCredential credentials = new NetworkCredential("mock_user", "mock_pasword"); + + TestReportHelper.LogRequest("client.LoginAsync()", "POST", + $"https://{_host}/v3/user-session"); + + var response = client.LoginAsync(credentials); + response.ContinueWith((t) => { - ContentstackErrorException errorException = t.Exception.InnerException as ContentstackErrorException; - Assert.AreEqual(HttpStatusCode.UnprocessableEntity, errorException.StatusCode); - Assert.AreEqual("Looks like your email or password is invalid. Please try again or reset your password.", errorException.Message); - Assert.AreEqual("Looks like your email or password is invalid. Please try again or reset your password.", errorException.ErrorMessage); - Assert.AreEqual(104, errorException.ErrorCode); - } - }); - Thread.Sleep(3000); + if (t.IsCompleted && t.Status == System.Threading.Tasks.TaskStatus.Faulted) + { + ContentstackErrorException errorException = t.Exception.InnerException as ContentstackErrorException; + Assert.AreEqual(HttpStatusCode.UnprocessableEntity, errorException.StatusCode); + Assert.AreEqual("Looks like your email or password is invalid. Please try again or reset your password.", errorException.Message); + Assert.AreEqual("Looks like your email or password is invalid. Please try again or reset your password.", errorException.ErrorMessage); + Assert.AreEqual(104, errorException.ErrorCode); + } + }); + Thread.Sleep(3000); + sw.Stop(); + TestReportHelper.LogAssertion(true, "Async login with wrong credentials handled via ContinueWith", type: "IsTrue"); + } + catch (Exception e) + { + sw.Stop(); + TestReportHelper.LogAssertion(false, $"Unexpected exception: {e.GetType().Name}", type: "Fail"); + Assert.Fail(e.Message); + } + finally + { + TestReportHelper.Flush(); + } } [TestMethod] [DoNotParallelize] public async System.Threading.Tasks.Task Test003_Should_Return_Success_On_Async_Login() { - ContentstackClient client = new ContentstackClient(); - + TestReportHelper.Begin(); + var sw = Stopwatch.StartNew(); try { - ContentstackResponse contentstackResponse = await client.LoginAsync(Contentstack.Credential); - string loginResponse = contentstackResponse.OpenResponse(); + ContentstackClient client = new ContentstackClient(); + TestReportHelper.LogRequest("client.LoginAsync()", "POST", + $"https://{_host}/v3/user-session"); + + ContentstackResponse contentstackResponse = await client.LoginAsync(Contentstack.Credential); + sw.Stop(); + var body = contentstackResponse.OpenResponse(); + TestReportHelper.LogResponse((int)contentstackResponse.StatusCode, + contentstackResponse.StatusCode.ToString(), sw.ElapsedMilliseconds, body); + + string loginResponse = body; + + TestReportHelper.LogAssertion(client.contentstackOptions.Authtoken != null, + "Authtoken is not null", type: "IsNotNull"); + TestReportHelper.LogAssertion(loginResponse != null, "Login response is not null", type: "IsNotNull"); Assert.IsNotNull(client.contentstackOptions.Authtoken); Assert.IsNotNull(loginResponse); - + await client.LogoutAsync(); } catch (Exception e) { + sw.Stop(); + TestReportHelper.LogAssertion(false, $"Exception: {e.GetType().Name} — {e.Message}", type: "Fail"); Assert.Fail(e.Message); } + finally + { + TestReportHelper.Flush(); + } } [TestMethod] [DoNotParallelize] public void Test004_Should_Return_Success_On_Login() { + TestReportHelper.Begin(); + var sw = Stopwatch.StartNew(); try { ContentstackClient client = new ContentstackClient(); - + + TestReportHelper.LogRequest("client.Login()", "POST", + $"https://{_host}/v3/user-session"); + ContentstackResponse contentstackResponse = client.Login(Contentstack.Credential); - string loginResponse = contentstackResponse.OpenResponse(); + sw.Stop(); + var body = contentstackResponse.OpenResponse(); + TestReportHelper.LogResponse((int)contentstackResponse.StatusCode, + contentstackResponse.StatusCode.ToString(), sw.ElapsedMilliseconds, body); + string loginResponse = body; + + TestReportHelper.LogAssertion(client.contentstackOptions.Authtoken != null, "Authtoken not null", type: "IsNotNull"); + TestReportHelper.LogAssertion(loginResponse != null, "Login response not null", type: "IsNotNull"); Assert.IsNotNull(client.contentstackOptions.Authtoken); Assert.IsNotNull(loginResponse); } catch (Exception e) { + sw.Stop(); + TestReportHelper.LogAssertion(false, $"Exception: {e.GetType().Name} — {e.Message}", type: "Fail"); Assert.Fail(e.Message); } + finally + { + TestReportHelper.Flush(); + } } [TestMethod] [DoNotParallelize] public void Test005_Should_Return_Loggedin_User() { + TestReportHelper.Begin(); + var sw = Stopwatch.StartNew(); try { ContentstackClient client = new ContentstackClient(); - client.Login(Contentstack.Credential); - + + TestReportHelper.LogRequest("client.GetUser()", "GET", + $"https://{_host}/v3/user"); + ContentstackResponse response = client.GetUser(); + sw.Stop(); + var body = response.OpenResponse(); + TestReportHelper.LogResponse((int)response.StatusCode, + response.StatusCode.ToString(), sw.ElapsedMilliseconds, body); var user = response.OpenJObjectResponse(); + TestReportHelper.LogAssertion(user != null, "User response not null", type: "IsNotNull"); Assert.IsNotNull(user); - } catch (Exception e) { + sw.Stop(); + TestReportHelper.LogAssertion(false, $"Exception: {e.GetType().Name} — {e.Message}", type: "Fail"); Assert.Fail(e.Message); } + finally + { + TestReportHelper.Flush(); + } } [TestMethod] [DoNotParallelize] public async System.Threading.Tasks.Task Test006_Should_Return_Loggedin_User_Async() { + TestReportHelper.Begin(); + var sw = Stopwatch.StartNew(); try { ContentstackClient client = new ContentstackClient(); - await client.LoginAsync(Contentstack.Credential); - + + TestReportHelper.LogRequest("client.GetUserAsync()", "GET", + $"https://{_host}/v3/user"); + ContentstackResponse response = await client.GetUserAsync(); + sw.Stop(); + var body = response.OpenResponse(); + TestReportHelper.LogResponse((int)response.StatusCode, + response.StatusCode.ToString(), sw.ElapsedMilliseconds, body); var user = response.OpenJObjectResponse(); + TestReportHelper.LogAssertion(user != null, "User not null", type: "IsNotNull"); + TestReportHelper.LogAssertion(user["user"]["organizations"] != null, "Organizations not null", type: "IsNotNull"); + TestReportHelper.LogAssertion(user["user"]["organizations"] is JArray, "Organizations is JArray", type: "IsInstanceOfType"); + TestReportHelper.LogAssertion(user["user"]["organizations"][0]["org_roles"] == null, "org_roles is null", type: "IsNull"); Assert.IsNotNull(user); Assert.IsNotNull(user["user"]["organizations"]); Assert.IsInstanceOfType(user["user"]["organizations"], typeof(JArray)); Assert.IsNull(user["user"]["organizations"][0]["org_roles"]); - } catch (Exception e) { + sw.Stop(); + TestReportHelper.LogAssertion(false, $"Exception: {e.GetType().Name} — {e.Message}", type: "Fail"); Assert.Fail(e.Message); } + finally + { + TestReportHelper.Flush(); + } } [TestMethod] [DoNotParallelize] public void Test007_Should_Return_Loggedin_User_With_Organizations_detail() { + TestReportHelper.Begin(); + var sw = Stopwatch.StartNew(); try { ParameterCollection collection = new ParameterCollection(); collection.Add("include_orgs_roles", true); - + ContentstackClient client = new ContentstackClient(); - client.Login(Contentstack.Credential); - + + TestReportHelper.LogRequest("client.GetUser(collection)", "GET", + $"https://{_host}/v3/user", + queryParams: new System.Collections.Generic.Dictionary { ["include_orgs_roles"] = "true" }); + ContentstackResponse response = client.GetUser(collection); + sw.Stop(); + var body = response.OpenResponse(); + TestReportHelper.LogResponse((int)response.StatusCode, + response.StatusCode.ToString(), sw.ElapsedMilliseconds, body); var user = response.OpenJObjectResponse(); + TestReportHelper.LogAssertion(user != null, "User not null", type: "IsNotNull"); + TestReportHelper.LogAssertion(user["user"]["organizations"] != null, "Organizations not null", type: "IsNotNull"); + TestReportHelper.LogAssertion(user["user"]["organizations"][0]["org_roles"] != null, "org_roles not null", type: "IsNotNull"); Assert.IsNotNull(user); Assert.IsNotNull(user["user"]["organizations"]); Assert.IsInstanceOfType(user["user"]["organizations"], typeof(JArray)); @@ -172,31 +301,58 @@ public void Test007_Should_Return_Loggedin_User_With_Organizations_detail() } catch (Exception e) { + sw.Stop(); + TestReportHelper.LogAssertion(false, $"Exception: {e.GetType().Name} — {e.Message}", type: "Fail"); Assert.Fail(e.Message); } + finally + { + TestReportHelper.Flush(); + } } [TestMethod] [DoNotParallelize] public void Test008_Should_Fail_Login_With_Invalid_MfaSecret() { - ContentstackClient client = new ContentstackClient(); - NetworkCredential credentials = new NetworkCredential("test_user", "test_password"); - string invalidMfaSecret = "INVALID_BASE32_SECRET!@#"; - + TestReportHelper.Begin(); + var sw = Stopwatch.StartNew(); try { - ContentstackResponse contentstackResponse = client.Login(credentials, null, invalidMfaSecret); - Assert.Fail("Expected exception for invalid MFA secret"); + ContentstackClient client = new ContentstackClient(); + NetworkCredential credentials = new NetworkCredential("test_user", "test_password"); + string invalidMfaSecret = "INVALID_BASE32_SECRET!@#"; + + TestReportHelper.LogRequest("client.Login() with invalid MFA secret", "POST", + $"https://{_host}/v3/user-session"); + + try + { + ContentstackResponse contentstackResponse = client.Login(credentials, null, invalidMfaSecret); + sw.Stop(); + Assert.Fail("Expected exception for invalid MFA secret"); + } + catch (ArgumentException) + { + sw.Stop(); + TestReportHelper.LogAssertion(true, "ArgumentException thrown for invalid Base32 MFA secret", type: "IsTrue"); + Assert.IsTrue(true); + } + catch (Exception e) + { + sw.Stop(); + Assert.Fail($"Unexpected exception type: {e.GetType().Name} - {e.Message}"); + } } - catch (ArgumentException) + catch (Exception e) { - // Expected exception for invalid Base32 encoding - Assert.IsTrue(true); + sw.Stop(); + TestReportHelper.LogAssertion(false, $"Exception: {e.GetType().Name} — {e.Message}", type: "Fail"); + Assert.Fail(e.Message); } - catch (Exception e) + finally { - Assert.Fail($"Unexpected exception type: {e.GetType().Name} - {e.Message}"); + TestReportHelper.Flush(); } } @@ -204,31 +360,54 @@ public void Test008_Should_Fail_Login_With_Invalid_MfaSecret() [DoNotParallelize] public void Test009_Should_Generate_TOTP_Token_With_Valid_MfaSecret() { - ContentstackClient client = new ContentstackClient(); - NetworkCredential credentials = new NetworkCredential("test_user", "test_password"); - string validMfaSecret = "JBSWY3DPEHPK3PXP"; // Valid Base32 test secret - + TestReportHelper.Begin(); + var sw = Stopwatch.StartNew(); try { - // This should fail due to invalid credentials, but should succeed in generating TOTP - ContentstackResponse contentstackResponse = client.Login(credentials, null, validMfaSecret); - } - catch (ContentstackErrorException errorException) - { - // Expected to fail due to invalid credentials, but we verify it processed the MFA secret - // The error should be about credentials, not about MFA secret format - Assert.AreEqual(HttpStatusCode.UnprocessableEntity, errorException.StatusCode); - Assert.IsTrue(errorException.Message.Contains("email or password") || - errorException.Message.Contains("credentials") || - errorException.Message.Contains("authentication")); + ContentstackClient client = new ContentstackClient(); + NetworkCredential credentials = new NetworkCredential("test_user", "test_password"); + string validMfaSecret = "JBSWY3DPEHPK3PXP"; + + TestReportHelper.LogRequest("client.Login() with valid MFA secret", "POST", + $"https://{_host}/v3/user-session"); + + try + { + ContentstackResponse contentstackResponse = client.Login(credentials, null, validMfaSecret); + sw.Stop(); + } + catch (ContentstackErrorException errorException) + { + sw.Stop(); + TestReportHelper.LogAssertion( + errorException.StatusCode == HttpStatusCode.UnprocessableEntity, + "Status code is UnprocessableEntity (credentials rejected, not MFA format)", + expected: "UnprocessableEntity", actual: errorException.StatusCode.ToString(), type: "AreEqual"); + Assert.AreEqual(HttpStatusCode.UnprocessableEntity, errorException.StatusCode); + Assert.IsTrue(errorException.Message.Contains("email or password") || + errorException.Message.Contains("credentials") || + errorException.Message.Contains("authentication")); + } + catch (ArgumentException) + { + sw.Stop(); + Assert.Fail("Should not throw ArgumentException for valid MFA secret"); + } + catch (Exception e) + { + sw.Stop(); + Assert.Fail($"Unexpected exception type: {e.GetType().Name} - {e.Message}"); + } } - catch (ArgumentException) + catch (Exception e) { - Assert.Fail("Should not throw ArgumentException for valid MFA secret"); + sw.Stop(); + TestReportHelper.LogAssertion(false, $"Exception: {e.GetType().Name} — {e.Message}", type: "Fail"); + Assert.Fail(e.Message); } - catch (Exception e) + finally { - Assert.Fail($"Unexpected exception type: {e.GetType().Name} - {e.Message}"); + TestReportHelper.Flush(); } } @@ -236,31 +415,54 @@ public void Test009_Should_Generate_TOTP_Token_With_Valid_MfaSecret() [DoNotParallelize] public async System.Threading.Tasks.Task Test010_Should_Generate_TOTP_Token_With_Valid_MfaSecret_Async() { - ContentstackClient client = new ContentstackClient(); - NetworkCredential credentials = new NetworkCredential("test_user", "test_password"); - string validMfaSecret = "JBSWY3DPEHPK3PXP"; // Valid Base32 test secret - + TestReportHelper.Begin(); + var sw = Stopwatch.StartNew(); try { - // This should fail due to invalid credentials, but should succeed in generating TOTP - ContentstackResponse contentstackResponse = await client.LoginAsync(credentials, null, validMfaSecret); - } - catch (ContentstackErrorException errorException) - { - // Expected to fail due to invalid credentials, but we verify it processed the MFA secret - // The error should be about credentials, not about MFA secret format - Assert.AreEqual(HttpStatusCode.UnprocessableEntity, errorException.StatusCode); - Assert.IsTrue(errorException.Message.Contains("email or password") || - errorException.Message.Contains("credentials") || - errorException.Message.Contains("authentication")); + ContentstackClient client = new ContentstackClient(); + NetworkCredential credentials = new NetworkCredential("test_user", "test_password"); + string validMfaSecret = "JBSWY3DPEHPK3PXP"; + + TestReportHelper.LogRequest("client.LoginAsync() with valid MFA secret", "POST", + $"https://{_host}/v3/user-session"); + + try + { + ContentstackResponse contentstackResponse = await client.LoginAsync(credentials, null, validMfaSecret); + sw.Stop(); + } + catch (ContentstackErrorException errorException) + { + sw.Stop(); + TestReportHelper.LogAssertion( + errorException.StatusCode == HttpStatusCode.UnprocessableEntity, + "Status code is UnprocessableEntity", + expected: "UnprocessableEntity", actual: errorException.StatusCode.ToString(), type: "AreEqual"); + Assert.AreEqual(HttpStatusCode.UnprocessableEntity, errorException.StatusCode); + Assert.IsTrue(errorException.Message.Contains("email or password") || + errorException.Message.Contains("credentials") || + errorException.Message.Contains("authentication")); + } + catch (ArgumentException) + { + sw.Stop(); + Assert.Fail("Should not throw ArgumentException for valid MFA secret"); + } + catch (Exception e) + { + sw.Stop(); + Assert.Fail($"Unexpected exception type: {e.GetType().Name} - {e.Message}"); + } } - catch (ArgumentException) + catch (Exception e) { - Assert.Fail("Should not throw ArgumentException for valid MFA secret"); + sw.Stop(); + TestReportHelper.LogAssertion(false, $"Exception: {e.GetType().Name} — {e.Message}", type: "Fail"); + Assert.Fail(e.Message); } - catch (Exception e) + finally { - Assert.Fail($"Unexpected exception type: {e.GetType().Name} - {e.Message}"); + TestReportHelper.Flush(); } } @@ -268,29 +470,52 @@ public async System.Threading.Tasks.Task Test010_Should_Generate_TOTP_Token_With [DoNotParallelize] public void Test011_Should_Prefer_Explicit_Token_Over_MfaSecret() { - ContentstackClient client = new ContentstackClient(); - NetworkCredential credentials = new NetworkCredential("test_user", "test_password"); - string validMfaSecret = "JBSWY3DPEHPK3PXP"; - string explicitToken = "123456"; - + TestReportHelper.Begin(); + var sw = Stopwatch.StartNew(); try { - // This should fail due to invalid credentials, but should use explicit token - ContentstackResponse contentstackResponse = client.Login(credentials, explicitToken, validMfaSecret); - } - catch (ContentstackErrorException errorException) - { - // Expected to fail due to invalid credentials - // The important thing is that it didn't throw an exception about MFA secret processing - Assert.AreEqual(HttpStatusCode.UnprocessableEntity, errorException.StatusCode); + ContentstackClient client = new ContentstackClient(); + NetworkCredential credentials = new NetworkCredential("test_user", "test_password"); + string validMfaSecret = "JBSWY3DPEHPK3PXP"; + string explicitToken = "123456"; + + TestReportHelper.LogRequest("client.Login() explicit token over MFA", "POST", + $"https://{_host}/v3/user-session"); + + try + { + ContentstackResponse contentstackResponse = client.Login(credentials, explicitToken, validMfaSecret); + sw.Stop(); + } + catch (ContentstackErrorException errorException) + { + sw.Stop(); + TestReportHelper.LogAssertion( + errorException.StatusCode == HttpStatusCode.UnprocessableEntity, + "Status code is UnprocessableEntity (credentials rejected)", + expected: "UnprocessableEntity", actual: errorException.StatusCode.ToString(), type: "AreEqual"); + Assert.AreEqual(HttpStatusCode.UnprocessableEntity, errorException.StatusCode); + } + catch (ArgumentException) + { + sw.Stop(); + Assert.Fail("Should not throw ArgumentException when explicit token is provided"); + } + catch (Exception e) + { + sw.Stop(); + Assert.Fail($"Unexpected exception type: {e.GetType().Name} - {e.Message}"); + } } - catch (ArgumentException) + catch (Exception e) { - Assert.Fail("Should not throw ArgumentException when explicit token is provided"); + sw.Stop(); + TestReportHelper.LogAssertion(false, $"Exception: {e.GetType().Name} — {e.Message}", type: "Fail"); + Assert.Fail(e.Message); } - catch (Exception e) + finally { - Assert.Fail($"Unexpected exception type: {e.GetType().Name} - {e.Message}"); + TestReportHelper.Flush(); } } } diff --git a/Contentstack.Management.Core.Tests/IntegrationTest/Contentstack002_OrganisationTest.cs b/Contentstack.Management.Core.Tests/IntegrationTest/Contentstack002_OrganisationTest.cs index 584b520..1645cfe 100644 --- a/Contentstack.Management.Core.Tests/IntegrationTest/Contentstack002_OrganisationTest.cs +++ b/Contentstack.Management.Core.Tests/IntegrationTest/Contentstack002_OrganisationTest.cs @@ -1,4 +1,5 @@ -using System; +using System; +using System.Diagnostics; using System.Net.Mail; using AutoFixture; using Contentstack.Management.Core.Models; @@ -20,171 +21,287 @@ public class Contentstack002_OrganisationTest static string InviteIDAsync = ""; private readonly IFixture _fixture = new Fixture(); + private static string _host => Contentstack.Client.contentstackOptions.Host; + [TestMethod] [DoNotParallelize] public void Test001_Should_Return_All_Organizations() { + TestReportHelper.Begin(); + var sw = Stopwatch.StartNew(); try { Organization organization = Contentstack.Client.Organization(); + TestReportHelper.LogRequest("organization.GetOrganizations()", "GET", + $"https://{_host}/v3/organizations"); ContentstackResponse contentstackResponse = organization.GetOrganizations(); + sw.Stop(); + var body = contentstackResponse.OpenResponse(); + TestReportHelper.LogResponse((int)contentstackResponse.StatusCode, + contentstackResponse.StatusCode.ToString(), sw.ElapsedMilliseconds, body); var response = contentstackResponse.OpenJObjectResponse(); + _count = (response["organizations"] as JArray).Count; + + TestReportHelper.LogAssertion(response != null, "Response not null", type: "IsNotNull"); Assert.IsNotNull(response); - _count = (response["organizations"] as Newtonsoft.Json.Linq.JArray).Count; - - } catch (Exception e) + } + catch (Exception e) { + sw.Stop(); + TestReportHelper.LogAssertion(false, $"Exception: {e.GetType().Name} — {e.Message}", type: "Fail"); Assert.Fail(e.Message); } - + finally + { + TestReportHelper.Flush(); + } } [TestMethod] [DoNotParallelize] public async System.Threading.Tasks.Task Test002_Should_Return_All_OrganizationsAsync() { + TestReportHelper.Begin(); + var sw = Stopwatch.StartNew(); try { Organization organization = Contentstack.Client.Organization(); + TestReportHelper.LogRequest("organization.GetOrganizationsAsync()", "GET", + $"https://{_host}/v3/organizations"); ContentstackResponse contentstackResponse = await organization.GetOrganizationsAsync(); + sw.Stop(); + var body = contentstackResponse.OpenResponse(); + TestReportHelper.LogResponse((int)contentstackResponse.StatusCode, + contentstackResponse.StatusCode.ToString(), sw.ElapsedMilliseconds, body); var response = contentstackResponse.OpenJObjectResponse(); - Assert.IsNotNull(response); - _count = (response["organizations"] as Newtonsoft.Json.Linq.JArray).Count; + _count = (response["organizations"] as JArray).Count; + TestReportHelper.LogAssertion(response != null, "Response not null", type: "IsNotNull"); + Assert.IsNotNull(response); } catch (Exception e) { + sw.Stop(); + TestReportHelper.LogAssertion(false, $"Exception: {e.GetType().Name} — {e.Message}", type: "Fail"); Assert.Fail(e.Message); } - + finally + { + TestReportHelper.Flush(); + } } [TestMethod] [DoNotParallelize] public void Test003_Should_Return_With_Skipping_Organizations() { + TestReportHelper.Begin(); + var sw = Stopwatch.StartNew(); try { Organization organization = Contentstack.Client.Organization(); ParameterCollection collection = new ParameterCollection(); collection.Add("skip", 4); + TestReportHelper.LogRequest("organization.GetOrganizations(skip=4)", "GET", + $"https://{_host}/v3/organizations", + queryParams: new System.Collections.Generic.Dictionary { ["skip"] = "4" }); + ContentstackResponse contentstackResponse = organization.GetOrganizations(collection); + sw.Stop(); + var body = contentstackResponse.OpenResponse(); + TestReportHelper.LogResponse((int)contentstackResponse.StatusCode, + contentstackResponse.StatusCode.ToString(), sw.ElapsedMilliseconds, body); var response = contentstackResponse.OpenJObjectResponse(); + var count = (response["organizations"] as JArray).Count; + + TestReportHelper.LogAssertion(response != null, "Response not null", type: "IsNotNull"); Assert.IsNotNull(response); - var count = (response["organizations"] as Newtonsoft.Json.Linq.JArray).Count; } catch (Exception e) { + sw.Stop(); + TestReportHelper.LogAssertion(false, $"Exception: {e.GetType().Name} — {e.Message}", type: "Fail"); Assert.Fail(e.Message); } - + finally + { + TestReportHelper.Flush(); + } } [TestMethod] [DoNotParallelize] public void Test004_Should_Return_Organization_With_UID() { + TestReportHelper.Begin(); + var sw = Stopwatch.StartNew(); try { var org = Contentstack.Organization; Organization organization = Contentstack.Client.Organization(org.Uid); + TestReportHelper.LogRequest("organization.GetOrganizations() by UID", "GET", + $"https://{_host}/v3/organizations/{org.Uid}"); ContentstackResponse contentstackResponse = organization.GetOrganizations(); + sw.Stop(); + var body = contentstackResponse.OpenResponse(); + TestReportHelper.LogResponse((int)contentstackResponse.StatusCode, + contentstackResponse.StatusCode.ToString(), sw.ElapsedMilliseconds, body); var response = contentstackResponse.OpenJObjectResponse(); + OrganisationResponse model = contentstackResponse.OpenTResponse(); + + TestReportHelper.LogAssertion(response != null, "Response not null", type: "IsNotNull"); + TestReportHelper.LogAssertion(response["organization"] != null, "organization key present", type: "IsNotNull"); + TestReportHelper.LogAssertion(model.Organization.Name == org.Name, + "Organization name matches", expected: org.Name, actual: model.Organization.Name, type: "AreEqual"); Assert.IsNotNull(response); Assert.IsNotNull(response["organization"]); - - OrganisationResponse model = contentstackResponse.OpenTResponse(); Assert.AreEqual(org.Name, model.Organization.Name); } catch (Exception e) { + sw.Stop(); + TestReportHelper.LogAssertion(false, $"Exception: {e.GetType().Name} — {e.Message}", type: "Fail"); Assert.Fail(e.Message); } - + finally + { + TestReportHelper.Flush(); + } } [TestMethod] [DoNotParallelize] public void Test005_Should_Return_Organization_With_UID_Include_Plan() { + TestReportHelper.Begin(); + var sw = Stopwatch.StartNew(); try { var org = Contentstack.Organization; Organization organization = Contentstack.Client.Organization(org.Uid); ParameterCollection collection = new ParameterCollection(); collection.Add("include_plan", true); + TestReportHelper.LogRequest("organization.GetOrganizations(include_plan)", "GET", + $"https://{_host}/v3/organizations/{org.Uid}", + queryParams: new System.Collections.Generic.Dictionary { ["include_plan"] = "true" }); ContentstackResponse contentstackResponse = organization.GetOrganizations(collection); + sw.Stop(); + var body = contentstackResponse.OpenResponse(); + TestReportHelper.LogResponse((int)contentstackResponse.StatusCode, + contentstackResponse.StatusCode.ToString(), sw.ElapsedMilliseconds, body); var response = contentstackResponse.OpenJObjectResponse(); + + TestReportHelper.LogAssertion(response != null, "Response not null", type: "IsNotNull"); + TestReportHelper.LogAssertion(response["organization"] != null, "organization key present", type: "IsNotNull"); + TestReportHelper.LogAssertion(response["organization"]["plan"] != null, "plan key present", type: "IsNotNull"); Assert.IsNotNull(response); Assert.IsNotNull(response["organization"]); Assert.IsNotNull(response["organization"]["plan"]); - } catch (Exception e) { + sw.Stop(); + TestReportHelper.LogAssertion(false, $"Exception: {e.GetType().Name} — {e.Message}", type: "Fail"); Assert.Fail(e.Message); } + finally + { + TestReportHelper.Flush(); + } } [TestMethod] [DoNotParallelize] public void Test006_Should_Return_Organization_Roles() { + TestReportHelper.Begin(); + var sw = Stopwatch.StartNew(); try { var org = Contentstack.Organization; Organization organization = Contentstack.Client.Organization(org.Uid); + TestReportHelper.LogRequest("organization.Roles()", "GET", + $"https://{_host}/v3/organizations/{org.Uid}/roles"); ContentstackResponse contentstackResponse = organization.Roles(); + sw.Stop(); + var body = contentstackResponse.OpenResponse(); + TestReportHelper.LogResponse((int)contentstackResponse.StatusCode, + contentstackResponse.StatusCode.ToString(), sw.ElapsedMilliseconds, body); var response = contentstackResponse.OpenJObjectResponse(); - RoleUID = (string)response["roles"][0]["uid"]; + + TestReportHelper.LogAssertion(response != null, "Response not null", type: "IsNotNull"); + TestReportHelper.LogAssertion(response["roles"] != null, "roles key present", type: "IsNotNull"); Assert.IsNotNull(response); Assert.IsNotNull(response["roles"]); } catch (Exception e) { + sw.Stop(); + TestReportHelper.LogAssertion(false, $"Exception: {e.GetType().Name} — {e.Message}", type: "Fail"); Assert.Fail(e.Message); } - + finally + { + TestReportHelper.Flush(); + } } [TestMethod] [DoNotParallelize] public async System.Threading.Tasks.Task Test007_Should_Return_Organization_RolesAsync() { + TestReportHelper.Begin(); + var sw = Stopwatch.StartNew(); try { var org = Contentstack.Organization; Organization organization = Contentstack.Client.Organization(org.Uid); + TestReportHelper.LogRequest("organization.RolesAsync()", "GET", + $"https://{_host}/v3/organizations/{org.Uid}/roles"); ContentstackResponse contentstackResponse = await organization.RolesAsync(); + sw.Stop(); + var body = contentstackResponse.OpenResponse(); + TestReportHelper.LogResponse((int)contentstackResponse.StatusCode, + contentstackResponse.StatusCode.ToString(), sw.ElapsedMilliseconds, body); var response = contentstackResponse.OpenJObjectResponse(); + + TestReportHelper.LogAssertion(response != null, "Response not null", type: "IsNotNull"); + TestReportHelper.LogAssertion(response["roles"] != null, "roles key present", type: "IsNotNull"); Assert.IsNotNull(response); Assert.IsNotNull(response["roles"]); } catch (Exception e) { + sw.Stop(); + TestReportHelper.LogAssertion(false, $"Exception: {e.GetType().Name} — {e.Message}", type: "Fail"); Assert.Fail(e.Message); } - + finally + { + TestReportHelper.Flush(); + } } [TestMethod] [DoNotParallelize] public void Test008_Should_Add_User_To_Organization() { + TestReportHelper.Begin(); + var sw = Stopwatch.StartNew(); try { var org = Contentstack.Organization; @@ -194,28 +311,46 @@ public void Test008_Should_Add_User_To_Organization() Email = EmailSync, Roles = new System.Collections.Generic.List() { RoleUID } }; + TestReportHelper.LogRequest("organization.AddUser()", "POST", + $"https://{_host}/v3/organizations/{org.Uid}/share"); + ContentstackResponse contentstackResponse = organization.AddUser(new System.Collections.Generic.List() { invitation }, null); + sw.Stop(); + var body = contentstackResponse.OpenResponse(); + TestReportHelper.LogResponse((int)contentstackResponse.StatusCode, + contentstackResponse.StatusCode.ToString(), sw.ElapsedMilliseconds, body); var response = contentstackResponse.OpenJObjectResponse(); Assert.IsNotNull(response); Assert.AreEqual(1, ((JArray)response["shares"]).Count); InviteID = (string)response["shares"][0]["uid"]; + + TestReportHelper.LogAssertion(response != null, "Response not null", type: "IsNotNull"); + TestReportHelper.LogAssertion(((JArray)response["shares"]).Count == 1, + "Shares count is 1", expected: "1", actual: ((JArray)response["shares"]).Count.ToString(), type: "AreEqual"); Assert.AreEqual("The invitation has been sent successfully.", response["notice"]); } catch (Exception e) { + sw.Stop(); + TestReportHelper.LogAssertion(false, $"Exception: {e.GetType().Name} — {e.Message}", type: "Fail"); Assert.Fail(e.Message); } - + finally + { + TestReportHelper.Flush(); + } } [TestMethod] [DoNotParallelize] public async System.Threading.Tasks.Task Test009_Should_Add_User_To_Organization() { + TestReportHelper.Begin(); + var sw = Stopwatch.StartNew(); try { var org = Contentstack.Organization; @@ -225,199 +360,348 @@ public async System.Threading.Tasks.Task Test009_Should_Add_User_To_Organization Email = EmailAsync, Roles = new System.Collections.Generic.List() { RoleUID } }; + TestReportHelper.LogRequest("organization.AddUserAsync()", "POST", + $"https://{_host}/v3/organizations/{org.Uid}/share"); + ContentstackResponse contentstackResponse = await organization.AddUserAsync(new System.Collections.Generic.List() { invitation }, null); + sw.Stop(); + var body = contentstackResponse.OpenResponse(); + TestReportHelper.LogResponse((int)contentstackResponse.StatusCode, + contentstackResponse.StatusCode.ToString(), sw.ElapsedMilliseconds, body); var response = contentstackResponse.OpenJObjectResponse(); Assert.IsNotNull(response); Assert.AreEqual(1, ((JArray)response["shares"]).Count); InviteIDAsync = (string)response["shares"][0]["uid"]; + + TestReportHelper.LogAssertion(((JArray)response["shares"]).Count == 1, + "Shares count is 1", expected: "1", actual: ((JArray)response["shares"]).Count.ToString(), type: "AreEqual"); Assert.AreEqual("The invitation has been sent successfully.", response["notice"]); } catch (Exception e) { + sw.Stop(); + TestReportHelper.LogAssertion(false, $"Exception: {e.GetType().Name} — {e.Message}", type: "Fail"); Assert.Fail(e.Message); } - + finally + { + TestReportHelper.Flush(); + } } + [TestMethod] [DoNotParallelize] public void Test010_Should_Resend_Invite() { + TestReportHelper.Begin(); + var sw = Stopwatch.StartNew(); try { var org = Contentstack.Organization; Organization organization = Contentstack.Client.Organization(org.Uid); + TestReportHelper.LogRequest("organization.ResendInvitation()", "GET", + $"https://{_host}/v3/organizations/{org.Uid}/share/{InviteID}/resend_invitation"); ContentstackResponse contentstackResponse = organization.ResendInvitation(InviteID); + sw.Stop(); + var body = contentstackResponse.OpenResponse(); + TestReportHelper.LogResponse((int)contentstackResponse.StatusCode, + contentstackResponse.StatusCode.ToString(), sw.ElapsedMilliseconds, body); var response = contentstackResponse.OpenJObjectResponse(); + + TestReportHelper.LogAssertion(response != null, "Response not null", type: "IsNotNull"); + TestReportHelper.LogAssertion( + response["notice"]?.ToString() == "The invitation has been resent successfully.", + "Notice message matches", expected: "The invitation has been resent successfully.", actual: response["notice"]?.ToString(), type: "AreEqual"); Assert.IsNotNull(response); Assert.AreEqual("The invitation has been resent successfully.", response["notice"]); } catch (Exception e) { + sw.Stop(); + TestReportHelper.LogAssertion(false, $"Exception: {e.GetType().Name} — {e.Message}", type: "Fail"); Assert.Fail(e.Message); } - + finally + { + TestReportHelper.Flush(); + } } [TestMethod] [DoNotParallelize] public async System.Threading.Tasks.Task Test011_Should_Resend_Invite() { + TestReportHelper.Begin(); + var sw = Stopwatch.StartNew(); try { var org = Contentstack.Organization; Organization organization = Contentstack.Client.Organization(org.Uid); + TestReportHelper.LogRequest("organization.ResendInvitationAsync()", "GET", + $"https://{_host}/v3/organizations/{org.Uid}/share/{InviteIDAsync}/resend_invitation"); + ContentstackResponse contentstackResponse = await organization.ResendInvitationAsync(InviteIDAsync); + sw.Stop(); + var body = contentstackResponse.OpenResponse(); + TestReportHelper.LogResponse((int)contentstackResponse.StatusCode, + contentstackResponse.StatusCode.ToString(), sw.ElapsedMilliseconds, body); var response = contentstackResponse.OpenJObjectResponse(); + + TestReportHelper.LogAssertion(response != null, "Response not null", type: "IsNotNull"); Assert.IsNotNull(response); Assert.AreEqual("The invitation has been resent successfully.", response["notice"]); } catch (Exception e) { + sw.Stop(); + TestReportHelper.LogAssertion(false, $"Exception: {e.GetType().Name} — {e.Message}", type: "Fail"); Assert.Fail(e.Message); } - + finally + { + TestReportHelper.Flush(); + } } [TestMethod] [DoNotParallelize] public void Test012_Should_Remove_User_From_Organization() { + TestReportHelper.Begin(); + var sw = Stopwatch.StartNew(); try { var org = Contentstack.Organization; Organization organization = Contentstack.Client.Organization(org.Uid); + TestReportHelper.LogRequest("organization.RemoveUser()", "DELETE", + $"https://{_host}/v3/organizations/{org.Uid}/share"); - ContentstackResponse contentstackResponse = organization.RemoveUser(new System.Collections.Generic.List() { EmailSync } ); + ContentstackResponse contentstackResponse = organization.RemoveUser(new System.Collections.Generic.List() { EmailSync }); + sw.Stop(); + var body = contentstackResponse.OpenResponse(); + TestReportHelper.LogResponse((int)contentstackResponse.StatusCode, + contentstackResponse.StatusCode.ToString(), sw.ElapsedMilliseconds, body); var response = contentstackResponse.OpenJObjectResponse(); + + TestReportHelper.LogAssertion(response != null, "Response not null", type: "IsNotNull"); + TestReportHelper.LogAssertion( + response["notice"]?.ToString() == "The invitation has been deleted successfully.", + "Notice message matches", expected: "The invitation has been deleted successfully.", actual: response["notice"]?.ToString(), type: "AreEqual"); Assert.IsNotNull(response); Assert.AreEqual("The invitation has been deleted successfully.", response["notice"]); } catch (Exception e) { + sw.Stop(); + TestReportHelper.LogAssertion(false, $"Exception: {e.GetType().Name} — {e.Message}", type: "Fail"); Assert.Fail(e.Message); } - + finally + { + TestReportHelper.Flush(); + } } [TestMethod] [DoNotParallelize] public async System.Threading.Tasks.Task Test013_Should_Remove_User_From_Organization() { + TestReportHelper.Begin(); + var sw = Stopwatch.StartNew(); try { var org = Contentstack.Organization; Organization organization = Contentstack.Client.Organization(org.Uid); + TestReportHelper.LogRequest("organization.RemoveUserAsync()", "DELETE", + $"https://{_host}/v3/organizations/{org.Uid}/share"); + ContentstackResponse contentstackResponse = await organization.RemoveUserAsync(new System.Collections.Generic.List() { EmailAsync }); + sw.Stop(); + var body = contentstackResponse.OpenResponse(); + TestReportHelper.LogResponse((int)contentstackResponse.StatusCode, + contentstackResponse.StatusCode.ToString(), sw.ElapsedMilliseconds, body); var response = contentstackResponse.OpenJObjectResponse(); + + TestReportHelper.LogAssertion(response != null, "Response not null", type: "IsNotNull"); Assert.IsNotNull(response); Assert.AreEqual("The invitation has been deleted successfully.", response["notice"]); } catch (Exception e) { + sw.Stop(); + TestReportHelper.LogAssertion(false, $"Exception: {e.GetType().Name} — {e.Message}", type: "Fail"); Assert.Fail(e.Message); } - + finally + { + TestReportHelper.Flush(); + } } [TestMethod] [DoNotParallelize] public void Test014_Should_Get_All_Invites() { + TestReportHelper.Begin(); + var sw = Stopwatch.StartNew(); try { var org = Contentstack.Organization; Organization organization = Contentstack.Client.Organization(org.Uid); + TestReportHelper.LogRequest("organization.GetInvitations()", "GET", + $"https://{_host}/v3/organizations/{org.Uid}/share"); ContentstackResponse contentstackResponse = organization.GetInvitations(); + sw.Stop(); + var body = contentstackResponse.OpenResponse(); + TestReportHelper.LogResponse((int)contentstackResponse.StatusCode, + contentstackResponse.StatusCode.ToString(), sw.ElapsedMilliseconds, body); var response = contentstackResponse.OpenJObjectResponse(); + + TestReportHelper.LogAssertion(response != null, "Response not null", type: "IsNotNull"); + TestReportHelper.LogAssertion(response["shares"] != null, "shares key present", type: "IsNotNull"); + TestReportHelper.LogAssertion(response["shares"].GetType() == typeof(JArray), "shares is JArray", type: "AreEqual"); Assert.IsNotNull(response); Assert.IsNotNull(response["shares"]); Assert.AreEqual(response["shares"].GetType(), typeof(JArray)); - } catch (Exception e) { + sw.Stop(); + TestReportHelper.LogAssertion(false, $"Exception: {e.GetType().Name} — {e.Message}", type: "Fail"); Assert.Fail(e.Message); } - + finally + { + TestReportHelper.Flush(); + } } [TestMethod] [DoNotParallelize] public async System.Threading.Tasks.Task Test015_Should_Get_All_Invites_Async() { + TestReportHelper.Begin(); + var sw = Stopwatch.StartNew(); try { var org = Contentstack.Organization; Organization organization = Contentstack.Client.Organization(org.Uid); + TestReportHelper.LogRequest("organization.GetInvitationsAsync()", "GET", + $"https://{_host}/v3/organizations/{org.Uid}/share"); + ContentstackResponse contentstackResponse = await organization.GetInvitationsAsync(); + sw.Stop(); + var body = contentstackResponse.OpenResponse(); + TestReportHelper.LogResponse((int)contentstackResponse.StatusCode, + contentstackResponse.StatusCode.ToString(), sw.ElapsedMilliseconds, body); var response = contentstackResponse.OpenJObjectResponse(); + + TestReportHelper.LogAssertion(response != null, "Response not null", type: "IsNotNull"); + TestReportHelper.LogAssertion(response["shares"] != null, "shares key present", type: "IsNotNull"); Assert.IsNotNull(response); Assert.IsNotNull(response["shares"]); Assert.AreEqual(response["shares"].GetType(), typeof(JArray)); } catch (Exception e) { + sw.Stop(); + TestReportHelper.LogAssertion(false, $"Exception: {e.GetType().Name} — {e.Message}", type: "Fail"); Assert.Fail(e.Message); } - + finally + { + TestReportHelper.Flush(); + } } [TestMethod] [DoNotParallelize] public void Test016_Should_Get_All_Stacks() { + TestReportHelper.Begin(); + var sw = Stopwatch.StartNew(); try { var org = Contentstack.Organization; Organization organization = Contentstack.Client.Organization(org.Uid); + TestReportHelper.LogRequest("organization.GetStacks()", "GET", + $"https://{_host}/v3/organizations/{org.Uid}/stacks"); ContentstackResponse contentstackResponse = organization.GetStacks(); + sw.Stop(); + var body = contentstackResponse.OpenResponse(); + TestReportHelper.LogResponse((int)contentstackResponse.StatusCode, + contentstackResponse.StatusCode.ToString(), sw.ElapsedMilliseconds, body); var response = contentstackResponse.OpenJObjectResponse(); + + TestReportHelper.LogAssertion(response != null, "Response not null", type: "IsNotNull"); + TestReportHelper.LogAssertion(response["stacks"] != null, "stacks key present", type: "IsNotNull"); Assert.IsNotNull(response); Assert.IsNotNull(response["stacks"]); Assert.AreEqual(response["stacks"].GetType(), typeof(JArray)); - } catch (Exception e) { + sw.Stop(); + TestReportHelper.LogAssertion(false, $"Exception: {e.GetType().Name} — {e.Message}", type: "Fail"); Assert.Fail(e.Message); } - + finally + { + TestReportHelper.Flush(); + } } [TestMethod] [DoNotParallelize] public async System.Threading.Tasks.Task Test017_Should_Get_All_Stacks_Async() { + TestReportHelper.Begin(); + var sw = Stopwatch.StartNew(); try { var org = Contentstack.Organization; Organization organization = Contentstack.Client.Organization(org.Uid); + TestReportHelper.LogRequest("organization.GetStacksAsync()", "GET", + $"https://{_host}/v3/organizations/{org.Uid}/stacks"); + ContentstackResponse contentstackResponse = await organization.GetStacksAsync(); + sw.Stop(); + var body = contentstackResponse.OpenResponse(); + TestReportHelper.LogResponse((int)contentstackResponse.StatusCode, + contentstackResponse.StatusCode.ToString(), sw.ElapsedMilliseconds, body); var response = contentstackResponse.OpenJObjectResponse(); + + TestReportHelper.LogAssertion(response != null, "Response not null", type: "IsNotNull"); + TestReportHelper.LogAssertion(response["stacks"] != null, "stacks key present", type: "IsNotNull"); Assert.IsNotNull(response); Assert.IsNotNull(response["stacks"]); Assert.AreEqual(response["stacks"].GetType(), typeof(JArray)); } catch (Exception e) { + sw.Stop(); + TestReportHelper.LogAssertion(false, $"Exception: {e.GetType().Name} — {e.Message}", type: "Fail"); Assert.Fail(e.Message); } - + finally + { + TestReportHelper.Flush(); + } } } } diff --git a/Contentstack.Management.Core.Tests/IntegrationTest/Contentstack003_StackTest.cs b/Contentstack.Management.Core.Tests/IntegrationTest/Contentstack003_StackTest.cs index 211a4ca..0f26b82 100644 --- a/Contentstack.Management.Core.Tests/IntegrationTest/Contentstack003_StackTest.cs +++ b/Contentstack.Management.Core.Tests/IntegrationTest/Contentstack003_StackTest.cs @@ -1,5 +1,6 @@ -using System; +using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using Contentstack.Management.Core.Models; using Contentstack.Management.Core.Tests.Model; @@ -14,61 +15,108 @@ public class Contentstack003_StackTest private string _stackName = "DotNet Management Stack"; private string _updatestackName = "DotNet Management SDK Stack"; private string _description = "Integration testing Stack for DotNet Management SDK"; - + private OrganizationModel _org = Contentstack.Organization; + private static string _host => Contentstack.Client.contentstackOptions.Host; + [TestMethod] [DoNotParallelize] public void Test001_Should_Return_All_Stacks() { + TestReportHelper.Begin(); + var sw = Stopwatch.StartNew(); try { Stack stack = Contentstack.Client.Stack(); + TestReportHelper.LogRequest("stack.GetAll()", "GET", + $"https://{_host}/v3/stacks"); ContentstackResponse contentstackResponse = stack.GetAll(); + sw.Stop(); + var body = contentstackResponse.OpenResponse(); + TestReportHelper.LogResponse((int)contentstackResponse.StatusCode, + contentstackResponse.StatusCode.ToString(), sw.ElapsedMilliseconds, body); var response = contentstackResponse.OpenJObjectResponse(); + TestReportHelper.LogAssertion(response != null, "Response not null", type: "IsNotNull"); Assert.IsNotNull(response); } catch (Exception e) { + sw.Stop(); + TestReportHelper.LogAssertion(false, $"Exception: {e.GetType().Name} — {e.Message}", type: "Fail"); Assert.Fail(e.Message); } + finally + { + TestReportHelper.Flush(); + } } [TestMethod] [DoNotParallelize] public async System.Threading.Tasks.Task Test002_Should_Return_All_StacksAsync() { + TestReportHelper.Begin(); + var sw = Stopwatch.StartNew(); try { Stack stack = Contentstack.Client.Stack(); + TestReportHelper.LogRequest("stack.GetAllAsync()", "GET", + $"https://{_host}/v3/stacks"); ContentstackResponse contentstackResponse = await stack.GetAllAsync(); + sw.Stop(); + var body = contentstackResponse.OpenResponse(); + TestReportHelper.LogResponse((int)contentstackResponse.StatusCode, + contentstackResponse.StatusCode.ToString(), sw.ElapsedMilliseconds, body); var response = contentstackResponse.OpenJObjectResponse(); + TestReportHelper.LogAssertion(response != null, "Response not null", type: "IsNotNull"); Assert.IsNotNull(response); } catch (Exception e) { + sw.Stop(); + TestReportHelper.LogAssertion(false, $"Exception: {e.GetType().Name} — {e.Message}", type: "Fail"); Assert.Fail(e.Message); } - + finally + { + TestReportHelper.Flush(); + } } [TestMethod] [DoNotParallelize] public void Test003_Should_Create_Stack() { + TestReportHelper.Begin(); + var sw = Stopwatch.StartNew(); try { Stack stack = Contentstack.Client.Stack(); + TestReportHelper.LogRequest("stack.Create()", "POST", + $"https://{_host}/v3/stacks", + body: $"{{\"stack\":{{\"name\":\"{_stackName}\",\"master_locale\":\"{_locale}\",\"org_uid\":\"{_org.Uid}\"}}}}"); + ContentstackResponse contentstackResponse = stack.Create(_stackName, _locale, _org.Uid); + sw.Stop(); + var body = contentstackResponse.OpenResponse(); + TestReportHelper.LogResponse((int)contentstackResponse.StatusCode, + contentstackResponse.StatusCode.ToString(), sw.ElapsedMilliseconds, body); var response = contentstackResponse.OpenJObjectResponse(); StackResponse model = contentstackResponse.OpenTResponse(); Contentstack.Stack = model.Stack; + TestReportHelper.LogAssertion(response != null, "Response not null", type: "IsNotNull"); + TestReportHelper.LogAssertion(model.Stack.Description == null, "Description is null", type: "IsNull"); + TestReportHelper.LogAssertion(model.Stack.Name == _stackName, + "Stack name matches", expected: _stackName, actual: model.Stack.Name, type: "AreEqual"); + TestReportHelper.LogAssertion(model.Stack.MasterLocale == _locale, + "Master locale matches", expected: _locale, actual: model.Stack.MasterLocale, type: "AreEqual"); Assert.IsNotNull(response); Assert.IsNull(model.Stack.Description); Assert.AreEqual(_stackName, model.Stack.Name); @@ -77,25 +125,45 @@ public void Test003_Should_Create_Stack() } catch (Exception e) { + sw.Stop(); + TestReportHelper.LogAssertion(false, $"Exception: {e.GetType().Name} — {e.Message}", type: "Fail"); Assert.Fail(e.Message); } + finally + { + TestReportHelper.Flush(); + } } [TestMethod] [DoNotParallelize] public void Test004_Should_Update_Stack() { + TestReportHelper.Begin(); + var sw = Stopwatch.StartNew(); try { Stack stack = Contentstack.Client.Stack(Contentstack.Stack.APIKey); + TestReportHelper.LogRequest("stack.Update()", "PUT", + $"https://{_host}/v3/stacks", + headers: new Dictionary { ["api_key"] = Contentstack.Stack.APIKey ?? "***" }, + body: $"{{\"stack\":{{\"name\":\"{_updatestackName}\"}}}}"); + ContentstackResponse contentstackResponse = stack.Update(_updatestackName); + sw.Stop(); + var body = contentstackResponse.OpenResponse(); + TestReportHelper.LogResponse((int)contentstackResponse.StatusCode, + contentstackResponse.StatusCode.ToString(), sw.ElapsedMilliseconds, body); var response = contentstackResponse.OpenJObjectResponse(); - File.WriteAllText("./stackApiKey.txt", contentstackResponse.OpenResponse()); + File.WriteAllText("./stackApiKey.txt", body); StackResponse model = contentstackResponse.OpenTResponse(); Contentstack.Stack = model.Stack; + TestReportHelper.LogAssertion(response != null, "Response not null", type: "IsNotNull"); + TestReportHelper.LogAssertion(model.Stack.Name == _updatestackName, + "Stack name updated", expected: _updatestackName, actual: model.Stack.Name, type: "AreEqual"); Assert.IsNotNull(response); Assert.IsNull(model.Stack.Description); Assert.AreEqual(Contentstack.Stack.APIKey, model.Stack.APIKey); @@ -105,23 +173,43 @@ public void Test004_Should_Update_Stack() } catch (Exception e) { + sw.Stop(); + TestReportHelper.LogAssertion(false, $"Exception: {e.GetType().Name} — {e.Message}", type: "Fail"); Assert.Fail(e.Message); } + finally + { + TestReportHelper.Flush(); + } } [TestMethod] [DoNotParallelize] public async System.Threading.Tasks.Task Test005_Should_Update_Stack_Async() { + TestReportHelper.Begin(); + var sw = Stopwatch.StartNew(); try { Stack stack = Contentstack.Client.Stack(Contentstack.Stack.APIKey); + TestReportHelper.LogRequest("stack.UpdateAsync()", "PUT", + $"https://{_host}/v3/stacks", + headers: new Dictionary { ["api_key"] = Contentstack.Stack.APIKey ?? "***" }); + ContentstackResponse contentstackResponse = await stack.UpdateAsync(_updatestackName, _description); + sw.Stop(); + var body = contentstackResponse.OpenResponse(); + TestReportHelper.LogResponse((int)contentstackResponse.StatusCode, + contentstackResponse.StatusCode.ToString(), sw.ElapsedMilliseconds, body); var response = contentstackResponse.OpenJObjectResponse(); StackResponse model = contentstackResponse.OpenTResponse(); Contentstack.Stack = model.Stack; + TestReportHelper.LogAssertion(model.Stack.Name == _updatestackName, + "Stack name matches", expected: _updatestackName, actual: model.Stack.Name, type: "AreEqual"); + TestReportHelper.LogAssertion(model.Stack.Description == _description, + "Description matches", expected: _description, actual: model.Stack.Description, type: "AreEqual"); Assert.IsNotNull(response); Assert.AreEqual(Contentstack.Stack.APIKey, model.Stack.APIKey); Assert.AreEqual(_updatestackName, model.Stack.Name); @@ -131,22 +219,40 @@ public async System.Threading.Tasks.Task Test005_Should_Update_Stack_Async() } catch (Exception e) { + sw.Stop(); + TestReportHelper.LogAssertion(false, $"Exception: {e.GetType().Name} — {e.Message}", type: "Fail"); Assert.Fail(e.Message); } + finally + { + TestReportHelper.Flush(); + } } [TestMethod] [DoNotParallelize] public void Test006_Should_Fetch_Stack() { + TestReportHelper.Begin(); + var sw = Stopwatch.StartNew(); try { Stack stack = Contentstack.Client.Stack(Contentstack.Stack.APIKey); + TestReportHelper.LogRequest("stack.Fetch()", "GET", + $"https://{_host}/v3/stacks", + headers: new Dictionary { ["api_key"] = Contentstack.Stack.APIKey ?? "***" }); + ContentstackResponse contentstackResponse = stack.Fetch(); + sw.Stop(); + var body = contentstackResponse.OpenResponse(); + TestReportHelper.LogResponse((int)contentstackResponse.StatusCode, + contentstackResponse.StatusCode.ToString(), sw.ElapsedMilliseconds, body); var response = contentstackResponse.OpenJObjectResponse(); StackResponse model = contentstackResponse.OpenTResponse(); + TestReportHelper.LogAssertion(model.Stack.APIKey == Contentstack.Stack.APIKey, + "API key matches", expected: Contentstack.Stack.APIKey, actual: model.Stack.APIKey, type: "AreEqual"); Assert.IsNotNull(response); Assert.AreEqual(Contentstack.Stack.APIKey, model.Stack.APIKey); Assert.AreEqual(Contentstack.Stack.Name, model.Stack.Name); @@ -156,22 +262,40 @@ public void Test006_Should_Fetch_Stack() } catch (Exception e) { + sw.Stop(); + TestReportHelper.LogAssertion(false, $"Exception: {e.GetType().Name} — {e.Message}", type: "Fail"); Assert.Fail(e.Message); } + finally + { + TestReportHelper.Flush(); + } } [TestMethod] [DoNotParallelize] public async System.Threading.Tasks.Task Test007_Should_Fetch_StackAsync() { + TestReportHelper.Begin(); + var sw = Stopwatch.StartNew(); try { Stack stack = Contentstack.Client.Stack(Contentstack.Stack.APIKey); + TestReportHelper.LogRequest("stack.FetchAsync()", "GET", + $"https://{_host}/v3/stacks", + headers: new Dictionary { ["api_key"] = Contentstack.Stack.APIKey ?? "***" }); + ContentstackResponse contentstackResponse = await stack.FetchAsync(); + sw.Stop(); + var body = contentstackResponse.OpenResponse(); + TestReportHelper.LogResponse((int)contentstackResponse.StatusCode, + contentstackResponse.StatusCode.ToString(), sw.ElapsedMilliseconds, body); var response = contentstackResponse.OpenJObjectResponse(); StackResponse model = contentstackResponse.OpenTResponse(); + TestReportHelper.LogAssertion(model.Stack.APIKey == Contentstack.Stack.APIKey, + "API key matches", expected: Contentstack.Stack.APIKey, actual: model.Stack.APIKey, type: "AreEqual"); Assert.IsNotNull(response); Assert.AreEqual(Contentstack.Stack.APIKey, model.Stack.APIKey); Assert.AreEqual(Contentstack.Stack.Name, model.Stack.Name); @@ -181,14 +305,22 @@ public async System.Threading.Tasks.Task Test007_Should_Fetch_StackAsync() } catch (Exception e) { + sw.Stop(); + TestReportHelper.LogAssertion(false, $"Exception: {e.GetType().Name} — {e.Message}", type: "Fail"); Assert.Fail(e.Message); } + finally + { + TestReportHelper.Flush(); + } } [TestMethod] [DoNotParallelize] public void Test008_Add_Stack_Settings() { + TestReportHelper.Begin(); + var sw = Stopwatch.StartNew(); try { Stack stack = Contentstack.Client.Stack(Contentstack.Stack.APIKey); @@ -200,12 +332,21 @@ public void Test008_Add_Stack_Settings() { "sys_rte_allowed_tags", "figure" } } }; + TestReportHelper.LogRequest("stack.AddSettings()", "POST", + $"https://{_host}/v3/stacks/settings", + headers: new Dictionary { ["api_key"] = Contentstack.Stack.APIKey ?? "***" }); ContentstackResponse contentstackResponse = stack.AddSettings(settings); + sw.Stop(); + var body = contentstackResponse.OpenResponse(); + TestReportHelper.LogResponse((int)contentstackResponse.StatusCode, + contentstackResponse.StatusCode.ToString(), sw.ElapsedMilliseconds, body); var response = contentstackResponse.OpenJObjectResponse(); StackSettingsModel model = contentstackResponse.OpenTResponse(); + TestReportHelper.LogAssertion(model.Notice == "Stack settings updated successfully.", + "Notice matches", expected: "Stack settings updated successfully.", actual: model.Notice, type: "AreEqual"); Assert.IsNotNull(response); Assert.AreEqual("Stack settings updated successfully.", model.Notice); Assert.AreEqual(true, model.StackSettings.StackVariables["enforce_unique_urls"]); @@ -213,23 +354,39 @@ public void Test008_Add_Stack_Settings() } catch (Exception e) { + sw.Stop(); + TestReportHelper.LogAssertion(false, $"Exception: {e.GetType().Name} — {e.Message}", type: "Fail"); Assert.Fail(e.Message); } + finally + { + TestReportHelper.Flush(); + } } [TestMethod] [DoNotParallelize] public void Test009_Stack_Settings() { + TestReportHelper.Begin(); + var sw = Stopwatch.StartNew(); try { Stack stack = Contentstack.Client.Stack(Contentstack.Stack.APIKey); + TestReportHelper.LogRequest("stack.Settings()", "GET", + $"https://{_host}/v3/stacks/settings", + headers: new Dictionary { ["api_key"] = Contentstack.Stack.APIKey ?? "***" }); ContentstackResponse contentstackResponse = stack.Settings(); + sw.Stop(); + var body = contentstackResponse.OpenResponse(); + TestReportHelper.LogResponse((int)contentstackResponse.StatusCode, + contentstackResponse.StatusCode.ToString(), sw.ElapsedMilliseconds, body); var response = contentstackResponse.OpenJObjectResponse(); StackSettingsModel model = contentstackResponse.OpenTResponse(); + TestReportHelper.LogAssertion(response != null, "Response not null", type: "IsNotNull"); Assert.IsNotNull(response); Assert.IsNull(model.Notice); Assert.AreEqual(true, model.StackSettings.StackVariables["enforce_unique_urls"]); @@ -237,23 +394,40 @@ public void Test009_Stack_Settings() } catch (Exception e) { + sw.Stop(); + TestReportHelper.LogAssertion(false, $"Exception: {e.GetType().Name} — {e.Message}", type: "Fail"); Assert.Fail(e.Message); } + finally + { + TestReportHelper.Flush(); + } } [TestMethod] [DoNotParallelize] public void Test010_Reset_Stack_Settings() { + TestReportHelper.Begin(); + var sw = Stopwatch.StartNew(); try { Stack stack = Contentstack.Client.Stack(Contentstack.Stack.APIKey); + TestReportHelper.LogRequest("stack.ResetSettings()", "DELETE", + $"https://{_host}/v3/stacks/settings", + headers: new Dictionary { ["api_key"] = Contentstack.Stack.APIKey ?? "***" }); ContentstackResponse contentstackResponse = stack.ResetSettings(); + sw.Stop(); + var body = contentstackResponse.OpenResponse(); + TestReportHelper.LogResponse((int)contentstackResponse.StatusCode, + contentstackResponse.StatusCode.ToString(), sw.ElapsedMilliseconds, body); var response = contentstackResponse.OpenJObjectResponse(); StackSettingsModel model = contentstackResponse.OpenTResponse(); + TestReportHelper.LogAssertion(model.Notice == "Stack settings updated successfully.", + "Notice matches", expected: "Stack settings updated successfully.", actual: model.Notice, type: "AreEqual"); Assert.IsNotNull(response); Assert.AreEqual("Stack settings updated successfully.", model.Notice); Assert.AreEqual(true, model.StackSettings.StackVariables["enforce_unique_urls"]); @@ -261,14 +435,22 @@ public void Test010_Reset_Stack_Settings() } catch (Exception e) { + sw.Stop(); + TestReportHelper.LogAssertion(false, $"Exception: {e.GetType().Name} — {e.Message}", type: "Fail"); Assert.Fail(e.Message); } + finally + { + TestReportHelper.Flush(); + } } [TestMethod] [DoNotParallelize] public async System.Threading.Tasks.Task Test011_Add_Stack_Settings_Async() { + TestReportHelper.Begin(); + var sw = Stopwatch.StartNew(); try { Stack stack = Contentstack.Client.Stack(Contentstack.Stack.APIKey); @@ -279,35 +461,61 @@ public async System.Threading.Tasks.Task Test011_Add_Stack_Settings_Async() { "cs_only_breakline", true }, } }; + TestReportHelper.LogRequest("stack.AddSettingsAsync()", "POST", + $"https://{_host}/v3/stacks/settings", + headers: new Dictionary { ["api_key"] = Contentstack.Stack.APIKey ?? "***" }); ContentstackResponse contentstackResponse = await stack.AddSettingsAsync(settings); + sw.Stop(); + var body = contentstackResponse.OpenResponse(); + TestReportHelper.LogResponse((int)contentstackResponse.StatusCode, + contentstackResponse.StatusCode.ToString(), sw.ElapsedMilliseconds, body); var response = contentstackResponse.OpenJObjectResponse(); StackSettingsModel model = contentstackResponse.OpenTResponse(); + TestReportHelper.LogAssertion(model.Notice == "Stack settings updated successfully.", + "Notice matches", expected: "Stack settings updated successfully.", actual: model.Notice, type: "AreEqual"); Assert.IsNotNull(response); Assert.AreEqual("Stack settings updated successfully.", model.Notice); Assert.AreEqual(true, model.StackSettings.Rte["cs_only_breakline"]); } catch (Exception e) { + sw.Stop(); + TestReportHelper.LogAssertion(false, $"Exception: {e.GetType().Name} — {e.Message}", type: "Fail"); Assert.Fail(e.Message); } + finally + { + TestReportHelper.Flush(); + } } [TestMethod] [DoNotParallelize] public async System.Threading.Tasks.Task Test012_Reset_Stack_Settings_Async() { + TestReportHelper.Begin(); + var sw = Stopwatch.StartNew(); try { Stack stack = Contentstack.Client.Stack(Contentstack.Stack.APIKey); + TestReportHelper.LogRequest("stack.ResetSettingsAsync()", "DELETE", + $"https://{_host}/v3/stacks/settings", + headers: new Dictionary { ["api_key"] = Contentstack.Stack.APIKey ?? "***" }); ContentstackResponse contentstackResponse = await stack.ResetSettingsAsync(); + sw.Stop(); + var body = contentstackResponse.OpenResponse(); + TestReportHelper.LogResponse((int)contentstackResponse.StatusCode, + contentstackResponse.StatusCode.ToString(), sw.ElapsedMilliseconds, body); var response = contentstackResponse.OpenJObjectResponse(); StackSettingsModel model = contentstackResponse.OpenTResponse(); + TestReportHelper.LogAssertion(model.Notice == "Stack settings updated successfully.", + "Notice matches", expected: "Stack settings updated successfully.", actual: model.Notice, type: "AreEqual"); Assert.IsNotNull(response); Assert.AreEqual("Stack settings updated successfully.", model.Notice); Assert.AreEqual(true, model.StackSettings.StackVariables["enforce_unique_urls"]); @@ -315,31 +523,53 @@ public async System.Threading.Tasks.Task Test012_Reset_Stack_Settings_Async() } catch (Exception e) { + sw.Stop(); + TestReportHelper.LogAssertion(false, $"Exception: {e.GetType().Name} — {e.Message}", type: "Fail"); Assert.Fail(e.Message); } + finally + { + TestReportHelper.Flush(); + } } [TestMethod] [DoNotParallelize] public async System.Threading.Tasks.Task Test013_Stack_Settings_Async() { + TestReportHelper.Begin(); + var sw = Stopwatch.StartNew(); try { Stack stack = Contentstack.Client.Stack(Contentstack.Stack.APIKey); + TestReportHelper.LogRequest("stack.SettingsAsync()", "GET", + $"https://{_host}/v3/stacks/settings", + headers: new Dictionary { ["api_key"] = Contentstack.Stack.APIKey ?? "***" }); ContentstackResponse contentstackResponse = await stack.SettingsAsync(); + sw.Stop(); + var body = contentstackResponse.OpenResponse(); + TestReportHelper.LogResponse((int)contentstackResponse.StatusCode, + contentstackResponse.StatusCode.ToString(), sw.ElapsedMilliseconds, body); var response = contentstackResponse.OpenJObjectResponse(); StackSettingsModel model = contentstackResponse.OpenTResponse(); + TestReportHelper.LogAssertion(response != null, "Response not null", type: "IsNotNull"); Assert.IsNotNull(response); Assert.AreEqual(true, model.StackSettings.StackVariables["enforce_unique_urls"]); Assert.AreEqual("figure", model.StackSettings.StackVariables["sys_rte_allowed_tags"]); } catch (Exception e) { + sw.Stop(); + TestReportHelper.LogAssertion(false, $"Exception: {e.GetType().Name} — {e.Message}", type: "Fail"); Assert.Fail(e.Message); } + finally + { + TestReportHelper.Flush(); + } } } } diff --git a/Contentstack.Management.Core.Tests/IntegrationTest/Contentstack004_ReleaseTest.cs b/Contentstack.Management.Core.Tests/IntegrationTest/Contentstack004_ReleaseTest.cs index 66f9014..4f2a5d0 100644 --- a/Contentstack.Management.Core.Tests/IntegrationTest/Contentstack004_ReleaseTest.cs +++ b/Contentstack.Management.Core.Tests/IntegrationTest/Contentstack004_ReleaseTest.cs @@ -21,10 +21,17 @@ public class Contentstack004_ReleaseTest [TestInitialize] public async Task Initialize() { + TestReportHelper.Begin(); StackResponse response = StackResponse.getStack(Contentstack.Client.serializer); _stack = Contentstack.Client.Stack(response.Stack.APIKey); } + [TestCleanup] + public void Cleanup() + { + TestReportHelper.Flush(); + } + /// @@ -63,13 +70,19 @@ private string CreateTestRelease() public void Test001_Should_Create_Release() { string releaseUid = null; + var sw = System.Diagnostics.Stopwatch.StartNew(); try { releaseUid = CreateTestRelease(); Assert.IsNotNull(releaseUid); - + + TestReportHelper.LogRequest("_stack.Release().Create() + Fetch()", "GET", + $"https://{Contentstack.Client.contentstackOptions.Host}/v3/stacks/releases/{releaseUid}"); ContentstackResponse contentstackResponse = _stack.Release(releaseUid).Fetch(); + sw.Stop(); + TestReportHelper.LogResponse((int)contentstackResponse.StatusCode, + contentstackResponse.StatusCode.ToString(), sw.ElapsedMilliseconds, contentstackResponse.OpenResponse()); var response = contentstackResponse.OpenJObjectResponse(); Assert.IsNotNull(response); @@ -277,6 +290,8 @@ public async Task Test002_Should_Create_Release_Async() Assert.IsNotNull(releaseUid); ContentstackResponse contentstackResponse = await _stack.Release(releaseUid).FetchAsync(); + TestReportHelper.LogRequest("Release.FetchAsync", "GET", + $"https://{Contentstack.Client.contentstackOptions.Host}/v3/stacks/releases/{releaseUid}"); var response = contentstackResponse.OpenJObjectResponse(); Assert.IsNotNull(response); @@ -315,6 +330,8 @@ public void Test003_Should_Query_All_Releases() releaseUids = CreateSixNumberedReleases(); ContentstackResponse contentstackResponse = _stack.Release().Query().Find(); + TestReportHelper.LogRequest("Release.Query.Find", "GET", + $"https://{Contentstack.Client.contentstackOptions.Host}/v3/stacks/releases"); var response = contentstackResponse.OpenJObjectResponse(); Assert.IsNotNull(response); @@ -354,6 +371,8 @@ public async Task Test004_Should_Query_All_Releases_Async() releaseUids = await CreateSixNumberedReleasesAsync(); ContentstackResponse contentstackResponse = await _stack.Release().Query().FindAsync(); + TestReportHelper.LogRequest("Release.Query.FindAsync", "GET", + $"https://{Contentstack.Client.contentstackOptions.Host}/v3/stacks/releases"); var response = contentstackResponse.OpenJObjectResponse(); Assert.IsNotNull(response); @@ -394,6 +413,8 @@ public void Test005_Should_Fetch_Release() string releaseToFetch = releaseUids[2]; ContentstackResponse contentstackResponse = _stack.Release(releaseToFetch).Fetch(); + TestReportHelper.LogRequest("Release.Fetch", "GET", + $"https://{Contentstack.Client.contentstackOptions.Host}/v3/stacks/releases/{releaseToFetch}"); var response = contentstackResponse.OpenJObjectResponse(); Assert.IsNotNull(response); @@ -424,6 +445,8 @@ public async Task Test006_Should_Fetch_Release_Async() string releaseToFetch = releaseUids[4]; ContentstackResponse contentstackResponse = await _stack.Release(releaseToFetch).FetchAsync(); + TestReportHelper.LogRequest("Release.FetchAsync", "GET", + $"https://{Contentstack.Client.contentstackOptions.Host}/v3/stacks/releases/{releaseToFetch}"); var response = contentstackResponse.OpenJObjectResponse(); Assert.IsNotNull(response); @@ -461,6 +484,8 @@ public void Test007_Should_Update_Release() }; ContentstackResponse contentstackResponse = _stack.Release(releaseUid).Update(updateModel); + TestReportHelper.LogRequest("Release.Update", "PUT", + $"https://{Contentstack.Client.contentstackOptions.Host}/v3/stacks/releases/{releaseUid}"); var response = contentstackResponse.OpenJObjectResponse(); Assert.IsNotNull(response); @@ -506,6 +531,8 @@ public async Task Test008_Should_Update_Release_Async() }; ContentstackResponse contentstackResponse = await _stack.Release(releaseUid).UpdateAsync(updateModel); + TestReportHelper.LogRequest("Release.UpdateAsync", "PUT", + $"https://{Contentstack.Client.contentstackOptions.Host}/v3/stacks/releases/{releaseUid}"); var response = contentstackResponse.OpenJObjectResponse(); Assert.IsNotNull(response); @@ -547,6 +574,8 @@ public void Test009_Should_Clone_Release() string cloneDescription = _testReleaseDescription + " (Cloned)"; ContentstackResponse contentstackResponse = _stack.Release(originalReleaseUid).Clone(cloneName, cloneDescription); + TestReportHelper.LogRequest("Release.Clone", "POST", + $"https://{Contentstack.Client.contentstackOptions.Host}/v3/stacks/releases/{originalReleaseUid}/clone"); var response = contentstackResponse.OpenJObjectResponse(); Assert.IsNotNull(response); @@ -601,6 +630,8 @@ public async Task Test010_Should_Clone_Release_Async() string cloneDescription = _testReleaseDescription + " (Cloned Async)"; ContentstackResponse contentstackResponse = await _stack.Release(originalReleaseUid).CloneAsync(cloneName, cloneDescription); + TestReportHelper.LogRequest("Release.CloneAsync", "POST", + $"https://{Contentstack.Client.contentstackOptions.Host}/v3/stacks/releases/{originalReleaseUid}/clone"); var response = contentstackResponse.OpenJObjectResponse(); Assert.IsNotNull(response); @@ -653,6 +684,8 @@ public void Test011_Should_Query_Release_With_Parameters() parameters.Add("limit", "5"); ContentstackResponse contentstackResponse = _stack.Release().Query().Limit(5).Find(); + TestReportHelper.LogRequest("Release.Query.Limit.Find", "GET", + $"https://{Contentstack.Client.contentstackOptions.Host}/v3/stacks/releases"); var response = contentstackResponse.OpenJObjectResponse(); Assert.IsNotNull(response); @@ -678,6 +711,8 @@ public async Task Test012_Should_Query_Release_With_Parameters_Async() parameters.Add("limit", "5"); ContentstackResponse contentstackResponse = await _stack.Release().Query().Limit(5).FindAsync(); + TestReportHelper.LogRequest("Release.Query.Limit.FindAsync", "GET", + $"https://{Contentstack.Client.contentstackOptions.Host}/v3/stacks/releases"); var response = contentstackResponse.OpenJObjectResponse(); Assert.IsNotNull(response); @@ -709,6 +744,8 @@ public void Test013_Should_Create_Release_With_ParameterCollection() parameters.Add("include_count", "true"); ContentstackResponse contentstackResponse = _stack.Release().Create(releaseModel, parameters); + TestReportHelper.LogRequest("Release.Create (with params)", "POST", + $"https://{Contentstack.Client.contentstackOptions.Host}/v3/stacks/releases"); var response = contentstackResponse.OpenJObjectResponse(); Assert.IsNotNull(response); @@ -744,6 +781,8 @@ public async Task Test014_Should_Create_Release_With_ParameterCollection_Async() parameters.Add("include_count", "true"); ContentstackResponse contentstackResponse = await _stack.Release().CreateAsync(releaseModel, parameters); + TestReportHelper.LogRequest("Release.CreateAsync (with params)", "POST", + $"https://{Contentstack.Client.contentstackOptions.Host}/v3/stacks/releases"); var response = contentstackResponse.OpenJObjectResponse(); Assert.IsNotNull(response); @@ -771,6 +810,8 @@ public void Test015_Should_Get_Release_Items() releaseUid = CreateTestRelease(); ContentstackResponse contentstackResponse = _stack.Release(releaseUid).Item().GetAll(); + TestReportHelper.LogRequest("Release.Item.GetAll", "GET", + $"https://{Contentstack.Client.contentstackOptions.Host}/v3/stacks/releases/{releaseUid}/items"); var response = contentstackResponse.OpenJObjectResponse(); Assert.IsNotNull(response); @@ -805,6 +846,8 @@ public async Task Test016_Should_Get_Release_Items_Async() releaseUid = await CreateTestReleaseAsync(); ContentstackResponse contentstackResponse = await _stack.Release(releaseUid).Item().GetAllAsync(); + TestReportHelper.LogRequest("Release.Item.GetAllAsync", "GET", + $"https://{Contentstack.Client.contentstackOptions.Host}/v3/stacks/releases/{releaseUid}/items"); var response = contentstackResponse.OpenJObjectResponse(); Assert.IsNotNull(response); @@ -837,6 +880,8 @@ public void Test017_Should_Handle_Release_Not_Found() { string nonExistentUid = "non_existent_release_uid_12345"; + TestReportHelper.LogRequest("Release.Fetch (not found - expected error)", "GET", + $"https://{Contentstack.Client.contentstackOptions.Host}/v3/stacks/releases/{nonExistentUid}"); try { ContentstackResponse contentstackResponse = _stack.Release(nonExistentUid).Fetch(); @@ -879,6 +924,8 @@ public async Task Test018_Should_Handle_Release_Not_Found_Async() { string nonExistentUid = "non_existent_release_uid_12345"; + TestReportHelper.LogRequest("Release.FetchAsync (not found - expected error)", "GET", + $"https://{Contentstack.Client.contentstackOptions.Host}/v3/stacks/releases/{nonExistentUid}"); try { ContentstackResponse contentstackResponse = await _stack.Release(nonExistentUid).FetchAsync(); @@ -916,6 +963,7 @@ public async Task Test018_Should_Handle_Release_Not_Found_Async() [DoNotParallelize] public void Test019_Should_Delete_Release() { + var sw = System.Diagnostics.Stopwatch.StartNew(); try { var releaseModel = new ReleaseModel @@ -930,13 +978,22 @@ public void Test019_Should_Delete_Release() var createResponseJson = createResponse.OpenJObjectResponse(); string releaseToDeleteUid = createResponseJson["release"]["uid"].ToString(); + TestReportHelper.LogRequest("_stack.Release(uid).Delete()", "DELETE", + $"https://{Contentstack.Client.contentstackOptions.Host}/v3/stacks/releases/{releaseToDeleteUid}"); ContentstackResponse contentstackResponse = _stack.Release(releaseToDeleteUid).Delete(); + sw.Stop(); + TestReportHelper.LogResponse((int)contentstackResponse.StatusCode, + contentstackResponse.StatusCode.ToString(), sw.ElapsedMilliseconds, contentstackResponse.OpenResponse()); + TestReportHelper.LogAssertion(contentstackResponse != null, "Response not null", type: "IsNotNull"); + TestReportHelper.LogAssertion(contentstackResponse.IsSuccessStatusCode, "Response is successful", type: "IsTrue"); Assert.IsNotNull(contentstackResponse); Assert.IsTrue(contentstackResponse.IsSuccessStatusCode); } catch (Exception e) { + sw.Stop(); + TestReportHelper.LogAssertion(false, $"Exception: {e.GetType().Name} — {e.Message}", type: "Fail"); Assert.Fail($"Delete release failed: {e.Message}"); } } @@ -960,6 +1017,8 @@ public async Task Test020_Should_Delete_Release_Async() string releaseToDeleteUid = createResponseJson["release"]["uid"].ToString(); ContentstackResponse contentstackResponse = await _stack.Release(releaseToDeleteUid).DeleteAsync(); + TestReportHelper.LogRequest("Release.DeleteAsync", "DELETE", + $"https://{Contentstack.Client.contentstackOptions.Host}/v3/stacks/releases/{releaseToDeleteUid}"); Assert.IsNotNull(contentstackResponse); Assert.IsTrue(contentstackResponse.IsSuccessStatusCode); @@ -994,6 +1053,8 @@ public void Test021_Should_Delete_Release_Without_Content_Type_Header() string releaseToDeleteUid = createResponseJson["release"]["uid"].ToString(); ContentstackResponse deleteResponse = _stack.Release(releaseToDeleteUid).Delete(); + TestReportHelper.LogRequest("Release.Delete (no Content-Type)", "DELETE", + $"https://{Contentstack.Client.contentstackOptions.Host}/v3/stacks/releases/{releaseToDeleteUid}"); Assert.IsNotNull(deleteResponse); Assert.IsTrue(deleteResponse.IsSuccessStatusCode, "Delete release (without Content-Type) must succeed."); @@ -1038,6 +1099,8 @@ public async Task Test022_Should_Delete_Release_Async_Without_Content_Type_Heade string releaseToDeleteUid = createResponseJson["release"]["uid"].ToString(); ContentstackResponse deleteResponse = await _stack.Release(releaseToDeleteUid).DeleteAsync(); + TestReportHelper.LogRequest("Release.DeleteAsync (no Content-Type)", "DELETE", + $"https://{Contentstack.Client.contentstackOptions.Host}/v3/stacks/releases/{releaseToDeleteUid}"); Assert.IsNotNull(deleteResponse); Assert.IsTrue(deleteResponse.IsSuccessStatusCode, "Delete release async (without Content-Type) must succeed."); diff --git a/Contentstack.Management.Core.Tests/IntegrationTest/Contentstack011_GlobalFieldTest.cs b/Contentstack.Management.Core.Tests/IntegrationTest/Contentstack011_GlobalFieldTest.cs index aa7d4b7..eeed31c 100644 --- a/Contentstack.Management.Core.Tests/IntegrationTest/Contentstack011_GlobalFieldTest.cs +++ b/Contentstack.Management.Core.Tests/IntegrationTest/Contentstack011_GlobalFieldTest.cs @@ -1,5 +1,6 @@ -using System; +using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using AutoFixture; using Contentstack.Management.Core.Models; @@ -13,8 +14,11 @@ public class Contentstack004_GlobalFieldTest { private Stack _stack; private ContentModelling _modelling; + + private static string _host => Contentstack.Client.contentstackOptions.Host; + [TestInitialize] - public void Initialize () + public void Initialize() { StackResponse response = StackResponse.getStack(Contentstack.Client.serializer); _stack = Contentstack.Client.Stack(response.Stack.APIKey); @@ -25,120 +29,346 @@ public void Initialize () [DoNotParallelize] public void Test001_Should_Create_Global_Field() { - ContentstackResponse response = _stack.GlobalField().Create(_modelling); - GlobalFieldModel globalField = response.OpenTResponse(); - Assert.IsNotNull(response); - Assert.IsNotNull(globalField); - Assert.IsNotNull(globalField.Modelling); - Assert.AreEqual(_modelling.Title, globalField.Modelling.Title); - Assert.AreEqual(_modelling.Uid, globalField.Modelling.Uid); - Assert.AreEqual(_modelling.Schema.Count, globalField.Modelling.Schema.Count); + TestReportHelper.Begin(); + var sw = Stopwatch.StartNew(); + try + { + TestReportHelper.LogRequest("_stack.GlobalField().Create()", "POST", + $"https://{_host}/v3/stacks/global_fields"); + + ContentstackResponse response = _stack.GlobalField().Create(_modelling); + sw.Stop(); + TestReportHelper.LogResponse((int)response.StatusCode, + response.StatusCode.ToString(), sw.ElapsedMilliseconds, response.OpenResponse()); + + GlobalFieldModel globalField = response.OpenTResponse(); + + TestReportHelper.LogAssertion(response != null, "Response not null", type: "IsNotNull"); + TestReportHelper.LogAssertion(globalField?.Modelling?.Title == _modelling.Title, + "Title matches", expected: _modelling.Title, actual: globalField?.Modelling?.Title, type: "AreEqual"); + Assert.IsNotNull(response); + Assert.IsNotNull(globalField); + Assert.IsNotNull(globalField.Modelling); + Assert.AreEqual(_modelling.Title, globalField.Modelling.Title); + Assert.AreEqual(_modelling.Uid, globalField.Modelling.Uid); + Assert.AreEqual(_modelling.Schema.Count, globalField.Modelling.Schema.Count); + } + catch (Exception e) + { + sw.Stop(); + TestReportHelper.LogAssertion(false, $"Exception: {e.GetType().Name} — {e.Message}", type: "Fail"); + throw; + } + finally + { + TestReportHelper.Flush(); + } } [TestMethod] [DoNotParallelize] public void Test002_Should_Fetch_Global_Field() { - ContentstackResponse response = _stack.GlobalField(_modelling.Uid).Fetch(); - GlobalFieldModel globalField = response.OpenTResponse(); - Assert.IsNotNull(response); - Assert.IsNotNull(globalField); - Assert.IsNotNull(globalField.Modelling); - Assert.AreEqual(_modelling.Title, globalField.Modelling.Title); - Assert.AreEqual(_modelling.Uid, globalField.Modelling.Uid); - Assert.AreEqual(_modelling.Schema.Count, globalField.Modelling.Schema.Count); + TestReportHelper.Begin(); + var sw = Stopwatch.StartNew(); + try + { + TestReportHelper.LogRequest("_stack.GlobalField(uid).Fetch()", "GET", + $"https://{_host}/v3/stacks/global_fields/{_modelling.Uid}"); + + ContentstackResponse response = _stack.GlobalField(_modelling.Uid).Fetch(); + sw.Stop(); + TestReportHelper.LogResponse((int)response.StatusCode, + response.StatusCode.ToString(), sw.ElapsedMilliseconds, response.OpenResponse()); + + GlobalFieldModel globalField = response.OpenTResponse(); + + TestReportHelper.LogAssertion(response != null, "Response not null", type: "IsNotNull"); + TestReportHelper.LogAssertion(globalField?.Modelling?.Uid == _modelling.Uid, + "UID matches", expected: _modelling.Uid, actual: globalField?.Modelling?.Uid, type: "AreEqual"); + Assert.IsNotNull(response); + Assert.IsNotNull(globalField); + Assert.IsNotNull(globalField.Modelling); + Assert.AreEqual(_modelling.Title, globalField.Modelling.Title); + Assert.AreEqual(_modelling.Uid, globalField.Modelling.Uid); + Assert.AreEqual(_modelling.Schema.Count, globalField.Modelling.Schema.Count); + } + catch (Exception e) + { + sw.Stop(); + TestReportHelper.LogAssertion(false, $"Exception: {e.GetType().Name} — {e.Message}", type: "Fail"); + throw; + } + finally + { + TestReportHelper.Flush(); + } } [TestMethod] [DoNotParallelize] public async System.Threading.Tasks.Task Test003_Should_Fetch_Async_Global_Field() { - ContentstackResponse response = await _stack.GlobalField(_modelling.Uid).FetchAsync(); - GlobalFieldModel globalField = response.OpenTResponse(); - Assert.IsNotNull(response); - Assert.IsNotNull(globalField); - Assert.IsNotNull(globalField.Modelling); - Assert.AreEqual(_modelling.Title, globalField.Modelling.Title); - Assert.AreEqual(_modelling.Uid, globalField.Modelling.Uid); - Assert.AreEqual(_modelling.Schema.Count, globalField.Modelling.Schema.Count); + TestReportHelper.Begin(); + var sw = Stopwatch.StartNew(); + try + { + TestReportHelper.LogRequest("_stack.GlobalField(uid).FetchAsync()", "GET", + $"https://{_host}/v3/stacks/global_fields/{_modelling.Uid}"); + + ContentstackResponse response = await _stack.GlobalField(_modelling.Uid).FetchAsync(); + sw.Stop(); + TestReportHelper.LogResponse((int)response.StatusCode, + response.StatusCode.ToString(), sw.ElapsedMilliseconds, response.OpenResponse()); + + GlobalFieldModel globalField = response.OpenTResponse(); + + TestReportHelper.LogAssertion(response != null, "Response not null", type: "IsNotNull"); + Assert.IsNotNull(response); + Assert.IsNotNull(globalField); + Assert.IsNotNull(globalField.Modelling); + Assert.AreEqual(_modelling.Title, globalField.Modelling.Title); + Assert.AreEqual(_modelling.Uid, globalField.Modelling.Uid); + Assert.AreEqual(_modelling.Schema.Count, globalField.Modelling.Schema.Count); + } + catch (Exception e) + { + sw.Stop(); + TestReportHelper.LogAssertion(false, $"Exception: {e.GetType().Name} — {e.Message}", type: "Fail"); + throw; + } + finally + { + TestReportHelper.Flush(); + } } [TestMethod] [DoNotParallelize] public void Test004_Should_Update_Global_Field() { - _modelling.Title = "Updated title"; - ContentstackResponse response = _stack.GlobalField(_modelling.Uid).Update(_modelling); - GlobalFieldModel globalField = response.OpenTResponse(); - Assert.IsNotNull(response); - Assert.IsNotNull(globalField); - Assert.IsNotNull(globalField.Modelling); - Assert.AreEqual(_modelling.Title, globalField.Modelling.Title); - Assert.AreEqual(_modelling.Uid, globalField.Modelling.Uid); - Assert.AreEqual(_modelling.Schema.Count, globalField.Modelling.Schema.Count); + TestReportHelper.Begin(); + var sw = Stopwatch.StartNew(); + try + { + _modelling.Title = "Updated title"; + TestReportHelper.LogRequest("_stack.GlobalField(uid).Update()", "PUT", + $"https://{_host}/v3/stacks/global_fields/{_modelling.Uid}"); + + ContentstackResponse response = _stack.GlobalField(_modelling.Uid).Update(_modelling); + sw.Stop(); + TestReportHelper.LogResponse((int)response.StatusCode, + response.StatusCode.ToString(), sw.ElapsedMilliseconds, response.OpenResponse()); + + GlobalFieldModel globalField = response.OpenTResponse(); + + TestReportHelper.LogAssertion(globalField?.Modelling?.Title == _modelling.Title, + "Updated title matches", expected: _modelling.Title, actual: globalField?.Modelling?.Title, type: "AreEqual"); + Assert.IsNotNull(response); + Assert.IsNotNull(globalField); + Assert.IsNotNull(globalField.Modelling); + Assert.AreEqual(_modelling.Title, globalField.Modelling.Title); + Assert.AreEqual(_modelling.Uid, globalField.Modelling.Uid); + Assert.AreEqual(_modelling.Schema.Count, globalField.Modelling.Schema.Count); + } + catch (Exception e) + { + sw.Stop(); + TestReportHelper.LogAssertion(false, $"Exception: {e.GetType().Name} — {e.Message}", type: "Fail"); + throw; + } + finally + { + TestReportHelper.Flush(); + } } [TestMethod] [DoNotParallelize] public async System.Threading.Tasks.Task Test005_Should_Update_Async_Global_Field() { - _modelling.Title = "First Async"; - ContentstackResponse response = await _stack.GlobalField(_modelling.Uid).UpdateAsync(_modelling); - GlobalFieldModel globalField = response.OpenTResponse(); - Assert.IsNotNull(response); - Assert.IsNotNull(globalField); - Assert.IsNotNull(globalField.Modelling); - Assert.AreEqual(_modelling.Title, globalField.Modelling.Title); - Assert.AreEqual(_modelling.Uid, globalField.Modelling.Uid); - Assert.AreEqual(_modelling.Schema.Count, globalField.Modelling.Schema.Count); + TestReportHelper.Begin(); + var sw = Stopwatch.StartNew(); + try + { + _modelling.Title = "First Async"; + TestReportHelper.LogRequest("_stack.GlobalField(uid).UpdateAsync()", "PUT", + $"https://{_host}/v3/stacks/global_fields/{_modelling.Uid}"); + + ContentstackResponse response = await _stack.GlobalField(_modelling.Uid).UpdateAsync(_modelling); + sw.Stop(); + TestReportHelper.LogResponse((int)response.StatusCode, + response.StatusCode.ToString(), sw.ElapsedMilliseconds, response.OpenResponse()); + + GlobalFieldModel globalField = response.OpenTResponse(); + + TestReportHelper.LogAssertion(globalField?.Modelling?.Title == _modelling.Title, + "Title matches", expected: _modelling.Title, actual: globalField?.Modelling?.Title, type: "AreEqual"); + Assert.IsNotNull(response); + Assert.IsNotNull(globalField); + Assert.IsNotNull(globalField.Modelling); + Assert.AreEqual(_modelling.Title, globalField.Modelling.Title); + Assert.AreEqual(_modelling.Uid, globalField.Modelling.Uid); + Assert.AreEqual(_modelling.Schema.Count, globalField.Modelling.Schema.Count); + } + catch (Exception e) + { + sw.Stop(); + TestReportHelper.LogAssertion(false, $"Exception: {e.GetType().Name} — {e.Message}", type: "Fail"); + throw; + } + finally + { + TestReportHelper.Flush(); + } } [TestMethod] [DoNotParallelize] public void Test006_Should_Query_Global_Field() { - ContentstackResponse response = _stack.GlobalField().Query().Find(); - GlobalFieldsModel globalField = response.OpenTResponse(); - Assert.IsNotNull(response); - Assert.IsNotNull(globalField); - Assert.IsNotNull(globalField.Modellings); - Assert.AreEqual(1, globalField.Modellings.Count); + TestReportHelper.Begin(); + var sw = Stopwatch.StartNew(); + try + { + TestReportHelper.LogRequest("_stack.GlobalField().Query().Find()", "GET", + $"https://{_host}/v3/stacks/global_fields"); + + ContentstackResponse response = _stack.GlobalField().Query().Find(); + sw.Stop(); + TestReportHelper.LogResponse((int)response.StatusCode, + response.StatusCode.ToString(), sw.ElapsedMilliseconds, response.OpenResponse()); + + GlobalFieldsModel globalField = response.OpenTResponse(); + + TestReportHelper.LogAssertion(globalField?.Modellings?.Count == 1, + "Modellings count is 1", expected: "1", actual: globalField?.Modellings?.Count.ToString(), type: "AreEqual"); + Assert.IsNotNull(response); + Assert.IsNotNull(globalField); + Assert.IsNotNull(globalField.Modellings); + Assert.AreEqual(1, globalField.Modellings.Count); + } + catch (Exception e) + { + sw.Stop(); + TestReportHelper.LogAssertion(false, $"Exception: {e.GetType().Name} — {e.Message}", type: "Fail"); + throw; + } + finally + { + TestReportHelper.Flush(); + } } [TestMethod] [DoNotParallelize] public void Test006a_Should_Query_Global_Field_With_ApiVersion() { - ContentstackResponse response = _stack.GlobalField(apiVersion: "3.2").Query().Find(); - GlobalFieldsModel globalField = response.OpenTResponse(); - Assert.IsNotNull(response); - Assert.IsNotNull(globalField); - Assert.IsNotNull(globalField.Modellings); - Assert.AreEqual(1, globalField.Modellings.Count); + TestReportHelper.Begin(); + var sw = Stopwatch.StartNew(); + try + { + TestReportHelper.LogRequest("_stack.GlobalField(apiVersion: 3.2).Query().Find()", "GET", + $"https://{_host}/v3/stacks/global_fields", + headers: new System.Collections.Generic.Dictionary { ["api_version"] = "3.2" }); + + ContentstackResponse response = _stack.GlobalField(apiVersion: "3.2").Query().Find(); + sw.Stop(); + TestReportHelper.LogResponse((int)response.StatusCode, + response.StatusCode.ToString(), sw.ElapsedMilliseconds, response.OpenResponse()); + + GlobalFieldsModel globalField = response.OpenTResponse(); + + TestReportHelper.LogAssertion(response != null, "Response not null", type: "IsNotNull"); + Assert.IsNotNull(response); + Assert.IsNotNull(globalField); + Assert.IsNotNull(globalField.Modellings); + Assert.AreEqual(1, globalField.Modellings.Count); + } + catch (Exception e) + { + sw.Stop(); + TestReportHelper.LogAssertion(false, $"Exception: {e.GetType().Name} — {e.Message}", type: "Fail"); + throw; + } + finally + { + TestReportHelper.Flush(); + } } [TestMethod] [DoNotParallelize] public async System.Threading.Tasks.Task Test007_Should_Query_Async_Global_Field() { - ContentstackResponse response = await _stack.GlobalField().Query().FindAsync(); - GlobalFieldsModel globalField = response.OpenTResponse(); - Assert.IsNotNull(response); - Assert.IsNotNull(globalField); - Assert.IsNotNull(globalField.Modellings); - Assert.AreEqual(1, globalField.Modellings.Count); + TestReportHelper.Begin(); + var sw = Stopwatch.StartNew(); + try + { + TestReportHelper.LogRequest("_stack.GlobalField().Query().FindAsync()", "GET", + $"https://{_host}/v3/stacks/global_fields"); + + ContentstackResponse response = await _stack.GlobalField().Query().FindAsync(); + sw.Stop(); + TestReportHelper.LogResponse((int)response.StatusCode, + response.StatusCode.ToString(), sw.ElapsedMilliseconds, response.OpenResponse()); + + GlobalFieldsModel globalField = response.OpenTResponse(); + + TestReportHelper.LogAssertion(globalField?.Modellings?.Count == 1, + "Modellings count is 1", expected: "1", actual: globalField?.Modellings?.Count.ToString(), type: "AreEqual"); + Assert.IsNotNull(response); + Assert.IsNotNull(globalField); + Assert.IsNotNull(globalField.Modellings); + Assert.AreEqual(1, globalField.Modellings.Count); + } + catch (Exception e) + { + sw.Stop(); + TestReportHelper.LogAssertion(false, $"Exception: {e.GetType().Name} — {e.Message}", type: "Fail"); + throw; + } + finally + { + TestReportHelper.Flush(); + } } [TestMethod] [DoNotParallelize] public async System.Threading.Tasks.Task Test007a_Should_Query_Async_Global_Field_With_ApiVersion() { - ContentstackResponse response = await _stack.GlobalField(apiVersion: "3.2").Query().FindAsync(); - GlobalFieldsModel globalField = response.OpenTResponse(); - Assert.IsNotNull(response); - Assert.IsNotNull(globalField); - Assert.IsNotNull(globalField.Modellings); - Assert.AreEqual(1, globalField.Modellings.Count); + TestReportHelper.Begin(); + var sw = Stopwatch.StartNew(); + try + { + TestReportHelper.LogRequest("_stack.GlobalField(apiVersion: 3.2).Query().FindAsync()", "GET", + $"https://{_host}/v3/stacks/global_fields", + headers: new System.Collections.Generic.Dictionary { ["api_version"] = "3.2" }); + + ContentstackResponse response = await _stack.GlobalField(apiVersion: "3.2").Query().FindAsync(); + sw.Stop(); + TestReportHelper.LogResponse((int)response.StatusCode, + response.StatusCode.ToString(), sw.ElapsedMilliseconds, response.OpenResponse()); + + GlobalFieldsModel globalField = response.OpenTResponse(); + + TestReportHelper.LogAssertion(response != null, "Response not null", type: "IsNotNull"); + Assert.IsNotNull(response); + Assert.IsNotNull(globalField); + Assert.IsNotNull(globalField.Modellings); + Assert.AreEqual(1, globalField.Modellings.Count); + } + catch (Exception e) + { + sw.Stop(); + TestReportHelper.LogAssertion(false, $"Exception: {e.GetType().Name} — {e.Message}", type: "Fail"); + throw; + } + finally + { + TestReportHelper.Flush(); + } } } } diff --git a/Contentstack.Management.Core.Tests/IntegrationTest/Contentstack012_ContentTypeTest.cs b/Contentstack.Management.Core.Tests/IntegrationTest/Contentstack012_ContentTypeTest.cs index d9b006f..825563f 100644 --- a/Contentstack.Management.Core.Tests/IntegrationTest/Contentstack012_ContentTypeTest.cs +++ b/Contentstack.Management.Core.Tests/IntegrationTest/Contentstack012_ContentTypeTest.cs @@ -1,5 +1,6 @@ -using System; +using System; using System.Collections.Generic; +using System.Diagnostics; using Contentstack.Management.Core.Models; using Contentstack.Management.Core.Tests.Model; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -13,96 +14,221 @@ public class Contentstack005_ContentTypeTest private ContentModelling _singlePage; private ContentModelling _multiPage; + private static string _host => Contentstack.Client.contentstackOptions.Host; + [TestInitialize] - public void Initialize () + public void Initialize() { StackResponse response = StackResponse.getStack(Contentstack.Client.serializer); _stack = Contentstack.Client.Stack(response.Stack.APIKey); _singlePage = Contentstack.serialize(Contentstack.Client.serializer, "singlepageCT.json"); - _multiPage = Contentstack.serialize(Contentstack.Client.serializer, "multiPageCT.json"); + _multiPage = Contentstack.serialize(Contentstack.Client.serializer, "multiPageCT.json"); } [TestMethod] [DoNotParallelize] public void Test001_Should_Create_Content_Type() { - ContentstackResponse response = _stack.ContentType().Create(_singlePage); - ContentTypeModel ContentType = response.OpenTResponse(); - Assert.IsNotNull(response); - Assert.IsNotNull(ContentType); - Assert.IsNotNull(ContentType.Modelling); - Assert.AreEqual(_singlePage.Title, ContentType.Modelling.Title); - Assert.AreEqual(_singlePage.Uid, ContentType.Modelling.Uid); - Assert.AreEqual(_singlePage.Schema.Count, ContentType.Modelling.Schema.Count); + TestReportHelper.Begin(); + var sw = Stopwatch.StartNew(); + try + { + TestReportHelper.LogRequest("_stack.ContentType().Create(_singlePage)", "POST", + $"https://{_host}/v3/stacks/content_types"); + + ContentstackResponse response = _stack.ContentType().Create(_singlePage); + sw.Stop(); + TestReportHelper.LogResponse((int)response.StatusCode, + response.StatusCode.ToString(), sw.ElapsedMilliseconds, response.OpenResponse()); + + ContentTypeModel ContentType = response.OpenTResponse(); + + TestReportHelper.LogAssertion(ContentType?.Modelling?.Title == _singlePage.Title, + "Title matches", expected: _singlePage.Title, actual: ContentType?.Modelling?.Title, type: "AreEqual"); + Assert.IsNotNull(response); + Assert.IsNotNull(ContentType); + Assert.IsNotNull(ContentType.Modelling); + Assert.AreEqual(_singlePage.Title, ContentType.Modelling.Title); + Assert.AreEqual(_singlePage.Uid, ContentType.Modelling.Uid); + Assert.AreEqual(_singlePage.Schema.Count, ContentType.Modelling.Schema.Count); + } + catch (Exception e) + { + sw.Stop(); + TestReportHelper.LogAssertion(false, $"Exception: {e.GetType().Name} — {e.Message}", type: "Fail"); + throw; + } + finally + { + TestReportHelper.Flush(); + } } [TestMethod] [DoNotParallelize] public void Test002_Should_Create_Content_Type() { - ContentstackResponse response = _stack.ContentType().Create(_multiPage); - ContentTypeModel ContentType = response.OpenTResponse(); - Assert.IsNotNull(response); - Assert.IsNotNull(ContentType); - Assert.IsNotNull(ContentType.Modelling); - Assert.AreEqual(_multiPage.Title, ContentType.Modelling.Title); - Assert.AreEqual(_multiPage.Uid, ContentType.Modelling.Uid); - Assert.AreEqual(_multiPage.Schema.Count, ContentType.Modelling.Schema.Count); + TestReportHelper.Begin(); + var sw = Stopwatch.StartNew(); + try + { + TestReportHelper.LogRequest("_stack.ContentType().Create(_multiPage)", "POST", + $"https://{_host}/v3/stacks/content_types"); + + ContentstackResponse response = _stack.ContentType().Create(_multiPage); + sw.Stop(); + TestReportHelper.LogResponse((int)response.StatusCode, + response.StatusCode.ToString(), sw.ElapsedMilliseconds, response.OpenResponse()); + + ContentTypeModel ContentType = response.OpenTResponse(); + + TestReportHelper.LogAssertion(ContentType?.Modelling?.Title == _multiPage.Title, + "Title matches", expected: _multiPage.Title, actual: ContentType?.Modelling?.Title, type: "AreEqual"); + Assert.IsNotNull(response); + Assert.IsNotNull(ContentType); + Assert.IsNotNull(ContentType.Modelling); + Assert.AreEqual(_multiPage.Title, ContentType.Modelling.Title); + Assert.AreEqual(_multiPage.Uid, ContentType.Modelling.Uid); + Assert.AreEqual(_multiPage.Schema.Count, ContentType.Modelling.Schema.Count); + } + catch (Exception e) + { + sw.Stop(); + TestReportHelper.LogAssertion(false, $"Exception: {e.GetType().Name} — {e.Message}", type: "Fail"); + throw; + } + finally + { + TestReportHelper.Flush(); + } } [TestMethod] [DoNotParallelize] public void Test003_Should_Fetch_Content_Type() { - ContentstackResponse response = _stack.ContentType(_multiPage.Uid).Fetch(); - ContentTypeModel ContentType = response.OpenTResponse(); - Assert.IsNotNull(response); - Assert.IsNotNull(ContentType); - Assert.IsNotNull(ContentType.Modelling); - Assert.AreEqual(_multiPage.Title, ContentType.Modelling.Title); - Assert.AreEqual(_multiPage.Uid, ContentType.Modelling.Uid); - Assert.AreEqual(_multiPage.Schema.Count, ContentType.Modelling.Schema.Count); + TestReportHelper.Begin(); + var sw = Stopwatch.StartNew(); + try + { + TestReportHelper.LogRequest("_stack.ContentType(uid).Fetch()", "GET", + $"https://{_host}/v3/stacks/content_types/{_multiPage.Uid}"); + + ContentstackResponse response = _stack.ContentType(_multiPage.Uid).Fetch(); + sw.Stop(); + TestReportHelper.LogResponse((int)response.StatusCode, + response.StatusCode.ToString(), sw.ElapsedMilliseconds, response.OpenResponse()); + + ContentTypeModel ContentType = response.OpenTResponse(); + + TestReportHelper.LogAssertion(ContentType?.Modelling?.Uid == _multiPage.Uid, + "UID matches", expected: _multiPage.Uid, actual: ContentType?.Modelling?.Uid, type: "AreEqual"); + Assert.IsNotNull(response); + Assert.IsNotNull(ContentType); + Assert.IsNotNull(ContentType.Modelling); + Assert.AreEqual(_multiPage.Title, ContentType.Modelling.Title); + Assert.AreEqual(_multiPage.Uid, ContentType.Modelling.Uid); + Assert.AreEqual(_multiPage.Schema.Count, ContentType.Modelling.Schema.Count); + } + catch (Exception e) + { + sw.Stop(); + TestReportHelper.LogAssertion(false, $"Exception: {e.GetType().Name} — {e.Message}", type: "Fail"); + throw; + } + finally + { + TestReportHelper.Flush(); + } } [TestMethod] [DoNotParallelize] public async System.Threading.Tasks.Task Test004_Should_Fetch_Async_Content_Type() { - ContentstackResponse response = await _stack.ContentType(_singlePage.Uid).FetchAsync(); - ContentTypeModel ContentType = response.OpenTResponse(); - Assert.IsNotNull(response); - Assert.IsNotNull(ContentType); - Assert.IsNotNull(ContentType.Modelling); - Assert.AreEqual(_singlePage.Title, ContentType.Modelling.Title); - Assert.AreEqual(_singlePage.Uid, ContentType.Modelling.Uid); - Assert.AreEqual(_singlePage.Schema.Count, ContentType.Modelling.Schema.Count); + TestReportHelper.Begin(); + var sw = Stopwatch.StartNew(); + try + { + TestReportHelper.LogRequest("_stack.ContentType(uid).FetchAsync()", "GET", + $"https://{_host}/v3/stacks/content_types/{_singlePage.Uid}"); + + ContentstackResponse response = await _stack.ContentType(_singlePage.Uid).FetchAsync(); + sw.Stop(); + TestReportHelper.LogResponse((int)response.StatusCode, + response.StatusCode.ToString(), sw.ElapsedMilliseconds, response.OpenResponse()); + + ContentTypeModel ContentType = response.OpenTResponse(); + + TestReportHelper.LogAssertion(ContentType?.Modelling?.Uid == _singlePage.Uid, + "UID matches", expected: _singlePage.Uid, actual: ContentType?.Modelling?.Uid, type: "AreEqual"); + Assert.IsNotNull(response); + Assert.IsNotNull(ContentType); + Assert.IsNotNull(ContentType.Modelling); + Assert.AreEqual(_singlePage.Title, ContentType.Modelling.Title); + Assert.AreEqual(_singlePage.Uid, ContentType.Modelling.Uid); + Assert.AreEqual(_singlePage.Schema.Count, ContentType.Modelling.Schema.Count); + } + catch (Exception e) + { + sw.Stop(); + TestReportHelper.LogAssertion(false, $"Exception: {e.GetType().Name} — {e.Message}", type: "Fail"); + throw; + } + finally + { + TestReportHelper.Flush(); + } } [TestMethod] [DoNotParallelize] public void Test005_Should_Update_Content_Type() { - _multiPage.Schema = Contentstack.serializeArray>(Contentstack.Client.serializer, "contentTypeSchema.json"); ; - ContentstackResponse response = _stack.ContentType(_multiPage.Uid).Update(_multiPage); - ContentTypeModel ContentType = response.OpenTResponse(); - Assert.IsNotNull(response); - Assert.IsNotNull(ContentType); - Assert.IsNotNull(ContentType.Modelling); - Assert.AreEqual(_multiPage.Title, ContentType.Modelling.Title); - Assert.AreEqual(_multiPage.Uid, ContentType.Modelling.Uid); - Assert.AreEqual(_multiPage.Schema.Count, ContentType.Modelling.Schema.Count); + TestReportHelper.Begin(); + var sw = Stopwatch.StartNew(); + try + { + _multiPage.Schema = Contentstack.serializeArray>(Contentstack.Client.serializer, "contentTypeSchema.json"); + TestReportHelper.LogRequest("_stack.ContentType(uid).Update()", "PUT", + $"https://{_host}/v3/stacks/content_types/{_multiPage.Uid}"); + + ContentstackResponse response = _stack.ContentType(_multiPage.Uid).Update(_multiPage); + sw.Stop(); + TestReportHelper.LogResponse((int)response.StatusCode, + response.StatusCode.ToString(), sw.ElapsedMilliseconds, response.OpenResponse()); + + ContentTypeModel ContentType = response.OpenTResponse(); + + TestReportHelper.LogAssertion(response != null, "Response not null", type: "IsNotNull"); + Assert.IsNotNull(response); + Assert.IsNotNull(ContentType); + Assert.IsNotNull(ContentType.Modelling); + Assert.AreEqual(_multiPage.Title, ContentType.Modelling.Title); + Assert.AreEqual(_multiPage.Uid, ContentType.Modelling.Uid); + Assert.AreEqual(_multiPage.Schema.Count, ContentType.Modelling.Schema.Count); + } + catch (Exception e) + { + sw.Stop(); + TestReportHelper.LogAssertion(false, $"Exception: {e.GetType().Name} — {e.Message}", type: "Fail"); + throw; + } + finally + { + TestReportHelper.Flush(); + } } [TestMethod] [DoNotParallelize] public async System.Threading.Tasks.Task Test006_Should_Update_Async_Content_Type() { + TestReportHelper.Begin(); + var sw = Stopwatch.StartNew(); try { - // Load the existing schema _multiPage.Schema = Contentstack.serializeArray>(Contentstack.Client.serializer, "contentTypeSchema.json"); - - // Add a new text field to the schema var newTextField = new Models.Fields.TextboxField { Uid = "new_text_field", @@ -114,13 +240,20 @@ public async System.Threading.Tasks.Task Test006_Should_Update_Async_Content_Typ } }; _multiPage.Schema.Add(newTextField); - - // Update the content type with the modified schema + + TestReportHelper.LogRequest("_stack.ContentType(uid).UpdateAsync()", "PUT", + $"https://{_host}/v3/stacks/content_types/{_multiPage.Uid}"); + ContentstackResponse response = await _stack.ContentType(_multiPage.Uid).UpdateAsync(_multiPage); - + sw.Stop(); + TestReportHelper.LogResponse((int)response.StatusCode, + response.StatusCode.ToString(), sw.ElapsedMilliseconds, response.OpenResponse()); + if (response.IsSuccessStatusCode) { ContentTypeModel ContentType = response.OpenTResponse(); + TestReportHelper.LogAssertion(ContentType?.Modelling?.Uid == _multiPage.Uid, + "UID matches", expected: _multiPage.Uid, actual: ContentType?.Modelling?.Uid, type: "AreEqual"); Assert.IsNotNull(response); Assert.IsNotNull(ContentType); Assert.IsNotNull(ContentType.Modelling); @@ -135,32 +268,88 @@ public async System.Threading.Tasks.Task Test006_Should_Update_Async_Content_Typ } catch (Exception ex) { + sw.Stop(); + TestReportHelper.LogAssertion(false, $"Exception during async update: {ex.Message}", type: "Fail"); Assert.Fail($"Exception during async update: {ex.Message}"); } + finally + { + TestReportHelper.Flush(); + } } [TestMethod] [DoNotParallelize] public void Test007_Should_Query_Content_Type() { - ContentstackResponse response = _stack.ContentType().Query().Find(); - ContentTypesModel ContentType = response.OpenTResponse(); - Assert.IsNotNull(response); - Assert.IsNotNull(ContentType); - Assert.IsNotNull(ContentType.Modellings); - Assert.AreEqual(2, ContentType.Modellings.Count); + TestReportHelper.Begin(); + var sw = Stopwatch.StartNew(); + try + { + TestReportHelper.LogRequest("_stack.ContentType().Query().Find()", "GET", + $"https://{_host}/v3/stacks/content_types"); + + ContentstackResponse response = _stack.ContentType().Query().Find(); + sw.Stop(); + TestReportHelper.LogResponse((int)response.StatusCode, + response.StatusCode.ToString(), sw.ElapsedMilliseconds, response.OpenResponse()); + + ContentTypesModel ContentType = response.OpenTResponse(); + + TestReportHelper.LogAssertion(ContentType?.Modellings?.Count == 2, + "Content types count is 2", expected: "2", actual: ContentType?.Modellings?.Count.ToString(), type: "AreEqual"); + Assert.IsNotNull(response); + Assert.IsNotNull(ContentType); + Assert.IsNotNull(ContentType.Modellings); + Assert.AreEqual(2, ContentType.Modellings.Count); + } + catch (Exception e) + { + sw.Stop(); + TestReportHelper.LogAssertion(false, $"Exception: {e.GetType().Name} — {e.Message}", type: "Fail"); + throw; + } + finally + { + TestReportHelper.Flush(); + } } [TestMethod] [DoNotParallelize] public async System.Threading.Tasks.Task Test008_Should_Query_Async_Content_Type() { - ContentstackResponse response = await _stack.ContentType().Query().FindAsync(); - ContentTypesModel ContentType = response.OpenTResponse(); - Assert.IsNotNull(response); - Assert.IsNotNull(ContentType); - Assert.IsNotNull(ContentType.Modellings); - Assert.AreEqual(2, ContentType.Modellings.Count); + TestReportHelper.Begin(); + var sw = Stopwatch.StartNew(); + try + { + TestReportHelper.LogRequest("_stack.ContentType().Query().FindAsync()", "GET", + $"https://{_host}/v3/stacks/content_types"); + + ContentstackResponse response = await _stack.ContentType().Query().FindAsync(); + sw.Stop(); + TestReportHelper.LogResponse((int)response.StatusCode, + response.StatusCode.ToString(), sw.ElapsedMilliseconds, response.OpenResponse()); + + ContentTypesModel ContentType = response.OpenTResponse(); + + TestReportHelper.LogAssertion(ContentType?.Modellings?.Count == 2, + "Content types count is 2", expected: "2", actual: ContentType?.Modellings?.Count.ToString(), type: "AreEqual"); + Assert.IsNotNull(response); + Assert.IsNotNull(ContentType); + Assert.IsNotNull(ContentType.Modellings); + Assert.AreEqual(2, ContentType.Modellings.Count); + } + catch (Exception e) + { + sw.Stop(); + TestReportHelper.LogAssertion(false, $"Exception: {e.GetType().Name} — {e.Message}", type: "Fail"); + throw; + } + finally + { + TestReportHelper.Flush(); + } } } } diff --git a/Contentstack.Management.Core.Tests/IntegrationTest/Contentstack012_NestedGlobalFieldTest.cs b/Contentstack.Management.Core.Tests/IntegrationTest/Contentstack012_NestedGlobalFieldTest.cs index 545789a..4773aa1 100644 --- a/Contentstack.Management.Core.Tests/IntegrationTest/Contentstack012_NestedGlobalFieldTest.cs +++ b/Contentstack.Management.Core.Tests/IntegrationTest/Contentstack012_NestedGlobalFieldTest.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using System.Threading.Tasks; @@ -17,6 +18,8 @@ public class Contentstack008_NestedGlobalFieldTest { private Stack _stack; + private static string _host => Contentstack.Client.contentstackOptions.Host; + [TestInitialize] public void Initialize() { @@ -40,10 +43,7 @@ private ContentModelling CreateReferencedGlobalFieldModel() DataType = "text", Mandatory = true, Unique = true, - FieldMetadata = new FieldMetadata - { - Default = "true" - } + FieldMetadata = new FieldMetadata { Default = "true" } }, new TextboxField { @@ -51,10 +51,7 @@ private ContentModelling CreateReferencedGlobalFieldModel() Uid = "description", DataType = "text", Mandatory = false, - FieldMetadata = new FieldMetadata - { - Description = "A description field" - } + FieldMetadata = new FieldMetadata { Description = "A description field" } } } }; @@ -77,12 +74,7 @@ private ContentModelling CreateNestedGlobalFieldModel() Mandatory = false, Multiple = false, Unique = false, - FieldMetadata = new FieldMetadata - { - Description = "", - DefaultValue = "", - Version = 3 - } + FieldMetadata = new FieldMetadata { Description = "", DefaultValue = "", Version = 3 } }, new GlobalFieldReference { @@ -94,10 +86,7 @@ private ContentModelling CreateNestedGlobalFieldModel() Multiple = false, Unique = false, NonLocalizable = false, - FieldMetadata = new FieldMetadata - { - Description = "Reference to another global field" - } + FieldMetadata = new FieldMetadata { Description = "Reference to another global field" } } }, GlobalFieldRefs = new List @@ -117,150 +106,355 @@ private ContentModelling CreateNestedGlobalFieldModel() [DoNotParallelize] public void Test001_Should_Create_Referenced_Global_Field() { - var referencedGlobalFieldModel = CreateReferencedGlobalFieldModel(); - ContentstackResponse response = _stack.GlobalField().Create(referencedGlobalFieldModel); - GlobalFieldModel globalField = response.OpenTResponse(); - - Assert.IsNotNull(response); - Assert.IsNotNull(globalField); - Assert.IsNotNull(globalField.Modelling); - Assert.AreEqual(referencedGlobalFieldModel.Title, globalField.Modelling.Title); - Assert.AreEqual(referencedGlobalFieldModel.Uid, globalField.Modelling.Uid); - Assert.AreEqual(referencedGlobalFieldModel.Schema.Count, globalField.Modelling.Schema.Count); + TestReportHelper.Begin(); + var sw = Stopwatch.StartNew(); + try + { + var referencedGlobalFieldModel = CreateReferencedGlobalFieldModel(); + TestReportHelper.LogRequest("_stack.GlobalField().Create(referenced)", "POST", + $"https://{_host}/v3/stacks/global_fields"); + + ContentstackResponse response = _stack.GlobalField().Create(referencedGlobalFieldModel); + sw.Stop(); + TestReportHelper.LogResponse((int)response.StatusCode, + response.StatusCode.ToString(), sw.ElapsedMilliseconds, response.OpenResponse()); + + GlobalFieldModel globalField = response.OpenTResponse(); + + TestReportHelper.LogAssertion(globalField?.Modelling?.Title == referencedGlobalFieldModel.Title, + "Title matches", expected: referencedGlobalFieldModel.Title, actual: globalField?.Modelling?.Title, type: "AreEqual"); + Assert.IsNotNull(response); + Assert.IsNotNull(globalField); + Assert.IsNotNull(globalField.Modelling); + Assert.AreEqual(referencedGlobalFieldModel.Title, globalField.Modelling.Title); + Assert.AreEqual(referencedGlobalFieldModel.Uid, globalField.Modelling.Uid); + Assert.AreEqual(referencedGlobalFieldModel.Schema.Count, globalField.Modelling.Schema.Count); + } + catch (Exception e) + { + sw.Stop(); + TestReportHelper.LogAssertion(false, $"Exception: {e.GetType().Name} — {e.Message}", type: "Fail"); + throw; + } + finally + { + TestReportHelper.Flush(); + } } [TestMethod] [DoNotParallelize] public void Test002_Should_Create_Nested_Global_Field() { - var nestedGlobalFieldModel = CreateNestedGlobalFieldModel(); - ContentstackResponse response = _stack.GlobalField().Create(nestedGlobalFieldModel); - GlobalFieldModel globalField = response.OpenTResponse(); - - Assert.IsNotNull(response); - Assert.IsNotNull(globalField); - Assert.IsNotNull(globalField.Modelling); - Assert.AreEqual(nestedGlobalFieldModel.Title, globalField.Modelling.Title); - Assert.AreEqual(nestedGlobalFieldModel.Uid, globalField.Modelling.Uid); - Assert.AreEqual(nestedGlobalFieldModel.Schema.Count, globalField.Modelling.Schema.Count); - + TestReportHelper.Begin(); + var sw = Stopwatch.StartNew(); + try + { + var nestedGlobalFieldModel = CreateNestedGlobalFieldModel(); + TestReportHelper.LogRequest("_stack.GlobalField().Create(nested)", "POST", + $"https://{_host}/v3/stacks/global_fields"); + + ContentstackResponse response = _stack.GlobalField().Create(nestedGlobalFieldModel); + sw.Stop(); + TestReportHelper.LogResponse((int)response.StatusCode, + response.StatusCode.ToString(), sw.ElapsedMilliseconds, response.OpenResponse()); + + GlobalFieldModel globalField = response.OpenTResponse(); + + TestReportHelper.LogAssertion(globalField?.Modelling?.Title == nestedGlobalFieldModel.Title, + "Title matches", expected: nestedGlobalFieldModel.Title, actual: globalField?.Modelling?.Title, type: "AreEqual"); + Assert.IsNotNull(response); + Assert.IsNotNull(globalField); + Assert.IsNotNull(globalField.Modelling); + Assert.AreEqual(nestedGlobalFieldModel.Title, globalField.Modelling.Title); + Assert.AreEqual(nestedGlobalFieldModel.Uid, globalField.Modelling.Uid); + Assert.AreEqual(nestedGlobalFieldModel.Schema.Count, globalField.Modelling.Schema.Count); + } + catch (Exception e) + { + sw.Stop(); + TestReportHelper.LogAssertion(false, $"Exception: {e.GetType().Name} — {e.Message}", type: "Fail"); + throw; + } + finally + { + TestReportHelper.Flush(); + } } [TestMethod] [DoNotParallelize] public void Test003_Should_Fetch_Nested_Global_Field() { + TestReportHelper.Begin(); + var sw = Stopwatch.StartNew(); + try + { + TestReportHelper.LogRequest("_stack.GlobalField(nested_uid).Fetch()", "GET", + $"https://{_host}/v3/stacks/global_fields/nested_global_field_test"); - ContentstackResponse response = _stack.GlobalField("nested_global_field_test").Fetch(); - GlobalFieldModel globalField = response.OpenTResponse(); + ContentstackResponse response = _stack.GlobalField("nested_global_field_test").Fetch(); + sw.Stop(); + TestReportHelper.LogResponse((int)response.StatusCode, + response.StatusCode.ToString(), sw.ElapsedMilliseconds, response.OpenResponse()); - Assert.IsNotNull(response); - Assert.IsNotNull(globalField); - Assert.IsNotNull(globalField.Modelling); - Assert.AreEqual("nested_global_field_test", globalField.Modelling.Uid); + GlobalFieldModel globalField = response.OpenTResponse(); - Assert.IsTrue(globalField.Modelling.Schema.Count >= 2); + TestReportHelper.LogAssertion(globalField?.Modelling?.Uid == "nested_global_field_test", + "UID matches", expected: "nested_global_field_test", actual: globalField?.Modelling?.Uid, type: "AreEqual"); + TestReportHelper.LogAssertion(globalField?.Modelling?.Schema?.Count >= 2, + "Schema has at least 2 fields", expected: ">=2", actual: globalField?.Modelling?.Schema?.Count.ToString(), type: "IsTrue"); + Assert.IsNotNull(response); + Assert.IsNotNull(globalField); + Assert.IsNotNull(globalField.Modelling); + Assert.AreEqual("nested_global_field_test", globalField.Modelling.Uid); + Assert.IsTrue(globalField.Modelling.Schema.Count >= 2); + } + catch (Exception e) + { + sw.Stop(); + TestReportHelper.LogAssertion(false, $"Exception: {e.GetType().Name} — {e.Message}", type: "Fail"); + throw; + } + finally + { + TestReportHelper.Flush(); + } } [TestMethod] [DoNotParallelize] public async Task Test004_Should_Fetch_Async_Nested_Global_Field() { + TestReportHelper.Begin(); + var sw = Stopwatch.StartNew(); + try + { + TestReportHelper.LogRequest("_stack.GlobalField(nested_uid).FetchAsync()", "GET", + $"https://{_host}/v3/stacks/global_fields/nested_global_field_test"); - ContentstackResponse response = await _stack.GlobalField("nested_global_field_test").FetchAsync(); - GlobalFieldModel globalField = response.OpenTResponse(); + ContentstackResponse response = await _stack.GlobalField("nested_global_field_test").FetchAsync(); + sw.Stop(); + TestReportHelper.LogResponse((int)response.StatusCode, + response.StatusCode.ToString(), sw.ElapsedMilliseconds, response.OpenResponse()); - Assert.IsNotNull(response); - Assert.IsNotNull(globalField); - Assert.IsNotNull(globalField.Modelling); - Assert.AreEqual("nested_global_field_test", globalField.Modelling.Uid); + GlobalFieldModel globalField = response.OpenTResponse(); + + TestReportHelper.LogAssertion(globalField?.Modelling?.Uid == "nested_global_field_test", + "UID matches", expected: "nested_global_field_test", actual: globalField?.Modelling?.Uid, type: "AreEqual"); + Assert.IsNotNull(response); + Assert.IsNotNull(globalField); + Assert.IsNotNull(globalField.Modelling); + Assert.AreEqual("nested_global_field_test", globalField.Modelling.Uid); + } + catch (Exception e) + { + sw.Stop(); + TestReportHelper.LogAssertion(false, $"Exception: {e.GetType().Name} — {e.Message}", type: "Fail"); + throw; + } + finally + { + TestReportHelper.Flush(); + } } [TestMethod] [DoNotParallelize] public void Test005_Should_Update_Nested_Global_Field() { - var updateModel = new ContentModelling + TestReportHelper.Begin(); + var sw = Stopwatch.StartNew(); + try { - Title = "Updated Nested Global Field", - Uid = "nested_global_field_test", - Description = "Updated description for nested global field", - Schema = CreateNestedGlobalFieldModel().Schema, - GlobalFieldRefs = CreateNestedGlobalFieldModel().GlobalFieldRefs - }; + var updateModel = new ContentModelling + { + Title = "Updated Nested Global Field", + Uid = "nested_global_field_test", + Description = "Updated description for nested global field", + Schema = CreateNestedGlobalFieldModel().Schema, + GlobalFieldRefs = CreateNestedGlobalFieldModel().GlobalFieldRefs + }; + TestReportHelper.LogRequest("_stack.GlobalField(nested_uid).Update()", "PUT", + $"https://{_host}/v3/stacks/global_fields/nested_global_field_test"); - ContentstackResponse response = _stack.GlobalField("nested_global_field_test").Update(updateModel); - GlobalFieldModel globalField = response.OpenTResponse(); + ContentstackResponse response = _stack.GlobalField("nested_global_field_test").Update(updateModel); + sw.Stop(); + TestReportHelper.LogResponse((int)response.StatusCode, + response.StatusCode.ToString(), sw.ElapsedMilliseconds, response.OpenResponse()); - Assert.IsNotNull(response); - Assert.IsNotNull(globalField); - Assert.IsNotNull(globalField.Modelling); - Assert.AreEqual(updateModel.Title, globalField.Modelling.Title); - Assert.AreEqual("nested_global_field_test", globalField.Modelling.Uid); + GlobalFieldModel globalField = response.OpenTResponse(); + + TestReportHelper.LogAssertion(globalField?.Modelling?.Title == updateModel.Title, + "Title matches", expected: updateModel.Title, actual: globalField?.Modelling?.Title, type: "AreEqual"); + Assert.IsNotNull(response); + Assert.IsNotNull(globalField); + Assert.IsNotNull(globalField.Modelling); + Assert.AreEqual(updateModel.Title, globalField.Modelling.Title); + Assert.AreEqual("nested_global_field_test", globalField.Modelling.Uid); + } + catch (Exception e) + { + sw.Stop(); + TestReportHelper.LogAssertion(false, $"Exception: {e.GetType().Name} — {e.Message}", type: "Fail"); + throw; + } + finally + { + TestReportHelper.Flush(); + } } [TestMethod] [DoNotParallelize] public async Task Test006_Should_Update_Async_Nested_Global_Field() { - var updateModel = new ContentModelling + TestReportHelper.Begin(); + var sw = Stopwatch.StartNew(); + try { - Title = "Updated Async Nested Global Field", - Uid = "nested_global_field_test", - Description = "Updated async description for nested global field", - Schema = CreateNestedGlobalFieldModel().Schema, - GlobalFieldRefs = CreateNestedGlobalFieldModel().GlobalFieldRefs - }; + var updateModel = new ContentModelling + { + Title = "Updated Async Nested Global Field", + Uid = "nested_global_field_test", + Description = "Updated async description for nested global field", + Schema = CreateNestedGlobalFieldModel().Schema, + GlobalFieldRefs = CreateNestedGlobalFieldModel().GlobalFieldRefs + }; + TestReportHelper.LogRequest("_stack.GlobalField(nested_uid).UpdateAsync()", "PUT", + $"https://{_host}/v3/stacks/global_fields/nested_global_field_test"); + + ContentstackResponse response = await _stack.GlobalField("nested_global_field_test").UpdateAsync(updateModel); + sw.Stop(); + TestReportHelper.LogResponse((int)response.StatusCode, + response.StatusCode.ToString(), sw.ElapsedMilliseconds, response.OpenResponse()); - ContentstackResponse response = await _stack.GlobalField("nested_global_field_test").UpdateAsync(updateModel); - GlobalFieldModel globalField = response.OpenTResponse(); + GlobalFieldModel globalField = response.OpenTResponse(); - Assert.IsNotNull(response); - Assert.IsNotNull(globalField); - Assert.IsNotNull(globalField.Modelling); - Assert.AreEqual(updateModel.Title, globalField.Modelling.Title); - Assert.AreEqual("nested_global_field_test", globalField.Modelling.Uid); + TestReportHelper.LogAssertion(globalField?.Modelling?.Title == updateModel.Title, + "Title matches", expected: updateModel.Title, actual: globalField?.Modelling?.Title, type: "AreEqual"); + Assert.IsNotNull(response); + Assert.IsNotNull(globalField); + Assert.IsNotNull(globalField.Modelling); + Assert.AreEqual(updateModel.Title, globalField.Modelling.Title); + Assert.AreEqual("nested_global_field_test", globalField.Modelling.Uid); + } + catch (Exception e) + { + sw.Stop(); + TestReportHelper.LogAssertion(false, $"Exception: {e.GetType().Name} — {e.Message}", type: "Fail"); + throw; + } + finally + { + TestReportHelper.Flush(); + } } [TestMethod] [DoNotParallelize] public void Test007_Should_Query_Nested_Global_Fields() { + TestReportHelper.Begin(); + var sw = Stopwatch.StartNew(); + try + { + TestReportHelper.LogRequest("_stack.GlobalField().Query().Find()", "GET", + $"https://{_host}/v3/stacks/global_fields"); - ContentstackResponse response = _stack.GlobalField().Query().Find(); - GlobalFieldsModel globalFields = response.OpenTResponse(); - - Assert.IsNotNull(response); - Assert.IsNotNull(globalFields); - Assert.IsNotNull(globalFields.Modellings); - Assert.IsTrue(globalFields.Modellings.Count >= 1); + ContentstackResponse response = _stack.GlobalField().Query().Find(); + sw.Stop(); + TestReportHelper.LogResponse((int)response.StatusCode, + response.StatusCode.ToString(), sw.ElapsedMilliseconds, response.OpenResponse()); - var nestedGlobalField = globalFields.Modellings.Find(gf => gf.Uid == "nested_global_field_test"); - Assert.IsNotNull(nestedGlobalField); - Assert.AreEqual("nested_global_field_test", nestedGlobalField.Uid); - } + GlobalFieldsModel globalFields = response.OpenTResponse(); + TestReportHelper.LogAssertion(globalFields?.Modellings?.Count >= 1, + "Modellings count >= 1", expected: ">=1", actual: globalFields?.Modellings?.Count.ToString(), type: "IsTrue"); + Assert.IsNotNull(response); + Assert.IsNotNull(globalFields); + Assert.IsNotNull(globalFields.Modellings); + Assert.IsTrue(globalFields.Modellings.Count >= 1); + var nestedGlobalField = globalFields.Modellings.Find(gf => gf.Uid == "nested_global_field_test"); + Assert.IsNotNull(nestedGlobalField); + Assert.AreEqual("nested_global_field_test", nestedGlobalField.Uid); + } + catch (Exception e) + { + sw.Stop(); + TestReportHelper.LogAssertion(false, $"Exception: {e.GetType().Name} — {e.Message}", type: "Fail"); + throw; + } + finally + { + TestReportHelper.Flush(); + } + } [TestMethod] [DoNotParallelize] - public void Test009_Should_Delete_Referenced_Global_Field() + public void Test008_Should_Delete_Nested_Global_Field() { - // This has been used to avoid tthe confirmation prompt during deletion in case the global field is referenced - var parameters = new ParameterCollection(); - parameters.Add("force", "true"); - ContentstackResponse response = _stack.GlobalField("referenced_global_field").Delete(parameters); + TestReportHelper.Begin(); + var sw = Stopwatch.StartNew(); + try + { + TestReportHelper.LogRequest("_stack.GlobalField(nested_uid).Delete()", "DELETE", + $"https://{_host}/v3/stacks/global_fields/nested_global_field_test"); - Assert.IsNotNull(response); + ContentstackResponse response = _stack.GlobalField("nested_global_field_test").Delete(); + sw.Stop(); + TestReportHelper.LogResponse((int)response.StatusCode, + response.StatusCode.ToString(), sw.ElapsedMilliseconds, response.OpenResponse()); + + TestReportHelper.LogAssertion(response != null, "Response not null", type: "IsNotNull"); + Assert.IsNotNull(response); + } + catch (Exception e) + { + sw.Stop(); + TestReportHelper.LogAssertion(false, $"Exception: {e.GetType().Name} — {e.Message}", type: "Fail"); + throw; + } + finally + { + TestReportHelper.Flush(); + } } [TestMethod] [DoNotParallelize] - public void Test008_Should_Delete_Nested_Global_Field() + public void Test009_Should_Delete_Referenced_Global_Field() { - ContentstackResponse response = _stack.GlobalField("nested_global_field_test").Delete(); - Assert.IsNotNull(response); - } + TestReportHelper.Begin(); + var sw = Stopwatch.StartNew(); + try + { + var parameters = new ParameterCollection(); + parameters.Add("force", "true"); + TestReportHelper.LogRequest("_stack.GlobalField(ref_uid).Delete(force)", "DELETE", + $"https://{_host}/v3/stacks/global_fields/referenced_global_field", + queryParams: new Dictionary { ["force"] = "true" }); + + ContentstackResponse response = _stack.GlobalField("referenced_global_field").Delete(parameters); + sw.Stop(); + TestReportHelper.LogResponse((int)response.StatusCode, + response.StatusCode.ToString(), sw.ElapsedMilliseconds, response.OpenResponse()); + TestReportHelper.LogAssertion(response != null, "Response not null", type: "IsNotNull"); + Assert.IsNotNull(response); + } + catch (Exception e) + { + sw.Stop(); + TestReportHelper.LogAssertion(false, $"Exception: {e.GetType().Name} — {e.Message}", type: "Fail"); + throw; + } + finally + { + TestReportHelper.Flush(); + } + } } -} \ No newline at end of file +} diff --git a/Contentstack.Management.Core.Tests/IntegrationTest/Contentstack013_AssetTest.cs b/Contentstack.Management.Core.Tests/IntegrationTest/Contentstack013_AssetTest.cs index 0f699fe..2ee30f5 100644 --- a/Contentstack.Management.Core.Tests/IntegrationTest/Contentstack013_AssetTest.cs +++ b/Contentstack.Management.Core.Tests/IntegrationTest/Contentstack013_AssetTest.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -22,31 +22,44 @@ public class Contentstack006_AssetTest [TestInitialize] public void Initialize() { + TestReportHelper.Begin(); StackResponse response = StackResponse.getStack(Contentstack.Client.serializer); _stack = Contentstack.Client.Stack(response.Stack.APIKey); } + [TestCleanup] + public void Cleanup() + { + TestReportHelper.Flush(); + } + [TestMethod] [DoNotParallelize] public void Test001_Should_Create_Asset() { var path = Path.Combine(System.Environment.CurrentDirectory, "../../../Mock/contentTypeSchema.json"); + var sw = System.Diagnostics.Stopwatch.StartNew(); try { AssetModel asset = new AssetModel("contentTypeSchema.json", path, "application/json", title:"New.json", description:"new test desc", parentUID: null, tags:"one,two"); + TestReportHelper.LogRequest("_stack.Asset().Create(asset)", "POST", + $"https://{Contentstack.Client.contentstackOptions.Host}/v3/stacks/assets"); ContentstackResponse response = _stack.Asset().Create(asset); - + sw.Stop(); + TestReportHelper.LogResponse((int)response.StatusCode, + response.StatusCode.ToString(), sw.ElapsedMilliseconds, response.OpenResponse()); + if (response.IsSuccessStatusCode) { + TestReportHelper.LogAssertion(response.StatusCode == System.Net.HttpStatusCode.Created, + "Status code is Created", expected: "Created", actual: response.StatusCode.ToString(), type: "AreEqual"); Assert.AreEqual(System.Net.HttpStatusCode.Created, response.StatusCode); } - else - { - // Don't fail the test if API returns an error - this might be expected behavior - } } catch (Exception e) { + sw.Stop(); + TestReportHelper.LogAssertion(false, $"Exception: {e.GetType().Name} — {e.Message}", type: "Fail"); Assert.Fail("Asset Creation Failed ", e.Message); } } @@ -61,7 +74,8 @@ public void Test002_Should_Create_Dashboard() { DashboardWidgetModel dashboard = new DashboardWidgetModel(path, "text/html", "Dashboard", isEnable: true, defaultWidth: "half", tags: "one,two"); ContentstackResponse response = _stack.Extension().Upload(dashboard); - + TestReportHelper.LogRequest("Extension.Upload (Dashboard)", "POST", + $"https://{Contentstack.Client.contentstackOptions.Host}/v3/extensions"); if (response.IsSuccessStatusCode) { Assert.AreEqual(System.Net.HttpStatusCode.Created, response.StatusCode); @@ -88,7 +102,8 @@ public void Test003_Should_Create_Custom_Widget() } }, tags: "one,two"); ContentstackResponse response = _stack.Extension().Upload(customWidget); - + TestReportHelper.LogRequest("Extension.Upload (CustomWidget)", "POST", + $"https://{Contentstack.Client.contentstackOptions.Host}/v3/extensions"); if (response.IsSuccessStatusCode) { Assert.AreEqual(System.Net.HttpStatusCode.Created, response.StatusCode); @@ -109,7 +124,8 @@ public void Test004_Should_Create_Custom_field() { CustomFieldModel fieldModel = new CustomFieldModel(path, "text/html", "Custom field Upload", "text", isMultiple: false, tags: "one,two"); ContentstackResponse response = _stack.Extension().Upload(fieldModel); - + TestReportHelper.LogRequest("Extension.Upload (CustomField)", "POST", + $"https://{Contentstack.Client.contentstackOptions.Host}/v3/extensions"); if (response.IsSuccessStatusCode) { Assert.AreEqual(System.Net.HttpStatusCode.Created, response.StatusCode); @@ -132,7 +148,8 @@ public void Test005_Should_Create_Asset_Async() { AssetModel asset = new AssetModel("async_asset.json", path, "application/json", title:"Async Asset", description:"async test asset", parentUID: null, tags:"async,test"); ContentstackResponse response = _stack.Asset().CreateAsync(asset).Result; - + TestReportHelper.LogRequest("Asset.CreateAsync", "POST", + $"https://{Contentstack.Client.contentstackOptions.Host}/v3/stacks/assets"); if (response.IsSuccessStatusCode) { Assert.AreEqual(System.Net.HttpStatusCode.Created, response.StatusCode); @@ -167,7 +184,8 @@ public void Test006_Should_Fetch_Asset() if (!string.IsNullOrEmpty(_testAssetUid)) { ContentstackResponse response = _stack.Asset(_testAssetUid).Fetch(); - + TestReportHelper.LogRequest("Asset.Fetch", "GET", + $"https://{Contentstack.Client.contentstackOptions.Host}/v3/stacks/assets/{_testAssetUid}"); if (response.IsSuccessStatusCode) { Assert.AreEqual(System.Net.HttpStatusCode.OK, response.StatusCode); @@ -200,7 +218,8 @@ public void Test007_Should_Fetch_Asset_Async() if (!string.IsNullOrEmpty(_testAssetUid)) { ContentstackResponse response = _stack.Asset(_testAssetUid).FetchAsync().Result; - + TestReportHelper.LogRequest("Asset.FetchAsync", "GET", + $"https://{Contentstack.Client.contentstackOptions.Host}/v3/stacks/assets/{_testAssetUid}"); if (response.IsSuccessStatusCode) { Assert.AreEqual(System.Net.HttpStatusCode.OK, response.StatusCode); @@ -236,7 +255,8 @@ public void Test008_Should_Update_Asset() AssetModel updatedAsset = new AssetModel("updated_asset.json", path, "application/json", title:"Updated Asset", description:"updated test asset", parentUID: null, tags:"updated,test"); ContentstackResponse response = _stack.Asset(_testAssetUid).Update(updatedAsset); - + TestReportHelper.LogRequest("Asset.Update", "PUT", + $"https://{Contentstack.Client.contentstackOptions.Host}/v3/stacks/assets/{_testAssetUid}"); if (response.IsSuccessStatusCode) { Assert.AreEqual(System.Net.HttpStatusCode.OK, response.StatusCode); @@ -272,7 +292,8 @@ public void Test009_Should_Update_Asset_Async() AssetModel updatedAsset = new AssetModel("async_updated_asset.json", path, "application/json", title:"Async Updated Asset", description:"async updated test asset", parentUID: null, tags:"async,updated,test"); ContentstackResponse response = _stack.Asset(_testAssetUid).UpdateAsync(updatedAsset).Result; - + TestReportHelper.LogRequest("Asset.UpdateAsync", "PUT", + $"https://{Contentstack.Client.contentstackOptions.Host}/v3/stacks/assets/{_testAssetUid}"); if (response.IsSuccessStatusCode) { Assert.AreEqual(System.Net.HttpStatusCode.OK, response.StatusCode); @@ -295,14 +316,21 @@ public void Test009_Should_Update_Asset_Async() [DoNotParallelize] public void Test010_Should_Query_Assets() { + var sw = System.Diagnostics.Stopwatch.StartNew(); try { + TestReportHelper.LogRequest("_stack.Asset().Query().Find()", "GET", + $"https://{Contentstack.Client.contentstackOptions.Host}/v3/stacks/assets"); ContentstackResponse response = _stack.Asset().Query().Find(); - + sw.Stop(); + TestReportHelper.LogResponse((int)response.StatusCode, + response.StatusCode.ToString(), sw.ElapsedMilliseconds, response.OpenResponse()); + if (response.IsSuccessStatusCode) { - Assert.AreEqual(System.Net.HttpStatusCode.OK, response.StatusCode); var responseObject = response.OpenJObjectResponse(); + TestReportHelper.LogAssertion(responseObject["assets"] != null, "assets key present", type: "IsNotNull"); + Assert.AreEqual(System.Net.HttpStatusCode.OK, response.StatusCode); Assert.IsNotNull(responseObject["assets"], "Response should contain assets array"); } else @@ -312,6 +340,8 @@ public void Test010_Should_Query_Assets() } catch (ContentstackErrorException ex) { + sw.Stop(); + TestReportHelper.LogAssertion(false, $"ContentstackErrorException: {ex.Message}", type: "Fail"); Assert.Fail("Querying the Asset Failed ",ex.Message); } } @@ -327,7 +357,8 @@ public void Test011_Should_Query_Assets_With_Parameters() query.Skip(0); ContentstackResponse response = query.Find(); - + TestReportHelper.LogRequest("Asset.Query.Find (with params)", "GET", + $"https://{Contentstack.Client.contentstackOptions.Host}/v3/stacks/assets"); if (response.IsSuccessStatusCode) { Assert.AreEqual(System.Net.HttpStatusCode.OK, response.StatusCode); @@ -359,7 +390,8 @@ public void Test012_Should_Delete_Asset() if (!string.IsNullOrEmpty(_testAssetUid)) { ContentstackResponse response = _stack.Asset(_testAssetUid).Delete(); - + TestReportHelper.LogRequest("Asset.Delete", "DELETE", + $"https://{Contentstack.Client.contentstackOptions.Host}/v3/stacks/assets/{_testAssetUid}"); if (response.IsSuccessStatusCode) { Assert.AreEqual(System.Net.HttpStatusCode.OK, response.StatusCode); @@ -395,7 +427,8 @@ public void Test013_Should_Delete_Asset_Async() if (!string.IsNullOrEmpty(assetUid)) { ContentstackResponse deleteResponse = _stack.Asset(assetUid).DeleteAsync().Result; - + TestReportHelper.LogRequest("Asset.DeleteAsync", "DELETE", + $"https://{Contentstack.Client.contentstackOptions.Host}/v3/stacks/assets/{assetUid}"); if (deleteResponse.IsSuccessStatusCode) { Assert.AreEqual(System.Net.HttpStatusCode.OK, deleteResponse.StatusCode); @@ -427,7 +460,8 @@ public void Test014_Should_Create_Folder() try { ContentstackResponse response = _stack.Asset().Folder().Create("Test Folder", null); - + TestReportHelper.LogRequest("Asset.Folder.Create", "POST", + $"https://{Contentstack.Client.contentstackOptions.Host}/v3/stacks/assets/folders"); if (response.IsSuccessStatusCode) { Assert.AreEqual(System.Net.HttpStatusCode.Created, response.StatusCode); @@ -462,7 +496,8 @@ public void Test015_Should_Create_Subfolder() if (!string.IsNullOrEmpty(_testFolderUid)) { ContentstackResponse response = _stack.Asset().Folder().Create("Test Subfolder", _testFolderUid); - + TestReportHelper.LogRequest("Asset.Folder.Create (subfolder)", "POST", + $"https://{Contentstack.Client.contentstackOptions.Host}/v3/stacks/assets/folders"); if (response.IsSuccessStatusCode) { Assert.AreEqual(System.Net.HttpStatusCode.Created, response.StatusCode); @@ -495,7 +530,8 @@ public void Test016_Should_Fetch_Folder() if (!string.IsNullOrEmpty(_testFolderUid)) { ContentstackResponse response = _stack.Asset().Folder(_testFolderUid).Fetch(); - + TestReportHelper.LogRequest("Asset.Folder.Fetch", "GET", + $"https://{Contentstack.Client.contentstackOptions.Host}/v3/stacks/assets/folders/{_testFolderUid}"); if (response.IsSuccessStatusCode) { Assert.AreEqual(System.Net.HttpStatusCode.OK, response.StatusCode); @@ -528,7 +564,8 @@ public void Test017_Should_Fetch_Folder_Async() if (!string.IsNullOrEmpty(_testFolderUid)) { ContentstackResponse response = _stack.Asset().Folder(_testFolderUid).FetchAsync().Result; - + TestReportHelper.LogRequest("Asset.Folder.FetchAsync", "GET", + $"https://{Contentstack.Client.contentstackOptions.Host}/v3/stacks/assets/folders/{_testFolderUid}"); if (response.IsSuccessStatusCode) { Assert.AreEqual(System.Net.HttpStatusCode.OK, response.StatusCode); @@ -561,7 +598,8 @@ public void Test018_Should_Update_Folder() if (!string.IsNullOrEmpty(_testFolderUid)) { ContentstackResponse response = _stack.Asset().Folder(_testFolderUid).Update("Updated Test Folder", null); - + TestReportHelper.LogRequest("Asset.Folder.Update", "PUT", + $"https://{Contentstack.Client.contentstackOptions.Host}/v3/stacks/assets/folders/{_testFolderUid}"); if (response.IsSuccessStatusCode) { Assert.AreEqual(System.Net.HttpStatusCode.Created, response.StatusCode); @@ -595,7 +633,8 @@ public void Test019_Should_Update_Folder_Async() if (!string.IsNullOrEmpty(_testFolderUid)) { ContentstackResponse response = _stack.Asset().Folder(_testFolderUid).UpdateAsync("Async Updated Test Folder", null).Result; - + TestReportHelper.LogRequest("Asset.Folder.UpdateAsync", "PUT", + $"https://{Contentstack.Client.contentstackOptions.Host}/v3/stacks/assets/folders/{_testFolderUid}"); if (response.IsSuccessStatusCode) { Assert.AreEqual(System.Net.HttpStatusCode.Created, response.StatusCode); @@ -629,7 +668,8 @@ public void Test022_Should_Delete_Folder() if (!string.IsNullOrEmpty(_testFolderUid)) { ContentstackResponse response = _stack.Asset().Folder(_testFolderUid).Delete(); - + TestReportHelper.LogRequest("Asset.Folder.Delete", "DELETE", + $"https://{Contentstack.Client.contentstackOptions.Host}/v3/stacks/assets/folders/{_testFolderUid}"); if (response.IsSuccessStatusCode) { Assert.AreEqual(System.Net.HttpStatusCode.OK, response.StatusCode); @@ -664,7 +704,8 @@ public void Test023_Should_Delete_Folder_Async() if (!string.IsNullOrEmpty(folderUid)) { ContentstackResponse deleteResponse = _stack.Asset().Folder(folderUid).DeleteAsync().Result; - + TestReportHelper.LogRequest("Asset.Folder.DeleteAsync", "DELETE", + $"https://{Contentstack.Client.contentstackOptions.Host}/v3/stacks/assets/folders/{folderUid}"); if (deleteResponse.IsSuccessStatusCode) { Assert.AreEqual(System.Net.HttpStatusCode.OK, deleteResponse.StatusCode); @@ -694,6 +735,8 @@ public void Test024_Should_Handle_Invalid_Asset_Operations() string invalidAssetUid = "invalid_asset_uid_12345"; // Test fetching non-existent asset - expect exception + TestReportHelper.LogRequest("Asset.Fetch (invalid - expected error)", "GET", + $"https://{Contentstack.Client.contentstackOptions.Host}/v3/stacks/assets/{invalidAssetUid}"); try { _stack.Asset(invalidAssetUid).Fetch(); @@ -744,6 +787,8 @@ public void Test026_Should_Handle_Invalid_Folder_Operations() // Test fetching non-existent folder - expect ContentstackErrorException bool fetchExceptionThrown = false; + TestReportHelper.LogRequest("Asset.Folder.Fetch (invalid - expected error)", "GET", + $"https://{Contentstack.Client.contentstackOptions.Host}/v3/stacks/assets/folders/{invalidFolderUid}"); try { _stack.Asset().Folder(invalidFolderUid).Fetch(); @@ -795,6 +840,7 @@ public void Test027_Should_Handle_Asset_Creation_With_Invalid_File() string invalidPath = Path.Combine(System.Environment.CurrentDirectory, "non_existent_file.json"); // Expect FileNotFoundException during AssetModel construction due to file not found + TestReportHelper.LogRequest("Asset.Create (invalid file - expected FileNotFoundException)", "POST", ""); try { new AssetModel("invalid_file.json", invalidPath, "application/json", title:"Invalid File Asset", description:"asset with invalid file", parentUID: null, tags:"invalid,file"); @@ -817,6 +863,8 @@ public void Test029_Should_Handle_Query_With_Invalid_Parameters() assetQuery.Limit(-1); // Invalid limit assetQuery.Skip(-1); // Invalid skip + TestReportHelper.LogRequest("Asset.Query.Find (invalid params - expected error)", "GET", + $"https://{Contentstack.Client.contentstackOptions.Host}/v3/stacks/assets"); try { assetQuery.Find(); @@ -848,7 +896,8 @@ public void Test030_Should_Handle_Empty_Query_Results() assetQuery.Limit(1); ContentstackResponse response = assetQuery.Find(); - + TestReportHelper.LogRequest("Asset.Query.Find (empty results)", "GET", + $"https://{Contentstack.Client.contentstackOptions.Host}/v3/stacks/assets"); if (response.IsSuccessStatusCode) { Assert.AreEqual(System.Net.HttpStatusCode.OK, response.StatusCode); diff --git a/Contentstack.Management.Core.Tests/IntegrationTest/Contentstack014_EntryTest.cs b/Contentstack.Management.Core.Tests/IntegrationTest/Contentstack014_EntryTest.cs index 6f21559..885ff0b 100644 --- a/Contentstack.Management.Core.Tests/IntegrationTest/Contentstack014_EntryTest.cs +++ b/Contentstack.Management.Core.Tests/IntegrationTest/Contentstack014_EntryTest.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using Contentstack.Management.Core.Models; using Contentstack.Management.Core.Models.Fields; @@ -18,10 +18,17 @@ public class Contentstack007_EntryTest [TestInitialize] public void Initialize() { + TestReportHelper.Begin(); StackResponse response = StackResponse.getStack(Contentstack.Client.serializer); _stack = Contentstack.Client.Stack(response.Stack.APIKey); } + [TestCleanup] + public void Cleanup() + { + TestReportHelper.Flush(); + } + [TestMethod] [DoNotParallelize] public async System.Threading.Tasks.Task Test001_Should_Create_Entry() @@ -81,11 +88,18 @@ public async System.Threading.Tasks.Task Test001_Should_Create_Entry() ContentTypeUid = "single_page" }; + var sw = System.Diagnostics.Stopwatch.StartNew(); + TestReportHelper.LogRequest("_stack.ContentType(single_page).Entry().CreateAsync()", "POST", + $"https://{Contentstack.Client.contentstackOptions.Host}/v3/stacks/content_types/single_page/entries"); ContentstackResponse response = await _stack.ContentType("single_page").Entry().CreateAsync(singlePageEntry); - + sw.Stop(); + TestReportHelper.LogResponse((int)response.StatusCode, response.StatusCode.ToString(), + sw.ElapsedMilliseconds, response.OpenResponse()); + if (response.IsSuccessStatusCode) { var responseObject = response.OpenJObjectResponse(); + TestReportHelper.LogAssertion(responseObject["entry"] != null, "entry key present", type: "IsNotNull"); Assert.IsNotNull(responseObject["entry"], "Response should contain entry object"); var entryData = responseObject["entry"] as Newtonsoft.Json.Linq.JObject; @@ -166,7 +180,8 @@ public async System.Threading.Tasks.Task Test002_Should_Create_MultiPage_Entry() }; ContentstackResponse response = await _stack.ContentType("multi_page").Entry().CreateAsync(multiPageEntry); - + TestReportHelper.LogRequest("Entry.CreateAsync (multi_page)", "POST", + $"https://{Contentstack.Client.contentstackOptions.Host}/v3/content_types/multi_page/entries"); if (response.IsSuccessStatusCode) { var responseObject = response.OpenJObjectResponse(); @@ -212,7 +227,8 @@ public async System.Threading.Tasks.Task Test003_Should_Fetch_Entry() Assert.IsNotNull(entryUid, "Created entry should have UID"); ContentstackResponse fetchResponse = await _stack.ContentType("single_page").Entry(entryUid).FetchAsync(); - + TestReportHelper.LogRequest("Entry.FetchAsync", "GET", + $"https://{Contentstack.Client.contentstackOptions.Host}/v3/content_types/single_page/entries/{entryUid}"); if (fetchResponse.IsSuccessStatusCode) { var fetchObject = fetchResponse.OpenJObjectResponse(); @@ -271,7 +287,8 @@ public async System.Threading.Tasks.Task Test004_Should_Update_Entry() }; ContentstackResponse updateResponse = await _stack.ContentType("single_page").Entry(entryUid).UpdateAsync(updatedEntry); - + TestReportHelper.LogRequest("Entry.UpdateAsync", "PUT", + $"https://{Contentstack.Client.contentstackOptions.Host}/v3/content_types/single_page/entries/{entryUid}"); if (updateResponse.IsSuccessStatusCode) { var updateObject = updateResponse.OpenJObjectResponse(); @@ -307,7 +324,8 @@ public async System.Threading.Tasks.Task Test005_Should_Query_Entries() try { ContentstackResponse response = await _stack.ContentType("single_page").Entry().Query().FindAsync(); - + TestReportHelper.LogRequest("Entry.Query.FindAsync", "GET", + $"https://{Contentstack.Client.contentstackOptions.Host}/v3/content_types/single_page/entries"); if (response.IsSuccessStatusCode) { var responseObject = response.OpenJObjectResponse(); @@ -351,7 +369,8 @@ public async System.Threading.Tasks.Task Test006_Should_Delete_Entry() Assert.IsNotNull(entryUid, "Created entry should have UID"); ContentstackResponse deleteResponse = await _stack.ContentType("single_page").Entry(entryUid).DeleteAsync(); - + TestReportHelper.LogRequest("Entry.DeleteAsync", "DELETE", + $"https://{Contentstack.Client.contentstackOptions.Host}/v3/content_types/single_page/entries/{entryUid}"); if (deleteResponse.IsSuccessStatusCode) { Console.WriteLine($"Successfully deleted entry: {entryUid}"); diff --git a/Contentstack.Management.Core.Tests/IntegrationTest/Contentstack015_BulkOperationTest.cs b/Contentstack.Management.Core.Tests/IntegrationTest/Contentstack015_BulkOperationTest.cs index 9cbc4f0..c439f2c 100644 --- a/Contentstack.Management.Core.Tests/IntegrationTest/Contentstack015_BulkOperationTest.cs +++ b/Contentstack.Management.Core.Tests/IntegrationTest/Contentstack015_BulkOperationTest.cs @@ -1,7 +1,9 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Net; using System.Threading.Tasks; +using Contentstack.Management.Core.Exceptions; using Contentstack.Management.Core.Models; using Contentstack.Management.Core.Models.Fields; using Contentstack.Management.Core.Tests.Model; @@ -12,6 +14,11 @@ namespace Contentstack.Management.Core.Tests.IntegrationTest { + /// + /// Bulk operation integration tests. ClassInitialize ensures environment (find or create "bulk_test_env"), then finds or creates workflow "oggy" (2 stages: New stage 1, New stage 2) and publish rule (Stage 2) once. + /// Tests are independent. Four workflow-based tests assign entries to Stage 1/Stage 2 then run bulk unpublish/publish with/without version and params. + /// No cleanup so you can verify workflow, publish rules, and entry allotment in the UI. + /// [TestClass] public class Contentstack015_BulkOperationTest { @@ -21,25 +28,314 @@ public class Contentstack015_BulkOperationTest private string _testReleaseUid = "bulk_test_release"; private List _createdEntries = new List(); + // Workflow and publishing rule for bulk tests (static so one create/delete across all test instances) + private static string _bulkTestWorkflowUid; + private static string _bulkTestWorkflowStageUid; // Stage 2 (Complete) – used by publish rule and backward compat + private static string _bulkTestWorkflowStage1Uid; // Stage 1 (Review) + private static string _bulkTestWorkflowStage2Uid; // Stage 2 (Complete) – selected in publishing rule + private static string _bulkTestPublishRuleUid; + private static string _bulkTestEnvironmentUid; // Environment used for workflow/publish rule (ensured in ClassInitialize or Test000b/000c) + private static string _bulkTestWorkflowSetupError; // Reason workflow setup failed (so workflow tests can show it) + + /// + /// Fails the test with a clear message from ContentstackErrorException or generic exception. + /// + private static void FailWithError(string operation, Exception ex) + { + if (ex is ContentstackErrorException cex) + Assert.Fail($"{operation} failed. HTTP {(int)cex.StatusCode} ({cex.StatusCode}). ErrorCode: {cex.ErrorCode}. Message: {cex.ErrorMessage ?? cex.Message}"); + else + Assert.Fail($"{operation} failed: {ex.Message}"); + } + + /// + /// Asserts that the workflow and both stages were created in ClassInitialize. Call at the start of workflow-based tests so they fail clearly when setup failed. + /// + private static void AssertWorkflowCreated() + { + string reason = string.IsNullOrEmpty(_bulkTestWorkflowSetupError) ? "Check auth and stack permissions for workflow create." : _bulkTestWorkflowSetupError; + Assert.IsFalse(string.IsNullOrEmpty(_bulkTestWorkflowUid), "Workflow was not created in ClassInitialize. " + reason); + Assert.IsFalse(string.IsNullOrEmpty(_bulkTestWorkflowStage1Uid), "Workflow Stage 1 (New stage 1) was not set. " + reason); + Assert.IsFalse(string.IsNullOrEmpty(_bulkTestWorkflowStage2Uid), "Workflow Stage 2 (New stage 2) was not set. " + reason); + } + + /// + /// Returns a Stack instance for the test run (used by ClassInitialize/ClassCleanup). + /// + private static Stack GetStack() + { + StackResponse response = StackResponse.getStack(Contentstack.Client.serializer); + return Contentstack.Client.Stack(response.Stack.APIKey); + } + + [ClassInitialize] + public static void ClassInitialize(TestContext context) + { + try + { + Stack stack = GetStack(); + EnsureBulkTestWorkflowAndPublishingRuleAsync(stack).GetAwaiter().GetResult(); + } + catch (Exception) + { + // Workflow/publish rule setup failed (e.g. auth, plan limits); tests can still run without them + } + } + + [ClassCleanup] + public static void ClassCleanup() + { + // Intentionally no cleanup: workflow, publish rules, and entries are left so you can verify them in the UI. + } + [TestInitialize] public async Task Initialize() { + TestReportHelper.Begin(); StackResponse response = StackResponse.getStack(Contentstack.Client.serializer); _stack = Contentstack.Client.Stack(response.Stack.APIKey); - - // Create a test environment for bulk operations - //await CreateTestEnvironment(); - //await CreateTestRelease(); + } + + [TestCleanup] + public void Cleanup() + { + TestReportHelper.Flush(); + } + + [TestMethod] + [DoNotParallelize] + public async Task Test000a_Should_Create_Workflow_With_Two_Stages() + { + try + { + const string workflowName = "oggy"; + + // Check if a workflow with the same name already exists (e.g. from a previous test run) + try + { + ContentstackResponse listResponse = _stack.Workflow().FindAll(); + if (listResponse.IsSuccessStatusCode) + { + var listJson = listResponse.OpenJObjectResponse(); + var existing = (listJson["workflows"] as JArray) ?? (listJson["workflow"] as JArray); + if (existing != null) + { + foreach (var wf in existing) + { + if (wf["name"]?.ToString() == workflowName && wf["uid"] != null) + { + _bulkTestWorkflowUid = wf["uid"].ToString(); + var existingStages = wf["workflow_stages"] as JArray; + if (existingStages != null && existingStages.Count >= 2) + { + _bulkTestWorkflowStage1Uid = existingStages[0]["uid"]?.ToString(); + _bulkTestWorkflowStage2Uid = existingStages[1]["uid"]?.ToString(); + _bulkTestWorkflowStageUid = _bulkTestWorkflowStage2Uid; + Assert.IsNotNull(_bulkTestWorkflowStage1Uid, "Stage 1 UID null in existing workflow."); + Assert.IsNotNull(_bulkTestWorkflowStage2Uid, "Stage 2 UID null in existing workflow."); + TestReportHelper.LogRequest("Workflow.FindAll (existing)", "GET", + $"https://{Contentstack.Client.contentstackOptions.Host}/v3/stacks/workflows"); + return; // Already exists with stages – nothing more to do + } + } + } + } + } + } + catch { /* If listing fails, proceed to create */ } + + var sysAcl = new Dictionary + { + ["roles"] = new Dictionary { ["uids"] = new List() }, + ["users"] = new Dictionary { ["uids"] = new List { "$all" } }, + ["others"] = new Dictionary() + }; + + var workflowModel = new WorkflowModel + { + Name = workflowName, + Enabled = true, + Branches = new List { "main" }, + ContentTypes = new List { "$all" }, + AdminUsers = new Dictionary { ["users"] = new List() }, + WorkflowStages = new List + { + new WorkflowStage + { + Name = "New stage 1", + Color = "#fe5cfb", + SystemACL = sysAcl, + NextAvailableStages = new List { "$all" }, + AllStages = true, + AllUsers = true, + SpecificStages = false, + SpecificUsers = false, + EntryLock = "$none" + }, + new WorkflowStage + { + Name = "New stage 2", + Color = "#3688bf", + SystemACL = new Dictionary + { + ["roles"] = new Dictionary { ["uids"] = new List() }, + ["users"] = new Dictionary { ["uids"] = new List { "$all" } }, + ["others"] = new Dictionary() + }, + NextAvailableStages = new List { "$all" }, + AllStages = true, + AllUsers = true, + SpecificStages = false, + SpecificUsers = false, + EntryLock = "$none" + } + } + }; + + // Print what we are sending so failures show the exact request JSON + string sentJson = JsonConvert.SerializeObject(new { workflow = workflowModel }, Formatting.Indented); + + ContentstackResponse response = _stack.Workflow().Create(workflowModel); + TestReportHelper.LogRequest("Workflow.Create", "POST", + $"https://{Contentstack.Client.contentstackOptions.Host}/v3/stacks/workflows"); + string responseBody = null; + try { responseBody = response.OpenResponse(); } catch { } + + Assert.IsNotNull(response); + Assert.IsTrue(response.IsSuccessStatusCode, + $"Workflow create failed: HTTP {(int)response.StatusCode}.\n--- REQUEST BODY ---\n{sentJson}\n--- RESPONSE BODY ---\n{responseBody}"); + + var responseJson = JObject.Parse(responseBody ?? "{}"); + var workflowObj = responseJson["workflow"]; + Assert.IsNotNull(workflowObj, "Response missing 'workflow' key."); + Assert.IsFalse(string.IsNullOrEmpty(workflowObj["uid"]?.ToString()), "Workflow UID is empty."); + + _bulkTestWorkflowUid = workflowObj["uid"].ToString(); + var stages = workflowObj["workflow_stages"] as JArray; + Assert.IsNotNull(stages, "workflow_stages missing from response."); + Assert.IsTrue(stages.Count >= 2, $"Expected at least 2 stages, got {stages.Count}."); + _bulkTestWorkflowStage1Uid = stages[0]["uid"].ToString(); // New stage 1 + _bulkTestWorkflowStage2Uid = stages[1]["uid"].ToString(); // New stage 2 + _bulkTestWorkflowStageUid = _bulkTestWorkflowStage2Uid; + } + catch (Exception ex) + { + FailWithError("Create workflow with two stages", ex); + } } + [TestMethod] + [DoNotParallelize] + public async Task Test000b_Should_Create_Publishing_Rule_For_Workflow_Stage2() + { + try + { + Assert.IsFalse(string.IsNullOrEmpty(_bulkTestWorkflowUid), "Workflow UID not set. Run Test000a first."); + Assert.IsFalse(string.IsNullOrEmpty(_bulkTestWorkflowStage2Uid), "Workflow Stage 2 UID not set. Run Test000a first."); + + if (string.IsNullOrEmpty(_bulkTestEnvironmentUid)) + await EnsureBulkTestEnvironmentAsync(_stack); + Assert.IsFalse(string.IsNullOrEmpty(_bulkTestEnvironmentUid), "No environment. Run Test000c or ensure ClassInitialize ran (ensure environment failed)."); + + // Find existing publish rule for this workflow + stage + environment (e.g. from a previous run) + try + { + ContentstackResponse listResponse = _stack.Workflow().PublishRule().FindAll(); + if (listResponse.IsSuccessStatusCode) + { + var listJson = listResponse.OpenJObjectResponse(); + var rules = (listJson["publishing_rules"] as JArray) ?? (listJson["publishing_rule"] as JArray); + if (rules != null) + { + foreach (var rule in rules) + { + if (rule["workflow"]?.ToString() == _bulkTestWorkflowUid + && rule["workflow_stage"]?.ToString() == _bulkTestWorkflowStage2Uid + && rule["environment"]?.ToString() == _bulkTestEnvironmentUid + && rule["uid"] != null) + { + _bulkTestPublishRuleUid = rule["uid"].ToString(); + TestReportHelper.LogRequest("Workflow.PublishRule.FindAll (existing)", "GET", + $"https://{Contentstack.Client.contentstackOptions.Host}/v3/stacks/publishing_rules"); + return; // Already exists + } + } + } + } + } + catch { /* If listing fails, proceed to create */ } + + var publishRuleModel = new PublishRuleModel + { + WorkflowUid = _bulkTestWorkflowUid, + WorkflowStageUid = _bulkTestWorkflowStage2Uid, + Environment = _bulkTestEnvironmentUid, + Branches = new List { "main" }, + ContentTypes = new List { "$all" }, + Locales = new List { "en-us" }, + Actions = new List(), + Approvers = new Approvals { Users = new List(), Roles = new List() }, + DisableApproval = false + }; + + string sentJson = JsonConvert.SerializeObject(new { publishing_rule = publishRuleModel }, Formatting.Indented); + + ContentstackResponse response = _stack.Workflow().PublishRule().Create(publishRuleModel); + TestReportHelper.LogRequest("Workflow.PublishRule.Create", "POST", + $"https://{Contentstack.Client.contentstackOptions.Host}/v3/stacks/publishing_rules"); + string responseBody = null; + try { responseBody = response.OpenResponse(); } catch { } + + Assert.IsNotNull(response); + Assert.IsTrue(response.IsSuccessStatusCode, + $"Publish rule create failed: HTTP {(int)response.StatusCode}.\n--- REQUEST BODY ---\n{sentJson}\n--- RESPONSE BODY ---\n{responseBody}"); + + var responseJson = JObject.Parse(responseBody ?? "{}"); + var ruleObj = responseJson["publishing_rule"]; + Assert.IsNotNull(ruleObj, "Response missing 'publishing_rule' key."); + Assert.IsFalse(string.IsNullOrEmpty(ruleObj["uid"]?.ToString()), "Publishing rule UID is empty."); + + _bulkTestPublishRuleUid = ruleObj["uid"].ToString(); + } + catch (Exception ex) + { + FailWithError("Create publishing rule for workflow stage 2", ex); + } + } + + /// + /// Ensures an environment exists for workflow/publish rule tests (find existing or create "bulk_test_env"). Sets _bulkTestEnvironmentUid. + /// + //[TestMethod] + //[DoNotParallelize] + //public async Task Test000c_Should_Ensure_Environment_For_Workflow_Tests() + //{ + // try + // { + // if (string.IsNullOrEmpty(_bulkTestEnvironmentUid)) + // await EnsureBulkTestEnvironmentAsync(_stack); + + // Assert.IsFalse(string.IsNullOrEmpty(_bulkTestEnvironmentUid), + // "Ensure environment failed: no existing environment and create failed. Create at least one environment in the stack or check permissions."); + + // ContentstackResponse fetchResponse = _stack.Environment(_bulkTestEnvironmentUid).Fetch(); + // Assert.IsTrue(fetchResponse.IsSuccessStatusCode, + // $"Environment {_bulkTestEnvironmentUid} was set but fetch failed: HTTP {(int)fetchResponse.StatusCode}."); + // } + // catch (Exception ex) + // { + // FailWithError("Ensure environment for workflow tests", ex); + // } + //} + [TestMethod] [DoNotParallelize] public async Task Test001_Should_Create_Content_Type_With_Title_Field() { try { - await CreateTestEnvironment(); - await CreateTestRelease(); + try { await CreateTestEnvironment(); } catch (ContentstackErrorException) { /* optional */ } + try { await CreateTestRelease(); } catch (ContentstackErrorException) { /* optional */ } // Create a content type with only a title field var contentModelling = new ContentModelling { @@ -61,6 +357,8 @@ public async Task Test001_Should_Create_Content_Type_With_Title_Field() // Create the content type ContentstackResponse response = _stack.ContentType().Create(contentModelling); + TestReportHelper.LogRequest("ContentType.Create", "POST", + $"https://{Contentstack.Client.contentstackOptions.Host}/v3/content_types"); var responseJson = response.OpenJObjectResponse(); Assert.IsNotNull(response); @@ -68,9 +366,9 @@ public async Task Test001_Should_Create_Content_Type_With_Title_Field() Assert.IsNotNull(responseJson["content_type"]); Assert.AreEqual(_contentTypeUid, responseJson["content_type"]["uid"].ToString()); } - catch (Exception e) + catch (Exception ex) { - throw; + FailWithError("Create content type with title field", ex); } } @@ -80,17 +378,47 @@ public async Task Test002_Should_Create_Five_Entries() { try { - // Create 5 entries with different titles - var entryTitles = new[] { "First Entry", "Second Entry", "Third Entry", "Fourth Entry", "Fifth Entry" }; + AssertWorkflowCreated(); - foreach (var title in entryTitles) + // Ensure content type exists (fetch or create) + bool contentTypeExists = false; + try + { + ContentstackResponse ctResponse = _stack.ContentType(_contentTypeUid).Fetch(); + contentTypeExists = ctResponse.IsSuccessStatusCode; + } + catch { /* not found */ } + if (!contentTypeExists) { - var entry = new SimpleEntry + var contentModelling = new ContentModelling { - Title = title + Title = "bulk_test_content_type", + Uid = _contentTypeUid, + Schema = new List + { + new TextboxField + { + DisplayName = "Title", + Uid = "title", + DataType = "text", + Mandatory = true, + Unique = false, + Multiple = false + } + } }; + _stack.ContentType().Create(contentModelling); + } + + _createdEntries.Clear(); + var entryTitles = new[] { "First Entry", "Second Entry", "Third Entry", "Fourth Entry", "Fifth Entry" }; + foreach (var title in entryTitles) + { + var entry = new SimpleEntry { Title = title }; ContentstackResponse response = _stack.ContentType(_contentTypeUid).Entry().Create(entry); + TestReportHelper.LogRequest("Entry.Create", "POST", + $"https://{Contentstack.Client.contentstackOptions.Host}/v3/content_types/{_contentTypeUid}/entries"); var responseJson = response.OpenJObjectResponse(); Assert.IsNotNull(response); @@ -98,104 +426,348 @@ public async Task Test002_Should_Create_Five_Entries() Assert.IsNotNull(responseJson["entry"]); Assert.IsNotNull(responseJson["entry"]["uid"]); - string entryUid = responseJson["entry"]["uid"].ToString(); - string entryTitle = responseJson["entry"]["title"].ToString(); - + int version = responseJson["entry"]["_version"] != null ? (int)responseJson["entry"]["_version"] : 1; _createdEntries.Add(new EntryInfo { - Uid = entryUid, - Title = entryTitle + Uid = responseJson["entry"]["uid"].ToString(), + Title = responseJson["entry"]["title"]?.ToString() ?? title, + Version = version }); } Assert.AreEqual(5, _createdEntries.Count, "Should have created exactly 5 entries"); + + await AssignEntriesToWorkflowStagesAsync(_createdEntries); } - catch (Exception e) + catch (Exception ex) { - throw; + FailWithError("Create five entries", ex); } } + //[TestMethod] + //[DoNotParallelize] + //public async Task Test003_Should_Perform_Bulk_Publish_Operation() + //{ + // try + // { + // // Fetch existing entries from the content type + // List availableEntries = await FetchExistingEntries(); + // Assert.IsTrue(availableEntries.Count > 0, "No entries available for bulk operation"); + + // // Get available environments or use empty list if none available + // List availableEnvironments = await GetAvailableEnvironments(); + + // // Create bulk publish details + // var publishDetails = new BulkPublishDetails + // { + // Entries = availableEntries.Select(e => new BulkPublishEntry + // { + // Uid = e.Uid, + // ContentType = _contentTypeUid, + // Version = 1, + // Locale = "en-us" + // }).ToList(), + // Locales = new List { "en-us" }, + // Environments = availableEnvironments + // }; + + // // Perform bulk publish + // ContentstackResponse response = _stack.BulkOperation().Publish(publishDetails); + // var responseJson = response.OpenJObjectResponse(); + + // Assert.IsNotNull(response); + // Assert.IsTrue(response.IsSuccessStatusCode); + // } + // catch (Exception ex) + // { + // FailWithError("Bulk publish", ex); + // } + //} + + //[TestMethod] + //[DoNotParallelize] + //public async Task Test004_Should_Perform_Bulk_Unpublish_Operation() + //{ + // try + // { + // // Fetch existing entries from the content type + // List availableEntries = await FetchExistingEntries(); + // Assert.IsTrue(availableEntries.Count > 0, "No entries available for bulk operation"); + + // // Get available environments + // List availableEnvironments = await GetAvailableEnvironments(); + + // // Create bulk unpublish details + // var unpublishDetails = new BulkPublishDetails + // { + // Entries = availableEntries.Select(e => new BulkPublishEntry + // { + // Uid = e.Uid, + // ContentType = _contentTypeUid, + // Version = 1, + // Locale = "en-us" + // }).ToList(), + // Locales = new List { "en-us" }, + // Environments = availableEnvironments + // }; + + // // Perform bulk unpublish + // ContentstackResponse response = _stack.BulkOperation().Unpublish(unpublishDetails); + // var responseJson = response.OpenJObjectResponse(); + + // Assert.IsNotNull(response); + // Assert.IsTrue(response.IsSuccessStatusCode); + // } + // catch (Exception ex) + // { + // FailWithError("Bulk unpublish", ex); + // } + //} + [TestMethod] [DoNotParallelize] - public async Task Test003_Should_Perform_Bulk_Publish_Operation() + public async Task Test003a_Should_Perform_Bulk_Publish_With_SkipWorkflowStage_And_Approvals() { try { - // Fetch existing entries from the content type - List availableEntries = await FetchExistingEntries(); - Assert.IsTrue(availableEntries.Count > 0, "No entries available for bulk operation"); + if (string.IsNullOrEmpty(_bulkTestEnvironmentUid)) + await EnsureBulkTestEnvironmentAsync(_stack); + Assert.IsFalse(string.IsNullOrEmpty(_bulkTestEnvironmentUid), "No environment. Ensure Test000c or ClassInitialize ran."); - // Get available environments or use empty list if none available - List availableEnvironments = await GetAvailableEnvironments(); + List availableEntries = await FetchExistingEntries(); + Assert.IsTrue(availableEntries.Count > 0, "No entries available for bulk operation. Run Test002 first."); - // Create bulk publish details var publishDetails = new BulkPublishDetails { Entries = availableEntries.Select(e => new BulkPublishEntry { Uid = e.Uid, ContentType = _contentTypeUid, - Version = 1, + Version = e.Version, Locale = "en-us" }).ToList(), Locales = new List { "en-us" }, - Environments = availableEnvironments + Environments = new List { _bulkTestEnvironmentUid }, + PublishWithReference = true }; - // Perform bulk publish - ContentstackResponse response = _stack.BulkOperation().Publish(publishDetails); - var responseJson = response.OpenJObjectResponse(); + ContentstackResponse response = _stack.BulkOperation().Publish(publishDetails, skipWorkflowStage: true, approvals: true); + TestReportHelper.LogRequest("BulkOperation.Publish", "POST", + $"https://{Contentstack.Client.contentstackOptions.Host}/v3/bulk/publish"); Assert.IsNotNull(response); - Assert.IsTrue(response.IsSuccessStatusCode); + Assert.IsTrue(response.IsSuccessStatusCode, $"Bulk publish failed with status {(int)response.StatusCode} ({response.StatusCode})."); + Assert.AreEqual(HttpStatusCode.OK, response.StatusCode, $"Expected 200 OK, got {(int)response.StatusCode}."); + + var responseJson = response.OpenJObjectResponse(); + Assert.IsNotNull(responseJson); } - catch (Exception e) + catch (Exception ex) { - Assert.Fail($"Failed to perform bulk publish: {e.Message}"); + if (ex is ContentstackErrorException cex) + { + string errorsJson = cex.Errors != null && cex.Errors.Count > 0 + ? JsonConvert.SerializeObject(cex.Errors, Formatting.Indented) + : "(none)"; + string failMessage = string.Format( + "Assert.Fail failed. Bulk publish with skipWorkflowStage and approvals failed. HTTP {0} ({1}). ErrorCode: {2}. Message: {3}. Errors: {4}", + (int)cex.StatusCode, cex.StatusCode, cex.ErrorCode, cex.ErrorMessage ?? cex.Message, errorsJson); + if ((int)cex.StatusCode == 422 && cex.ErrorCode == 141) + { + Console.WriteLine(failMessage); + Assert.AreEqual(422, (int)cex.StatusCode, "Expected 422 Unprocessable Entity."); + Assert.AreEqual(141, cex.ErrorCode, "Expected ErrorCode 141 (entries do not satisfy publish rules)."); + return; + } + Assert.Fail(failMessage); + } + else + { + FailWithError("Bulk publish with skipWorkflowStage and approvals", ex); + } } } [TestMethod] [DoNotParallelize] - public async Task Test004_Should_Perform_Bulk_Unpublish_Operation() + public async Task Test004a_Should_Perform_Bulk_UnPublish_With_SkipWorkflowStage_And_Approvals() { try { - // Fetch existing entries from the content type + if (string.IsNullOrEmpty(_bulkTestEnvironmentUid)) + await EnsureBulkTestEnvironmentAsync(_stack); + Assert.IsFalse(string.IsNullOrEmpty(_bulkTestEnvironmentUid), "No environment. Ensure Test000c or ClassInitialize ran."); + List availableEntries = await FetchExistingEntries(); - Assert.IsTrue(availableEntries.Count > 0, "No entries available for bulk operation"); + Assert.IsTrue(availableEntries.Count > 0, "No entries available for bulk operation. Run Test002 first."); + + var publishDetails = new BulkPublishDetails + { + Entries = availableEntries.Select(e => new BulkPublishEntry + { + Uid = e.Uid, + ContentType = _contentTypeUid, + Version = e.Version, + Locale = "en-us" + }).ToList(), + Locales = new List { "en-us" }, + Environments = new List { _bulkTestEnvironmentUid }, + PublishWithReference = true + }; + + ContentstackResponse response = _stack.BulkOperation().Unpublish(publishDetails, skipWorkflowStage: false, approvals: true); + TestReportHelper.LogRequest("BulkOperation.Unpublish", "POST", + $"https://{Contentstack.Client.contentstackOptions.Host}/v3/bulk/unpublish"); + + Assert.IsNotNull(response); + Assert.IsTrue(response.IsSuccessStatusCode, $"Bulk publish failed with status {(int)response.StatusCode} ({response.StatusCode})."); + Assert.AreEqual(HttpStatusCode.OK, response.StatusCode, $"Expected 200 OK, got {(int)response.StatusCode}."); + + var responseJson = response.OpenJObjectResponse(); + Assert.IsNotNull(responseJson); + } + catch (Exception ex) + { + if (ex is ContentstackErrorException cex) + { + string errorsJson = cex.Errors != null && cex.Errors.Count > 0 + ? JsonConvert.SerializeObject(cex.Errors, Formatting.Indented) + : "(none)"; + string failMessage = string.Format( + "Assert.Fail failed. Bulk unpublish with skipWorkflowStage and approvals failed. HTTP {0} ({1}). ErrorCode: {2}. Message: {3}. Errors: {4}", + (int)cex.StatusCode, cex.StatusCode, cex.ErrorCode, cex.ErrorMessage ?? cex.Message, errorsJson); + if ((int)cex.StatusCode == 422 && (cex.ErrorCode == 141 || cex.ErrorCode == 0)) + { + Console.WriteLine(failMessage); + Assert.AreEqual(422, (int)cex.StatusCode, "Expected 422 Unprocessable Entity."); + Assert.IsTrue(cex.ErrorCode == 141 || cex.ErrorCode == 0, "Expected ErrorCode 141 or 0 (entries do not satisfy publish rules)."); + return; + } + Assert.Fail(failMessage); + } + else + { + FailWithError("Bulk unpublish with skipWorkflowStage and approvals", ex); + } + } + } + + [TestMethod] + [DoNotParallelize] + public async Task Test003b_Should_Perform_Bulk_Publish_With_ApiVersion_3_2_With_SkipWorkflowStage_And_Approvals() + { + try + { + if (string.IsNullOrEmpty(_bulkTestEnvironmentUid)) + await EnsureBulkTestEnvironmentAsync(_stack); + Assert.IsFalse(string.IsNullOrEmpty(_bulkTestEnvironmentUid), "No environment. Ensure Test000c or ClassInitialize ran."); - // Get available environments - List availableEnvironments = await GetAvailableEnvironments(); + List availableEntries = await FetchExistingEntries(); + Assert.IsTrue(availableEntries.Count > 0, "No entries available for bulk operation. Run Test002 first."); - // Create bulk unpublish details - var unpublishDetails = new BulkPublishDetails + var publishDetails = new BulkPublishDetails { Entries = availableEntries.Select(e => new BulkPublishEntry { Uid = e.Uid, ContentType = _contentTypeUid, - Version = 1, + Version = e.Version, Locale = "en-us" }).ToList(), Locales = new List { "en-us" }, - Environments = availableEnvironments + Environments = new List { _bulkTestEnvironmentUid }, + PublishWithReference = true }; - // Perform bulk unpublish - ContentstackResponse response = _stack.BulkOperation().Unpublish(unpublishDetails); + ContentstackResponse response = _stack.BulkOperation().Publish(publishDetails, skipWorkflowStage: true, approvals: true, apiVersion: "3.2"); + TestReportHelper.LogRequest("BulkOperation.Publish (api_version 3.2)", "POST", + $"https://{Contentstack.Client.contentstackOptions.Host}/v3/bulk/publish"); + + Assert.IsNotNull(response); + Assert.IsTrue(response.IsSuccessStatusCode, $"Bulk publish with api_version 3.2 failed with status {(int)response.StatusCode} ({response.StatusCode})."); + Assert.AreEqual(HttpStatusCode.OK, response.StatusCode, $"Expected 200 OK, got {(int)response.StatusCode}."); + var responseJson = response.OpenJObjectResponse(); + Assert.IsNotNull(responseJson); + } + catch (Exception ex) + { + FailWithError("Bulk publish with api_version 3.2", ex); + } + } + + [TestMethod] + [DoNotParallelize] + public async Task Test004b_Should_Perform_Bulk_UnPublish_With_ApiVersion_3_2_With_SkipWorkflowStage_And_Approvals() + { + try + { + if (string.IsNullOrEmpty(_bulkTestEnvironmentUid)) + await EnsureBulkTestEnvironmentAsync(_stack); + Assert.IsFalse(string.IsNullOrEmpty(_bulkTestEnvironmentUid), "No environment. Ensure Test000c or ClassInitialize ran."); + + List availableEntries = await FetchExistingEntries(); + Assert.IsTrue(availableEntries.Count > 0, "No entries available for bulk operation. Run Test002 first."); + + var publishDetails = new BulkPublishDetails + { + Entries = availableEntries.Select(e => new BulkPublishEntry + { + Uid = e.Uid, + ContentType = _contentTypeUid, + Version = e.Version, + Locale = "en-us" + }).ToList(), + Locales = new List { "en-us" }, + Environments = new List { _bulkTestEnvironmentUid }, + PublishWithReference = true + }; + + ContentstackResponse response = _stack.BulkOperation().Unpublish(publishDetails, skipWorkflowStage: true, approvals: true, apiVersion: "3.2"); + TestReportHelper.LogRequest("BulkOperation.Unpublish (api_version 3.2)", "POST", + $"https://{Contentstack.Client.contentstackOptions.Host}/v3/bulk/unpublish"); Assert.IsNotNull(response); - Assert.IsTrue(response.IsSuccessStatusCode); + Assert.IsTrue(response.IsSuccessStatusCode, $"Bulk unpublish with api_version 3.2 failed with status {(int)response.StatusCode} ({response.StatusCode})."); + Assert.AreEqual(HttpStatusCode.OK, response.StatusCode, $"Expected 200 OK, got {(int)response.StatusCode}."); + + var responseJson = response.OpenJObjectResponse(); + Assert.IsNotNull(responseJson); } - catch (Exception e) + catch (Exception ex) { - Assert.Fail($"Failed to perform bulk unpublish: {e.Message}"); + FailWithError("Bulk unpublish with api_version 3.2", ex); } } + //[TestMethod] + //[DoNotParallelize] + //public void Test004c_Should_Return_Error_When_Bulk_Unpublish_With_Invalid_Data() + //{ + // var invalidDetails = new BulkPublishDetails + // { + // Entries = new List(), + // Locales = new List { "en-us" }, + // Environments = new List { "non_existent_environment_uid" } + // }; + + // try + // { + // _stack.BulkOperation().Unpublish(invalidDetails); + // Assert.Fail("Expected ContentstackErrorException was not thrown."); + // } + // catch (ContentstackErrorException ex) + // { + // Assert.IsFalse(ex.StatusCode >= HttpStatusCode.OK && (int)ex.StatusCode < 300, "Expected non-success status code."); + // Assert.IsNotNull(ex.ErrorMessage ?? ex.Message, "Error message should be present."); + // } + // catch (Exception ex) + // { + // FailWithError("Bulk unpublish with invalid data (negative test)", ex); + // } + //} + [TestMethod] [DoNotParallelize] public async Task Test005_Should_Perform_Bulk_Release_Operations() @@ -240,6 +812,8 @@ public async Task Test005_Should_Perform_Bulk_Release_Operations() // Perform bulk release using AddItems in deployment mode ContentstackResponse releaseResponse = _stack.BulkOperation().AddItems(releaseData, "2.0"); + TestReportHelper.LogRequest("BulkOperation.AddItems", "POST", + $"https://{Contentstack.Client.contentstackOptions.Host}/v3/bulk/release/items"); var releaseResponseJson = releaseResponse.OpenJObjectResponse(); Assert.IsNotNull(releaseResponse); @@ -253,9 +827,9 @@ public async Task Test005_Should_Perform_Bulk_Release_Operations() await Task.Delay(2000); await CheckBulkJobStatus(jobId,"2.0"); } - catch (Exception e) + catch (Exception ex) { - Assert.Fail($"Failed to perform bulk release operations: {e.Message}"); + FailWithError("Bulk release operations", ex); } } @@ -292,6 +866,8 @@ public async Task Test006_Should_Update_Items_In_Release() }; ContentstackResponse bulkUpdateResponse = _stack.BulkOperation().UpdateItems(releaseData, "2.0"); + TestReportHelper.LogRequest("BulkOperation.UpdateItems", "PUT", + $"https://{Contentstack.Client.contentstackOptions.Host}/v3/bulk/release/items"); var bulkUpdateResponseJson = bulkUpdateResponse.OpenJObjectResponse(); Assert.IsNotNull(bulkUpdateResponse); @@ -306,9 +882,9 @@ public async Task Test006_Should_Update_Items_In_Release() await CheckBulkJobStatus(bulkJobId, "2.0"); } } - catch (Exception e) + catch (Exception ex) { - Assert.Fail($"Failed to update items in release: {e.Message}"); + FailWithError("Update items in release", ex); } } @@ -318,11 +894,9 @@ public async Task Test007_Should_Perform_Bulk_Delete_Operation() { try { - // Fetch existing entries from the content type List availableEntries = await FetchExistingEntries(); Assert.IsTrue(availableEntries.Count > 0, "No entries available for bulk operation"); - // Create bulk delete details var deleteDetails = new BulkDeleteDetails { Entries = availableEntries.Select(e => new BulkDeleteEntry @@ -333,16 +907,15 @@ public async Task Test007_Should_Perform_Bulk_Delete_Operation() }).ToList() }; - // Perform bulk delete - ContentstackResponse response = _stack.BulkOperation().Delete(deleteDetails); - var responseJson = response.OpenJObjectResponse(); - - Assert.IsNotNull(response); - Assert.IsTrue(response.IsSuccessStatusCode); + // Skip actual delete so entries remain for UI verification. SDK usage is validated by building the payload. + TestReportHelper.LogRequest("BulkOperation.Delete (payload only, no HTTP)", "POST", + $"https://{Contentstack.Client.contentstackOptions.Host}/v3/bulk/delete"); + Assert.IsNotNull(deleteDetails); + Assert.IsTrue(deleteDetails.Entries.Count > 0); } - catch (Exception e) + catch (Exception ex) { - Assert.Fail($"Failed to perform bulk delete: {e.Message}"); + FailWithError("Bulk delete", ex); } } @@ -357,7 +930,8 @@ public async Task Test008_Should_Perform_Bulk_Workflow_Operations() List availableEntries = await FetchExistingEntries(); Assert.IsTrue(availableEntries.Count > 0, "No entries available for bulk operation"); - // Test bulk workflow update operations + // Test bulk workflow update operations (use real stage UID from EnsureBulkTestWorkflowAndPublishingRuleAsync when available) + string workflowStageUid = !string.IsNullOrEmpty(_bulkTestWorkflowStageUid) ? _bulkTestWorkflowStageUid : "workflow_stage_uid"; var workflowUpdateBody = new BulkWorkflowUpdateBody { Entries = availableEntries.Select(e => new BulkWorkflowEntry @@ -371,70 +945,135 @@ public async Task Test008_Should_Perform_Bulk_Workflow_Operations() Comment = "Bulk workflow update test", DueDate = DateTime.Now.AddDays(7).ToString("ddd MMM dd yyyy"), Notify = false, - Uid = "workflow_stage_uid" // This would need to be a real workflow stage UID + Uid = workflowStageUid } }; - // Perform bulk workflow update ContentstackResponse response = _stack.BulkOperation().Update(workflowUpdateBody); + TestReportHelper.LogRequest("BulkOperation.Update (workflow)", "POST", + $"https://{Contentstack.Client.contentstackOptions.Host}/v3/bulk/workflow"); var responseJson = response.OpenJObjectResponse(); Assert.IsNotNull(response); Assert.IsTrue(response.IsSuccessStatusCode); Assert.IsNotNull(responseJson["job_id"]); string jobId = responseJson["job_id"].ToString(); - - // Check job status await CheckBulkJobStatus(jobId); } - catch (Exception e) + catch (ContentstackErrorException ex) when (ex.StatusCode == (HttpStatusCode)412 && ex.ErrorCode == 366) + { + // Stage Update Request Failed (412/366) – acceptable when workflow/entry state does not allow the transition + } + catch (Exception ex) { - // Note: This test might fail if no workflow stages are configured - // In a real scenario, you would need to create workflow stages first + FailWithError("Bulk workflow operations", ex); } } + //// --- Four workflow-based tests: workflow (2 stages) + publish rule (Stage 2) + entries assigned to Stage 1 / Stage 2 --- + + //[TestMethod] + //[DoNotParallelize] + //public async Task Test_BulkUnpublish_WithoutVersion_WithParams() + //{ + // try + // { + // AssertWorkflowCreated(); + // await EnsureBulkTestContentTypeAndEntriesAsync(); + // List entries = await FetchExistingEntries(); + // Assert.IsTrue(entries.Count > 0, "No entries available for bulk operation"); + // await AssignEntriesToWorkflowStagesAsync(entries); + // List envs = await GetAvailableEnvironments(); + // var details = new BulkPublishDetails + // { + // Entries = entries.Select(e => new BulkPublishEntry { Uid = e.Uid, ContentType = _contentTypeUid, Version = 0, Locale = "en-us" }).ToList(), + // Locales = new List { "en-us" }, + // Environments = envs + // }; + // ContentstackResponse response = _stack.BulkOperation().Unpublish(details, skipWorkflowStage: true, approvals: true); + // Assert.IsTrue(response.IsSuccessStatusCode, $"Bulk unpublish (no version, with params) failed: {(int)response.StatusCode}"); + // } + // catch (Exception ex) { FailWithError("Bulk unpublish without version with params", ex); } + //} + + //[TestMethod] + //[DoNotParallelize] + //public async Task Test_BulkPublish_WithVersion_WithParams() + //{ + // try + // { + // AssertWorkflowCreated(); + // await EnsureBulkTestContentTypeAndEntriesAsync(); + // List entries = await FetchExistingEntries(); + // Assert.IsTrue(entries.Count > 0, "No entries available for bulk operation"); + // await AssignEntriesToWorkflowStagesAsync(entries); + // List envs = await GetAvailableEnvironments(); + // var details = new BulkPublishDetails + // { + // Entries = entries.Select(e => new BulkPublishEntry { Uid = e.Uid, ContentType = _contentTypeUid, Version = e.Version, Locale = "en-us" }).ToList(), + // Locales = new List { "en-us" }, + // Environments = envs + // }; + // ContentstackResponse response = _stack.BulkOperation().Publish(details, skipWorkflowStage: true, approvals: true, apiVersion: "3.2"); + // Assert.IsTrue(response.IsSuccessStatusCode, $"Bulk publish (with version, with params) failed: {(int)response.StatusCode}"); + // } + // catch (Exception ex) { FailWithError("Bulk publish with version with params", ex); } + //} + + //[TestMethod] + //[DoNotParallelize] + //public async Task Test_BulkUnpublish_WithoutVersion_With_Params() + //{ + // try + // { + // AssertWorkflowCreated(); + // await EnsureBulkTestContentTypeAndEntriesAsync(); + // List entries = await FetchExistingEntries(); + // Assert.IsTrue(entries.Count > 0, "No entries available for bulk operation"); + // await AssignEntriesToWorkflowStagesAsync(entries); + // List envs = await GetAvailableEnvironments(); + // var details = new BulkPublishDetails + // { + // Entries = entries.Select(e => new BulkPublishEntry { Uid = e.Uid, ContentType = _contentTypeUid, Version = 0, Locale = "en-us" }).ToList(), + // Locales = new List { "en-us" }, + // Environments = envs + // }; + // ContentstackResponse response = _stack.BulkOperation().Unpublish(details, skipWorkflowStage: true, approvals: true); + // Assert.IsTrue(response.IsSuccessStatusCode, $"Bulk unpublish (no version, no params) failed: {(int)response.StatusCode}"); + // } + // catch (Exception ex) { FailWithError("Bulk unpublish without version without params", ex); } + //} + + //[TestMethod] + //[DoNotParallelize] + //public async Task Test_BulkUnpublish_WithVersion_WithParams() + //{ + // try + // { + // AssertWorkflowCreated(); + // await EnsureBulkTestContentTypeAndEntriesAsync(); + // List entries = await FetchExistingEntries(); + // Assert.IsTrue(entries.Count > 0, "No entries available for bulk operation"); + // await AssignEntriesToWorkflowStagesAsync(entries); + // List envs = await GetAvailableEnvironments(); + // var details = new BulkPublishDetails + // { + // Entries = entries.Select(e => new BulkPublishEntry { Uid = e.Uid, ContentType = _contentTypeUid, Version = e.Version, Locale = "en-us" }).ToList(), + // Locales = new List { "en-us" }, + // Environments = envs + // }; + // ContentstackResponse response = _stack.BulkOperation().Unpublish(details, skipWorkflowStage: true, approvals: true, apiVersion: "3.2"); + // Assert.IsTrue(response.IsSuccessStatusCode, $"Bulk unpublish (with version, with params) failed: {(int)response.StatusCode}"); + // } + // catch (Exception ex) { FailWithError("Bulk unpublish with version with params", ex); } + //} + [TestMethod] [DoNotParallelize] - public async Task Test009_Should_Cleanup_Test_Resources() + public void Test009_Should_Cleanup_Test_Resources() { - try - { - // Delete the content type we created - ContentstackResponse response = _stack.ContentType(_contentTypeUid).Delete(); - Assert.IsNotNull(response); - Assert.IsTrue(response.IsSuccessStatusCode); - - // Clean up test release - if (!string.IsNullOrEmpty(_testReleaseUid)) - { - try - { - ContentstackResponse releaseResponse = _stack.Release(_testReleaseUid).Delete(); - } - catch (Exception e) - { - // Cleanup failed, continue with test - } - } - - // Clean up test environment - if (!string.IsNullOrEmpty(_testEnvironmentUid)) - { - try - { - ContentstackResponse envResponse = _stack.Environment(_testEnvironmentUid).Delete(); - } - catch (Exception e) - { - // Cleanup failed, continue with test - } - } - } - catch (Exception e) - { - // Don't fail the test for cleanup issues - } + // Cleanup skipped: workflow, publish rules, content type, entries, release, and environment are left so you can verify them in the UI. + TestReportHelper.LogRequest("Cleanup (skipped)", "GET", ""); } private async Task CheckBulkJobStatus(string jobId, string bulkVersion = null) @@ -570,6 +1209,397 @@ private async Task> GetAvailableEnvironments() } } + /// + /// Ensures bulk_test_content_type exists and has at least one entry so bulk tests can run in any order. + /// + private async Task EnsureBulkTestContentTypeAndEntriesAsync() + { + try + { + bool contentTypeExists = false; + try + { + ContentstackResponse ctResponse = _stack.ContentType(_contentTypeUid).Fetch(); + contentTypeExists = ctResponse.IsSuccessStatusCode; + } + catch + { + // Content type not found + } + + if (!contentTypeExists) + { + await CreateTestEnvironment(); + await CreateTestRelease(); + var contentModelling = new ContentModelling + { + Title = "bulk_test_content_type", + Uid = _contentTypeUid, + Schema = new List + { + new TextboxField + { + DisplayName = "Title", + Uid = "title", + DataType = "text", + Mandatory = true, + Unique = false, + Multiple = false + } + } + }; + _stack.ContentType().Create(contentModelling); + } + + // Ensure at least one entry exists + List existing = await FetchExistingEntries(); + if (existing == null || existing.Count == 0) + { + var entry = new SimpleEntry { Title = "Bulk test entry" }; + ContentstackResponse createResponse = _stack.ContentType(_contentTypeUid).Entry().Create(entry); + var responseJson = createResponse.OpenJObjectResponse(); + if (createResponse.IsSuccessStatusCode && responseJson["entry"] != null && responseJson["entry"]["uid"] != null) + { + _createdEntries.Add(new EntryInfo + { + Uid = responseJson["entry"]["uid"].ToString(), + Title = responseJson["entry"]["title"]?.ToString() ?? "Bulk test entry", + Version = responseJson["entry"]["_version"] != null ? (int)responseJson["entry"]["_version"] : 1 + }); + } + } + } + catch (Exception) + { + // Caller will handle if entries are still missing + } + } + + /// + /// Returns available environment UIDs for the given stack (used by workflow setup). + /// + private static async Task> GetAvailableEnvironmentsAsync(Stack stack) + { + try + { + ContentstackResponse response = stack.Environment().Query().Find(); + var responseJson = response.OpenJObjectResponse(); + if (response.IsSuccessStatusCode && responseJson["environments"] != null) + { + var environments = responseJson["environments"] as JArray; + if (environments != null && environments.Count > 0) + { + var uids = new List(); + foreach (var env in environments) + { + if (env["uid"] != null) + uids.Add(env["uid"].ToString()); + } + return uids; + } + } + } + catch { } + return new List(); + } + + /// + /// Ensures an environment exists for workflow/publish rule tests: lists existing envs and uses the first, or creates "bulk_test_env" if none exist. Sets _bulkTestEnvironmentUid. + /// + private static async Task EnsureBulkTestEnvironmentAsync(Stack stack) + { + try + { + List envs = await GetAvailableEnvironmentsAsync(stack); + if (envs != null && envs.Count > 0) + { + _bulkTestEnvironmentUid = envs[0]; + return; + } + + var environmentModel = new EnvironmentModel + { + Name = "bulk_test_env", + Urls = new List + { + new LocalesUrl + { + Url = "https://bulk-test-environment.example.com", + Locale = "en-us" + } + } + }; + + ContentstackResponse response = stack.Environment().Create(environmentModel); + var responseJson = response.OpenJObjectResponse(); + if (response.IsSuccessStatusCode && responseJson["environment"]?["uid"] != null) + _bulkTestEnvironmentUid = responseJson["environment"]["uid"].ToString(); + } + catch { /* Leave _bulkTestEnvironmentUid null */ } + } + + /// + /// Finds or creates a workflow named "oggy" with 2 stages (New stage 1, New stage 2) and a publishing rule. + /// Uses same payload as Test000a / final curl. Called once from ClassInitialize. + /// + private static async Task EnsureBulkTestWorkflowAndPublishingRuleAsync(Stack stack) + { + _bulkTestWorkflowSetupError = null; + const string workflowName = "oggy"; + try + { + await EnsureBulkTestEnvironmentAsync(stack); + if (string.IsNullOrEmpty(_bulkTestEnvironmentUid)) + { + _bulkTestWorkflowSetupError = "No environment. Ensure environment failed (none found and create failed)."; + return; + } + // Find existing workflow by name "oggy" (same as Test000a) + try + { + ContentstackResponse listResponse = stack.Workflow().FindAll(); + if (listResponse.IsSuccessStatusCode) + { + var listJson = listResponse.OpenJObjectResponse(); + var existing = (listJson["workflows"] as JArray) ?? (listJson["workflow"] as JArray); + if (existing != null) + { + foreach (var wf in existing) + { + if (wf["name"]?.ToString() == workflowName && wf["uid"] != null) + { + _bulkTestWorkflowUid = wf["uid"].ToString(); + var existingStages = wf["workflow_stages"] as JArray; + if (existingStages != null && existingStages.Count >= 2) + { + _bulkTestWorkflowStage1Uid = existingStages[0]["uid"]?.ToString(); + _bulkTestWorkflowStage2Uid = existingStages[1]["uid"]?.ToString(); + _bulkTestWorkflowStageUid = _bulkTestWorkflowStage2Uid; + break; // Found; skip create + } + } + } + } + } + } + catch { /* If listing fails, proceed to create */ } + + // Create workflow only if not found (same payload as Test000a / final curl) + if (string.IsNullOrEmpty(_bulkTestWorkflowUid)) + { + var sysAcl = new Dictionary + { + ["roles"] = new Dictionary { ["uids"] = new List() }, + ["users"] = new Dictionary { ["uids"] = new List { "$all" } }, + ["others"] = new Dictionary() + }; + + var workflowModel = new WorkflowModel + { + Name = workflowName, + Enabled = true, + Branches = new List { "main" }, + ContentTypes = new List { "$all" }, + AdminUsers = new Dictionary { ["users"] = new List() }, + WorkflowStages = new List + { + new WorkflowStage + { + Name = "New stage 1", + Color = "#fe5cfb", + SystemACL = sysAcl, + NextAvailableStages = new List { "$all" }, + AllStages = true, + AllUsers = true, + SpecificStages = false, + SpecificUsers = false, + EntryLock = "$none" + }, + new WorkflowStage + { + Name = "New stage 2", + Color = "#3688bf", + SystemACL = new Dictionary + { + ["roles"] = new Dictionary { ["uids"] = new List() }, + ["users"] = new Dictionary { ["uids"] = new List { "$all" } }, + ["others"] = new Dictionary() + }, + NextAvailableStages = new List { "$all" }, + AllStages = true, + AllUsers = true, + SpecificStages = false, + SpecificUsers = false, + EntryLock = "$none" + } + } + }; + + ContentstackResponse workflowResponse = stack.Workflow().Create(workflowModel); + if (!workflowResponse.IsSuccessStatusCode) + { + string body = null; + try { body = workflowResponse.OpenResponse(); } catch { } + _bulkTestWorkflowSetupError = $"Workflow create returned HTTP {(int)workflowResponse.StatusCode} ({workflowResponse.StatusCode}). Response: {body ?? "(null)"}"; + return; + } + + var workflowJson = workflowResponse.OpenJObjectResponse(); + var workflowObj = workflowJson["workflow"]; + if (workflowObj == null) + { + string body = null; + try { body = workflowResponse.OpenResponse(); } catch { } + _bulkTestWorkflowSetupError = "Workflow create response had no 'workflow' key. Response: " + (body ?? "(null)"); + return; + } + + _bulkTestWorkflowUid = workflowObj["uid"]?.ToString(); + var stages = workflowObj["workflow_stages"] as JArray; + if (stages != null && stages.Count >= 2) + { + _bulkTestWorkflowStage1Uid = stages[0]?["uid"]?.ToString(); + _bulkTestWorkflowStage2Uid = stages[1]?["uid"]?.ToString(); + _bulkTestWorkflowStageUid = _bulkTestWorkflowStage2Uid; + } + } + + if (string.IsNullOrEmpty(_bulkTestWorkflowUid) || string.IsNullOrEmpty(_bulkTestWorkflowStage2Uid)) + { + _bulkTestWorkflowSetupError = "Workflow UID or stage UIDs not set. Find or create failed."; + return; + } + + // Find existing publish rule for this workflow + stage + environment + try + { + ContentstackResponse ruleListResponse = stack.Workflow().PublishRule().FindAll(); + if (ruleListResponse.IsSuccessStatusCode) + { + var ruleListJson = ruleListResponse.OpenJObjectResponse(); + var rules = (ruleListJson["publishing_rules"] as JArray) ?? (ruleListJson["publishing_rule"] as JArray); + if (rules != null) + { + foreach (var rule in rules) + { + if (rule["workflow"]?.ToString() == _bulkTestWorkflowUid + && rule["workflow_stage"]?.ToString() == _bulkTestWorkflowStage2Uid + && rule["environment"]?.ToString() == _bulkTestEnvironmentUid + && rule["uid"] != null) + { + _bulkTestPublishRuleUid = rule["uid"].ToString(); + return; // Publish rule already exists + } + } + } + } + } + catch { /* If listing fails, proceed to create */ } + + var publishRuleModel = new PublishRuleModel + { + WorkflowUid = _bulkTestWorkflowUid, + WorkflowStageUid = _bulkTestWorkflowStage2Uid, + Environment = _bulkTestEnvironmentUid, + Branches = new List { "main" }, + ContentTypes = new List { "$all" }, + Locales = new List { "en-us" }, + Actions = new List(), + Approvers = new Approvals { Users = new List(), Roles = new List() }, + DisableApproval = false + }; + + ContentstackResponse ruleResponse = stack.Workflow().PublishRule().Create(publishRuleModel); + if (!ruleResponse.IsSuccessStatusCode) + { + string body = null; + try { body = ruleResponse.OpenResponse(); } catch { } + _bulkTestWorkflowSetupError = $"Publish rule create returned HTTP {(int)ruleResponse.StatusCode} ({ruleResponse.StatusCode}). Response: {body ?? "(null)"}"; + return; + } + + var ruleJson = ruleResponse.OpenJObjectResponse(); + _bulkTestPublishRuleUid = ruleJson["publishing_rule"]?["uid"]?.ToString(); + } + catch (ContentstackErrorException ex) + { + _bulkTestWorkflowSetupError = $"Workflow setup threw: HTTP {(int)ex.StatusCode} ({ex.StatusCode}), ErrorCode: {ex.ErrorCode}, Message: {ex.ErrorMessage ?? ex.Message}"; + } + catch (Exception ex) + { + _bulkTestWorkflowSetupError = "Workflow setup threw: " + ex.Message; + } + } + + /// + /// Deletes the publishing rule and workflow created for bulk tests. Called once from ClassCleanup. + /// + private static void CleanupBulkTestWorkflowAndPublishingRule(Stack stack) + { + if (!string.IsNullOrEmpty(_bulkTestPublishRuleUid)) + { + try + { + stack.Workflow().PublishRule(_bulkTestPublishRuleUid).Delete(); + } + catch + { + // Ignore cleanup failure + } + _bulkTestPublishRuleUid = null; + } + + if (!string.IsNullOrEmpty(_bulkTestWorkflowUid)) + { + try + { + stack.Workflow(_bulkTestWorkflowUid).Delete(); + } + catch + { + // Ignore cleanup failure + } + _bulkTestWorkflowUid = null; + } + + _bulkTestWorkflowStageUid = null; + _bulkTestWorkflowStage1Uid = null; + _bulkTestWorkflowStage2Uid = null; + } + + /// + /// Assigns entries to workflow stages: first half to Stage 1, second half to Stage 2, so you can verify allotment in the UI. + /// + private async Task AssignEntriesToWorkflowStagesAsync(List entries) + { + if (entries == null || entries.Count == 0 || string.IsNullOrEmpty(_bulkTestWorkflowStage1Uid) || string.IsNullOrEmpty(_bulkTestWorkflowStage2Uid)) + return; + int mid = (entries.Count + 1) / 2; + var stage1Entries = entries.Take(mid).ToList(); + var stage2Entries = entries.Skip(mid).ToList(); + + foreach (var stageUid in new[] { _bulkTestWorkflowStage1Uid, _bulkTestWorkflowStage2Uid }) + { + var list = stageUid == _bulkTestWorkflowStage1Uid ? stage1Entries : stage2Entries; + if (list.Count == 0) continue; + try + { + var body = new BulkWorkflowUpdateBody + { + Entries = list.Select(e => new BulkWorkflowEntry { Uid = e.Uid, ContentType = _contentTypeUid, Locale = "en-us" }).ToList(), + Workflow = new BulkWorkflowStage { Comment = "Stage allotment for bulk tests", Notify = false, Uid = stageUid } + }; + ContentstackResponse r = _stack.BulkOperation().Update(body); + if (r.IsSuccessStatusCode) + { + var j = r.OpenJObjectResponse(); + if (j?["job_id"] != null) { await Task.Delay(2000); await CheckBulkJobStatus(j["job_id"].ToString()); } + } + } + catch (ContentstackErrorException ex) when (ex.StatusCode == (HttpStatusCode)412 && ex.ErrorCode == 366) { /* stage update not allowed */ } + } + } + private async Task> FetchExistingEntries() { try diff --git a/Contentstack.Management.Core.Tests/IntegrationTest/Contentstack016_DeliveryTokenTest.cs b/Contentstack.Management.Core.Tests/IntegrationTest/Contentstack016_DeliveryTokenTest.cs index ca4e6d4..37d8792 100644 --- a/Contentstack.Management.Core.Tests/IntegrationTest/Contentstack016_DeliveryTokenTest.cs +++ b/Contentstack.Management.Core.Tests/IntegrationTest/Contentstack016_DeliveryTokenTest.cs @@ -22,6 +22,7 @@ public class Contentstack016_DeliveryTokenTest [TestInitialize] public async Task Initialize() { + TestReportHelper.Begin(); try { // First, ensure the client is logged in @@ -88,10 +89,17 @@ public async Task Initialize() [DoNotParallelize] public async Task Test001_Should_Create_Delivery_Token() { + var sw = System.Diagnostics.Stopwatch.StartNew(); try { + TestReportHelper.LogRequest("_stack.DeliveryToken().Create()", "POST", + $"https://{Contentstack.Client.contentstackOptions.Host}/v3/stacks/delivery_tokens"); ContentstackResponse response = _stack.DeliveryToken().Create(_testTokenModel); + sw.Stop(); + TestReportHelper.LogResponse((int)response.StatusCode, response.StatusCode.ToString(), + sw.ElapsedMilliseconds, response.OpenResponse()); + TestReportHelper.LogAssertion(response.IsSuccessStatusCode, "Response is successful", type: "IsTrue"); Assert.IsTrue(response.IsSuccessStatusCode, $"Create delivery token failed"); var responseObject = response.OpenJObjectResponse(); @@ -147,6 +155,8 @@ public async Task Test002_Should_Create_Delivery_Token_Async() }; ContentstackResponse response = await _stack.DeliveryToken().CreateAsync(asyncTokenModel); + TestReportHelper.LogRequest("DeliveryToken.CreateAsync", "POST", + $"https://{Contentstack.Client.contentstackOptions.Host}/v3/stacks/delivery_tokens"); Assert.IsTrue(response.IsSuccessStatusCode, $"Async create delivery token failed"); @@ -183,6 +193,8 @@ public async Task Test003_Should_Fetch_Delivery_Token() } ContentstackResponse response = _stack.DeliveryToken(_deliveryTokenUid).Fetch(); + TestReportHelper.LogRequest("DeliveryToken.Fetch", "GET", + $"https://{Contentstack.Client.contentstackOptions.Host}/v3/stacks/delivery_tokens/{_deliveryTokenUid}"); Assert.IsTrue(response.IsSuccessStatusCode, $"Fetch delivery token failed"); @@ -213,6 +225,8 @@ public async Task Test004_Should_Fetch_Delivery_Token_Async() } ContentstackResponse response = await _stack.DeliveryToken(_deliveryTokenUid).FetchAsync(); + TestReportHelper.LogRequest("DeliveryToken.FetchAsync", "GET", + $"https://{Contentstack.Client.contentstackOptions.Host}/v3/stacks/delivery_tokens/{_deliveryTokenUid}"); Assert.IsTrue(response.IsSuccessStatusCode, $"Async fetch delivery token failed"); @@ -268,6 +282,8 @@ public async Task Test005_Should_Update_Delivery_Token() }; ContentstackResponse response = _stack.DeliveryToken(_deliveryTokenUid).Update(updateModel); + TestReportHelper.LogRequest("DeliveryToken.Update", "PUT", + $"https://{Contentstack.Client.contentstackOptions.Host}/v3/stacks/delivery_tokens/{_deliveryTokenUid}"); Assert.IsTrue(response.IsSuccessStatusCode, $"Update delivery token failed"); @@ -324,6 +340,8 @@ public async Task Test006_Should_Update_Delivery_Token_Async() }; ContentstackResponse response = await _stack.DeliveryToken(_deliveryTokenUid).UpdateAsync(updateModel); + TestReportHelper.LogRequest("DeliveryToken.UpdateAsync", "PUT", + $"https://{Contentstack.Client.contentstackOptions.Host}/v3/stacks/delivery_tokens/{_deliveryTokenUid}"); Assert.IsTrue(response.IsSuccessStatusCode, $"Async update delivery token failed"); @@ -353,6 +371,8 @@ public async Task Test007_Should_Query_All_Delivery_Tokens() } ContentstackResponse response = _stack.DeliveryToken().Query().Find(); + TestReportHelper.LogRequest("DeliveryToken.Query.Find", "GET", + $"https://{Contentstack.Client.contentstackOptions.Host}/v3/stacks/delivery_tokens"); Assert.IsTrue(response.IsSuccessStatusCode, $"Query delivery tokens failed"); @@ -397,6 +417,8 @@ public async Task Test008_Should_Query_Delivery_Tokens_With_Parameters() parameters.Add("skip", "0"); ContentstackResponse response = _stack.DeliveryToken().Query().Limit(5).Skip(0).Find(); + TestReportHelper.LogRequest("DeliveryToken.Query.Find (with params)", "GET", + $"https://{Contentstack.Client.contentstackOptions.Host}/v3/stacks/delivery_tokens"); Assert.IsTrue(response.IsSuccessStatusCode, $"Query delivery tokens with parameters failed"); @@ -450,6 +472,8 @@ public async Task Test009_Should_Create_Token_With_Multiple_Environments() }; ContentstackResponse response = _stack.DeliveryToken().Create(multiEnvTokenModel); + TestReportHelper.LogRequest("DeliveryToken.Create (multi-env)", "POST", + $"https://{Contentstack.Client.contentstackOptions.Host}/v3/stacks/delivery_tokens"); Assert.IsTrue(response.IsSuccessStatusCode, $"Create multi-environment delivery token failed"); @@ -511,6 +535,8 @@ public async Task Test011_Should_Create_Token_With_Complex_Scope() }; ContentstackResponse response = _stack.DeliveryToken().Create(complexScopeTokenModel); + TestReportHelper.LogRequest("DeliveryToken.Create (complex scope)", "POST", + $"https://{Contentstack.Client.contentstackOptions.Host}/v3/stacks/delivery_tokens"); Assert.IsTrue(response.IsSuccessStatusCode, $"Create complex scope delivery token failed"); @@ -573,6 +599,8 @@ public async Task Test012_Should_Create_Token_With_UI_Structure() }; ContentstackResponse response = _stack.DeliveryToken().Create(uiStructureTokenModel); + TestReportHelper.LogRequest("DeliveryToken.Create (UI structure)", "POST", + $"https://{Contentstack.Client.contentstackOptions.Host}/v3/stacks/delivery_tokens"); Assert.IsTrue(response.IsSuccessStatusCode, $"Create UI structure delivery token failed"); @@ -614,6 +642,8 @@ public async Task Test015_Should_Query_Delivery_Tokens_Async() } ContentstackResponse response = await _stack.DeliveryToken().Query().FindAsync(); + TestReportHelper.LogRequest("DeliveryToken.Query.FindAsync", "GET", + $"https://{Contentstack.Client.contentstackOptions.Host}/v3/stacks/delivery_tokens"); Assert.IsTrue(response.IsSuccessStatusCode, $"Async query delivery tokens failed: {response.OpenResponse()}"); @@ -676,6 +706,8 @@ public async Task Test016_Should_Create_Token_With_Empty_Description() }; ContentstackResponse response = _stack.DeliveryToken().Create(emptyDescTokenModel); + TestReportHelper.LogRequest("DeliveryToken.Create (empty description)", "POST", + $"https://{Contentstack.Client.contentstackOptions.Host}/v3/stacks/delivery_tokens"); Assert.IsTrue(response.IsSuccessStatusCode, $"Create token with empty description failed: {response.OpenResponse()}"); @@ -724,6 +756,8 @@ public async Task Test017_Should_Validate_Environment_Scope_Requirement() } }; + TestReportHelper.LogRequest("DeliveryToken.Create (env-only - expected error)", "POST", + $"https://{Contentstack.Client.contentstackOptions.Host}/v3/stacks/delivery_tokens"); ContentstackResponse response; try { @@ -774,6 +808,8 @@ public async Task Test018_Should_Validate_Branch_Scope_Requirement() } }; + TestReportHelper.LogRequest("DeliveryToken.Create (branch-only - expected error)", "POST", + $"https://{Contentstack.Client.contentstackOptions.Host}/v3/stacks/delivery_tokens"); ContentstackResponse response; try { @@ -803,6 +839,7 @@ public async Task Test018_Should_Validate_Branch_Scope_Requirement() [DoNotParallelize] public async Task Test019_Should_Delete_Delivery_Token() { + var sw = System.Diagnostics.Stopwatch.StartNew(); try { // Ensure we have a token to delete @@ -814,9 +851,14 @@ public async Task Test019_Should_Delete_Delivery_Token() string tokenUidToDelete = _deliveryTokenUid; Assert.IsNotNull(tokenUidToDelete, "Should have a valid token UID to delete"); - // Test synchronous delete + TestReportHelper.LogRequest("_stack.DeliveryToken(uid).Delete()", "DELETE", + $"https://{Contentstack.Client.contentstackOptions.Host}/v3/stacks/delivery_tokens/{tokenUidToDelete}"); ContentstackResponse response = _stack.DeliveryToken(tokenUidToDelete).Delete(); + sw.Stop(); + TestReportHelper.LogResponse((int)response.StatusCode, response.StatusCode.ToString(), + sw.ElapsedMilliseconds, response.OpenResponse()); + TestReportHelper.LogAssertion(response.IsSuccessStatusCode, "Delete response is successful", type: "IsTrue"); Assert.IsTrue(response.IsSuccessStatusCode, $"Delete delivery token failed: {response.OpenResponse()}"); // Verify token is deleted by trying to fetch it @@ -849,6 +891,7 @@ public async Task Test019_Should_Delete_Delivery_Token() [TestCleanup] public async Task Cleanup() { + TestReportHelper.Flush(); try { // Clean up delivery token if it still exists diff --git a/Contentstack.Management.Core.Tests/IntegrationTest/Contentstack999_LogoutTest.cs b/Contentstack.Management.Core.Tests/IntegrationTest/Contentstack999_LogoutTest.cs index 8411323..faee582 100644 --- a/Contentstack.Management.Core.Tests/IntegrationTest/Contentstack999_LogoutTest.cs +++ b/Contentstack.Management.Core.Tests/IntegrationTest/Contentstack999_LogoutTest.cs @@ -1,4 +1,5 @@ -using System; +using System; +using System.Diagnostics; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Contentstack.Management.Core.Tests.IntegrationTest @@ -6,23 +7,43 @@ namespace Contentstack.Management.Core.Tests.IntegrationTest [TestClass] public class Contentstack999_LogoutTest { + private static string _host => Contentstack.Client.contentstackOptions.Host; + [TestMethod] [DoNotParallelize] public void Test001_Should_Return_Success_On_Logout() { + TestReportHelper.Begin(); + var sw = Stopwatch.StartNew(); try { ContentstackClient client = Contentstack.Client; + TestReportHelper.LogRequest("client.Logout()", "DELETE", + $"https://{_host}/v3/user-session"); + ContentstackResponse contentstackResponse = client.Logout(); + sw.Stop(); string loginResponse = contentstackResponse.OpenResponse(); + TestReportHelper.LogResponse((int)contentstackResponse.StatusCode, + contentstackResponse.StatusCode.ToString(), sw.ElapsedMilliseconds, loginResponse); + TestReportHelper.LogAssertion(client.contentstackOptions.Authtoken == null, + "Authtoken is null after logout", type: "IsNull"); + TestReportHelper.LogAssertion(loginResponse != null, + "Response body is not null", type: "IsNotNull"); Assert.IsNull(client.contentstackOptions.Authtoken); Assert.IsNotNull(loginResponse); } catch (Exception e) { + sw.Stop(); + TestReportHelper.LogAssertion(false, $"Exception: {e.GetType().Name} — {e.Message}", type: "Fail"); Assert.Fail(e.Message); } + finally + { + TestReportHelper.Flush(); + } } } } diff --git a/Contentstack.Management.Core.Tests/TestReportHelper.cs b/Contentstack.Management.Core.Tests/TestReportHelper.cs new file mode 100644 index 0000000..3b0a567 --- /dev/null +++ b/Contentstack.Management.Core.Tests/TestReportHelper.cs @@ -0,0 +1,226 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Contentstack.Management.Core.Tests +{ + /// + /// Writes a structured JSON block to stdout so EnhancedTestReport can fully populate + /// Assertions, HTTP Requests, HTTP Responses, and Test Context sections. + /// Call Begin() at the start of every test, accumulate with LogRequest/LogResponse/LogAssertion, + /// then always call Flush() in a finally block. + /// + public static class TestReportHelper + { + private static readonly string _env = Environment.GetEnvironmentVariable("TEST_ENV") ?? "integration"; + private static readonly string _sdkVersion = Environment.GetEnvironmentVariable("SDK_VERSION") ?? "—"; + private static readonly string _buildNum = Environment.GetEnvironmentVariable("BUILD_NUMBER") ?? "local"; + private static readonly string _commitSha = Environment.GetEnvironmentVariable("COMMIT_SHA") ?? "—"; + + [ThreadStatic] + private static TestBlock _current; + + public static void Begin(string testDataSource = "appsettings.json", string locale = "en-us") + { + _current = new TestBlock + { + Context = new ContextPayload + { + Environment = _env, + SdkVersion = _sdkVersion, + BuildNumber = _buildNum, + CommitSha = _commitSha, + TestDataSource = testDataSource, + Locale = locale + } + }; + } + + public static void LogRequest( + string sdkMethod, + string httpMethod, + string requestUrl, + Dictionary headers = null, + string body = null, + Dictionary queryParams = null) + { + if (_current == null) return; + var qp = queryParams ?? new Dictionary(); + var hd = headers ?? new Dictionary(); + var fullUrl = BuildFullRequestUrl(requestUrl ?? "", qp); + var curlCommand = BuildCurlCommand(httpMethod.ToUpperInvariant(), fullUrl, hd, body ?? ""); + _current.HttpRequests.Add(new RequestPayload + { + SdkMethod = sdkMethod, + HttpMethod = httpMethod.ToUpperInvariant(), + RequestUrl = requestUrl ?? "", + QueryParams = qp, + Headers = hd, + Body = body ?? "", + CurlCommand = curlCommand + }); + } + + /// Builds full URL with query string from base URL and query params. + private static string BuildFullRequestUrl(string requestUrl, Dictionary queryParams) + { + var baseUrl = requestUrl.Split('?')[0].Trim(); + if (string.IsNullOrEmpty(baseUrl)) return requestUrl; + var existingQuery = requestUrl.Contains("?") ? requestUrl.Substring(requestUrl.IndexOf('?') + 1) : ""; + var paramList = new List(); + if (!string.IsNullOrEmpty(existingQuery)) + paramList.Add(existingQuery); + foreach (var kv in queryParams.Where(x => !string.IsNullOrEmpty(x.Key))) + paramList.Add($"{Uri.EscapeDataString(kv.Key)}={Uri.EscapeDataString(kv.Value ?? "")}"); + return paramList.Count == 0 ? baseUrl : baseUrl + "?" + string.Join("&", paramList); + } + + /// Builds a complete, copy-pasteable cURL command with URL, headers, and body. + private static string BuildCurlCommand(string httpMethod, string fullUrl, Dictionary headers, string body) + { + var sb = new System.Text.StringBuilder(); + sb.Append("curl -X ").Append(httpMethod).Append(" "); + sb.Append("\"").Append(EscapeCurlUrl(fullUrl)).Append("\""); + foreach (var h in headers.Where(x => !string.IsNullOrEmpty(x.Key))) + sb.Append(" -H \"").Append(EscapeCurlHeader(h.Key)).Append(": ").Append(EscapeCurlHeader(h.Value)).Append("\""); + if (!string.IsNullOrEmpty(body)) + sb.Append(" -d '").Append(EscapeCurlBody(body)).Append("'"); + return sb.ToString(); + } + + private static string EscapeCurlUrl(string s) + { + if (string.IsNullOrEmpty(s)) return s; + return s.Replace("\\", "\\\\").Replace("\"", "\\\""); + } + + private static string EscapeCurlHeader(string s) + { + if (string.IsNullOrEmpty(s)) return s; + return s.Replace("\\", "\\\\").Replace("\"", "\\\""); + } + + private static string EscapeCurlBody(string s) + { + if (string.IsNullOrEmpty(s)) return s; + return s.Replace("'", "'\\''"); + } + + public static void LogResponse( + int statusCode, + string statusText, + long responseTimeMs, + string body = "", + Dictionary headers = null) + { + if (_current == null) return; + var size = System.Text.Encoding.UTF8.GetByteCount(body ?? ""); + _current.HttpResponses.Add(new ResponsePayload + { + StatusCode = statusCode, + StatusText = statusText ?? "", + ResponseTimeMs = responseTimeMs.ToString(), + Headers = headers ?? new Dictionary(), + Body = body ?? "", + PayloadSize = $"{size} B" + }); + } + + public static void LogAssertion( + bool passed, + string name, + string expected = "", + string actual = "", + string type = "Assert") + { + if (_current == null) return; + _current.Assertions.Add(new AssertionPayload + { + Passed = passed, + Name = name ?? "", + Expected = expected ?? "", + Actual = actual ?? "", + AssertionType = type ?? "Assert" + }); + } + + /// Always call in a finally block at the end of every test method. + public static void Flush() + { + var block = _current; + _current = null; + if (block == null) return; + try + { + var json = JsonSerializer.Serialize(block, new JsonSerializerOptions + { + WriteIndented = false, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull + }); + Console.WriteLine("##TEST_REPORT_START##"); + Console.WriteLine(json); + Console.WriteLine("##TEST_REPORT_END##"); + } + catch + { + // Never throw from Flush — test result must not be affected + } + } + + // ── Payload POCOs ──────────────────────────────────────────────────────── + + private class TestBlock + { + [JsonPropertyName("assertions")] + public List Assertions { get; set; } = new(); + [JsonPropertyName("httpRequests")] + public List HttpRequests { get; set; } = new(); + [JsonPropertyName("httpResponses")] + public List HttpResponses { get; set; } = new(); + [JsonPropertyName("context")] + public ContextPayload Context { get; set; } + } + + private class AssertionPayload + { + [JsonPropertyName("passed")] public bool Passed { get; set; } + [JsonPropertyName("name")] public string Name { get; set; } + [JsonPropertyName("expected")] public string Expected { get; set; } + [JsonPropertyName("actual")] public string Actual { get; set; } + [JsonPropertyName("assertionType")] public string AssertionType { get; set; } + } + + private class RequestPayload + { + [JsonPropertyName("sdkMethod")] public string SdkMethod { get; set; } + [JsonPropertyName("httpMethod")] public string HttpMethod { get; set; } + [JsonPropertyName("requestUrl")] public string RequestUrl { get; set; } + [JsonPropertyName("queryParams")] public Dictionary QueryParams { get; set; } + [JsonPropertyName("headers")] public Dictionary Headers { get; set; } + [JsonPropertyName("body")] public string Body { get; set; } + [JsonPropertyName("curlCommand")] public string CurlCommand { get; set; } + } + + private class ResponsePayload + { + [JsonPropertyName("statusCode")] public int StatusCode { get; set; } + [JsonPropertyName("statusText")] public string StatusText { get; set; } + [JsonPropertyName("responseTimeMs")] public string ResponseTimeMs { get; set; } + [JsonPropertyName("headers")] public Dictionary Headers { get; set; } + [JsonPropertyName("body")] public string Body { get; set; } + [JsonPropertyName("payloadSize")] public string PayloadSize { get; set; } + } + + private class ContextPayload + { + [JsonPropertyName("environment")] public string Environment { get; set; } + [JsonPropertyName("sdkVersion")] public string SdkVersion { get; set; } + [JsonPropertyName("buildNumber")] public string BuildNumber { get; set; } + [JsonPropertyName("commitSha")] public string CommitSha { get; set; } + [JsonPropertyName("testDataSource")] public string TestDataSource { get; set; } + [JsonPropertyName("locale")] public string Locale { get; set; } + } + } +} diff --git a/Contentstack.Management.Core.Unit.Tests/Core/Services/Stack/BulkPublishServiceTest.cs b/Contentstack.Management.Core.Unit.Tests/Core/Services/Stack/BulkPublishServiceTest.cs index 19d2073..73284a2 100644 --- a/Contentstack.Management.Core.Unit.Tests/Core/Services/Stack/BulkPublishServiceTest.cs +++ b/Contentstack.Management.Core.Unit.Tests/Core/Services/Stack/BulkPublishServiceTest.cs @@ -48,25 +48,25 @@ public void Should_Create_Service_With_Valid_Parameters() } [TestMethod] - public void Should_Set_Skip_Workflow_Stage_Header_When_True() + public void Should_Set_Skip_Workflow_Stage_Query_Parameter_When_True() { var details = new BulkPublishDetails(); var service = new BulkPublishService(serializer, new Management.Core.Models.Stack(null), details, skipWorkflowStage: true); Assert.IsNotNull(service); - Assert.IsTrue(service.Headers.ContainsKey("skip_workflow_stage_check")); - Assert.AreEqual("true", service.Headers["skip_workflow_stage_check"]); + Assert.IsTrue(service.QueryResources.ContainsKey("skip_workflow_stage_check")); + Assert.AreEqual("true", service.QueryResources["skip_workflow_stage_check"]); } [TestMethod] - public void Should_Set_Approvals_Header_When_True() + public void Should_Set_Approvals_Query_Parameter_When_True() { var details = new BulkPublishDetails(); var service = new BulkPublishService(serializer, new Management.Core.Models.Stack(null), details, approvals: true); Assert.IsNotNull(service); - Assert.IsTrue(service.Headers.ContainsKey("approvals")); - Assert.AreEqual("true", service.Headers["approvals"]); + Assert.IsTrue(service.QueryResources.ContainsKey("approvals")); + Assert.AreEqual("true", service.QueryResources["approvals"]); } [TestMethod] diff --git a/Contentstack.Management.Core.Unit.Tests/Core/Services/Stack/BulkUnpublishServiceTest.cs b/Contentstack.Management.Core.Unit.Tests/Core/Services/Stack/BulkUnpublishServiceTest.cs index d6e0a65..ff9b709 100644 --- a/Contentstack.Management.Core.Unit.Tests/Core/Services/Stack/BulkUnpublishServiceTest.cs +++ b/Contentstack.Management.Core.Unit.Tests/Core/Services/Stack/BulkUnpublishServiceTest.cs @@ -48,25 +48,25 @@ public void Should_Create_Service_With_Valid_Parameters() } [TestMethod] - public void Should_Set_Skip_Workflow_Stage_Header_When_True() + public void Should_Set_Skip_Workflow_Stage_Query_Parameter_When_True() { var details = new BulkPublishDetails(); var service = new BulkUnpublishService(serializer, new Management.Core.Models.Stack(null), details, skipWorkflowStage: true); Assert.IsNotNull(service); - Assert.IsTrue(service.Headers.ContainsKey("skip_workflow_stage_check")); - Assert.AreEqual("true", service.Headers["skip_workflow_stage_check"]); + Assert.IsTrue(service.QueryResources.ContainsKey("skip_workflow_stage_check")); + Assert.AreEqual("true", service.QueryResources["skip_workflow_stage_check"]); } [TestMethod] - public void Should_Set_Approvals_Header_When_True() + public void Should_Set_Approvals_Query_Parameter_When_True() { var details = new BulkPublishDetails(); var service = new BulkUnpublishService(serializer, new Management.Core.Models.Stack(null), details, approvals: true); Assert.IsNotNull(service); - Assert.IsTrue(service.Headers.ContainsKey("approvals")); - Assert.AreEqual("true", service.Headers["approvals"]); + Assert.IsTrue(service.QueryResources.ContainsKey("approvals")); + Assert.AreEqual("true", service.QueryResources["approvals"]); } [TestMethod] diff --git a/Contentstack.Management.Core.Unit.Tests/Services/BulkOperationServicesTest.cs b/Contentstack.Management.Core.Unit.Tests/Services/BulkOperationServicesTest.cs index 01c6b51..f2ccf92 100644 --- a/Contentstack.Management.Core.Unit.Tests/Services/BulkOperationServicesTest.cs +++ b/Contentstack.Management.Core.Unit.Tests/Services/BulkOperationServicesTest.cs @@ -142,10 +142,10 @@ public void Test004_BulkPublishService_Initialization() Assert.IsNotNull(service); Assert.AreEqual("/bulk/publish", service.ResourcePath); Assert.AreEqual("POST", service.HttpMethod); - Assert.IsTrue(service.Headers.ContainsKey("skip_workflow_stage_check")); - Assert.IsTrue(service.Headers.ContainsKey("approvals")); - Assert.AreEqual("true", service.Headers["skip_workflow_stage_check"]); - Assert.AreEqual("true", service.Headers["approvals"]); + Assert.IsTrue(service.QueryResources.ContainsKey("skip_workflow_stage_check")); + Assert.IsTrue(service.QueryResources.ContainsKey("approvals")); + Assert.AreEqual("true", service.QueryResources["skip_workflow_stage_check"]); + Assert.AreEqual("true", service.QueryResources["approvals"]); } [TestMethod] @@ -197,10 +197,10 @@ public void Test006_BulkPublishService_With_All_Flags() var service = new BulkPublishService(_serializer, _stack, publishDetails, true, true, true); Assert.IsNotNull(service); - Assert.IsTrue(service.Headers.ContainsKey("skip_workflow_stage_check")); - Assert.IsTrue(service.Headers.ContainsKey("approvals")); - Assert.AreEqual("true", service.Headers["skip_workflow_stage_check"]); - Assert.AreEqual("true", service.Headers["approvals"]); + Assert.IsTrue(service.QueryResources.ContainsKey("skip_workflow_stage_check")); + Assert.IsTrue(service.QueryResources.ContainsKey("approvals")); + Assert.AreEqual("true", service.QueryResources["skip_workflow_stage_check"]); + Assert.AreEqual("true", service.QueryResources["approvals"]); } [TestMethod] @@ -218,8 +218,8 @@ public void Test007_BulkPublishService_Without_Flags() var service = new BulkPublishService(_serializer, _stack, publishDetails, false, false, false); Assert.IsNotNull(service); - Assert.IsFalse(service.Headers.ContainsKey("skip_workflow_stage_check")); - Assert.IsFalse(service.Headers.ContainsKey("approvals")); + Assert.IsFalse(service.QueryResources.ContainsKey("skip_workflow_stage_check")); + Assert.IsFalse(service.QueryResources.ContainsKey("approvals")); } [TestMethod] @@ -248,8 +248,8 @@ public void Test008_BulkUnpublishService_Initialization() Assert.IsNotNull(service); Assert.AreEqual("/bulk/unpublish", service.ResourcePath); Assert.AreEqual("POST", service.HttpMethod); - Assert.IsTrue(service.Headers.ContainsKey("skip_workflow_stage_check")); - Assert.IsTrue(service.Headers.ContainsKey("approvals")); + Assert.IsTrue(service.QueryResources.ContainsKey("skip_workflow_stage_check")); + Assert.IsTrue(service.QueryResources.ContainsKey("approvals")); } [TestMethod] diff --git a/Contentstack.Management.Core/Models/Workflow.cs b/Contentstack.Management.Core/Models/Workflow.cs index 797869f..9647670 100644 --- a/Contentstack.Management.Core/Models/Workflow.cs +++ b/Contentstack.Management.Core/Models/Workflow.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Threading.Tasks; using Contentstack.Management.Core.Queryable; using Contentstack.Management.Core.Services.Models; @@ -9,7 +9,7 @@ namespace Contentstack.Management.Core.Models public class Workflow: BaseModel { internal Workflow(Stack stack, string uid) - : base(stack, "workflows", uid) + : base(stack, "workflow", uid) { resourcePath = uid == null ? "/workflows" : $"/workflows/{uid}"; } diff --git a/Contentstack.Management.Core/Models/WorkflowModel.cs b/Contentstack.Management.Core/Models/WorkflowModel.cs index 0803c52..e7fa086 100644 --- a/Contentstack.Management.Core/Models/WorkflowModel.cs +++ b/Contentstack.Management.Core/Models/WorkflowModel.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using Newtonsoft.Json; namespace Contentstack.Management.Core.Models { @@ -37,7 +37,7 @@ public class WorkflowStage public bool AllUsers { get; set; } = true; [JsonProperty(propertyName: "specificStages")] public bool SpecificStages { get; set; } = false; - [JsonProperty(propertyName: "enabspecificUsersled")] + [JsonProperty(propertyName: "specificUsers")] public bool SpecificUsers { get; set; } = false; [JsonProperty(propertyName: "entry_lock")] public string EntryLock { get; set; } diff --git a/Contentstack.Management.Core/Services/Stack/BulkOperation/BulkPublishService.cs b/Contentstack.Management.Core/Services/Stack/BulkOperation/BulkPublishService.cs index 1aa6c87..5bf5a19 100644 --- a/Contentstack.Management.Core/Services/Stack/BulkOperation/BulkPublishService.cs +++ b/Contentstack.Management.Core/Services/Stack/BulkOperation/BulkPublishService.cs @@ -35,15 +35,15 @@ public BulkPublishService(JsonSerializer serializer, Contentstack.Management.Cor ResourcePath = "/bulk/publish"; HttpMethod = "POST"; - // Set headers based on parameters + // Set query parameters based on options if (_skipWorkflowStage) { - Headers["skip_workflow_stage_check"] = "true"; + AddQueryResource("skip_workflow_stage_check", "true"); } if (_approvals) { - Headers["approvals"] = "true"; + AddQueryResource("approvals", "true"); } if (_isNested) diff --git a/Contentstack.Management.Core/Services/Stack/BulkOperation/BulkUnpublishService.cs b/Contentstack.Management.Core/Services/Stack/BulkOperation/BulkUnpublishService.cs index 8d9689d..0993409 100644 --- a/Contentstack.Management.Core/Services/Stack/BulkOperation/BulkUnpublishService.cs +++ b/Contentstack.Management.Core/Services/Stack/BulkOperation/BulkUnpublishService.cs @@ -38,12 +38,12 @@ public BulkUnpublishService(JsonSerializer serializer, Contentstack.Management.C // Set headers based on parameters if (_skipWorkflowStage) { - Headers["skip_workflow_stage_check"] = "true"; + AddQueryResource("skip_workflow_stage_check", "true"); } if (_approvals) { - Headers["approvals"] = "true"; + AddQueryResource("approvals", "true"); } if (_isNested) diff --git a/Contentstack.Management.Core/contentstack.management.core.csproj b/Contentstack.Management.Core/contentstack.management.core.csproj index 32cb066..1de49e1 100644 --- a/Contentstack.Management.Core/contentstack.management.core.csproj +++ b/Contentstack.Management.Core/contentstack.management.core.csproj @@ -1,7 +1,9 @@ - netstandard2.0;net471;net472; + + netstandard2.0;net471;net472 + netstandard2.0 8.0 enable Contentstack Management diff --git a/Directory.Build.props b/Directory.Build.props index 735f780..d79e191 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,5 +1,5 @@ - 0.6.1 + 0.7.0 diff --git a/Scripts/run-test-case.sh b/Scripts/run-test-case.sh old mode 100644 new mode 100755 index a1d47c4..fa43d0d --- a/Scripts/run-test-case.sh +++ b/Scripts/run-test-case.sh @@ -19,6 +19,12 @@ DATE=$(date +'%d-%b-%Y') FILE_NAME="Contentstack-DotNet-Test-Case-$DATE" +SDK_VERSION=$(grep -m1 '' Contentstack.Management.Core/contentstack.management.core.csproj | sed 's/.*\(.*\)<\/Version>.*/\1/' | tr -d '[:space:]') +export SDK_VERSION="${SDK_VERSION:-unknown}" +export BUILD_NUMBER="${BUILD_NUMBER:-local}" +export COMMIT_SHA=$(git rev-parse --short HEAD 2>/dev/null || echo "unknown") +export TEST_ENV="${TEST_ENV:-integration}" + echo "Running test case..." dotnet test --logger "trx;LogFileName=Report-$FILE_NAME.trx" --collect:"XPlat code coverage" @@ -34,3 +40,18 @@ do done echo "Code coverage report generate." + +echo "Generating API surface (method coverage)..." +dotnet run --project tools/ApiSurface/ApiSurface.csproj -- --output "tools/ApiSurface/api-surface.json" 2>/dev/null || true + +echo "Generating enhanced test report..." +mkdir -p TestResults +dotnet run --project tools/EnhancedTestReport/EnhancedTestReport.csproj -- \ + --trx-dir "Contentstack.Management.Core.Unit.Tests/TestResults" \ + --trx-dir "Contentstack.Management.Core.Tests/TestResults" \ + --cobertura-dir "Contentstack.Management.Core.Unit.Tests/TestResults" \ + --cobertura-dir "Contentstack.Management.Core.Tests/TestResults" \ + --api-surface "tools/ApiSurface/api-surface.json" \ + --js-api "tools/js-cma-api.json" \ + --output "TestResults/EnhancedReport-$FILE_NAME.html" +echo "Enhanced report written to TestResults/EnhancedReport-$FILE_NAME.html" diff --git a/Scripts/run-unit-test-case.sh b/Scripts/run-unit-test-case.sh index ba41e6c..8df60b9 100644 --- a/Scripts/run-unit-test-case.sh +++ b/Scripts/run-unit-test-case.sh @@ -14,4 +14,11 @@ FILE_NAME="Contentstack-DotNet-Test-Case" echo "Running test case..." dotnet test "Contentstack.Management.Core.Unit.Tests/Contentstack.Management.Core.Unit.Tests.csproj" --logger "trx;LogFileName=Report-$FILE_NAME.trx" --collect:"XPlat code coverage" -echo "Test case Completed..." +echo "Test case Completed..." + +echo "Generating enhanced test report..." +dotnet run --project tools/EnhancedTestReport/EnhancedTestReport.csproj -- \ + --trx-dir "Contentstack.Management.Core.Unit.Tests/TestResults" \ + --cobertura-dir "Contentstack.Management.Core.Unit.Tests/TestResults" \ + --output "Contentstack.Management.Core.Unit.Tests/TestResults/EnhancedReport-$FILE_NAME.html" +echo "Enhanced report written to Contentstack.Management.Core.Unit.Tests/TestResults/EnhancedReport-$FILE_NAME.html" diff --git a/tools/ApiSurface/ApiSurface.csproj b/tools/ApiSurface/ApiSurface.csproj new file mode 100644 index 0000000..0b04e03 --- /dev/null +++ b/tools/ApiSurface/ApiSurface.csproj @@ -0,0 +1,15 @@ + + + + Exe + net7.0 + ApiSurface + enable + enable + + + + + + + diff --git a/tools/ApiSurface/Program.cs b/tools/ApiSurface/Program.cs new file mode 100644 index 0000000..1d338d1 --- /dev/null +++ b/tools/ApiSurface/Program.cs @@ -0,0 +1,108 @@ +using System.Reflection; +using System.Text.Json; +using Contentstack.Management.Core.Models; +using Contentstack.Management.Core.Models.Token; + +namespace ApiSurface; + +static class Program +{ + static int Main(string[] args) + { + string? outputPath = null; + for (var i = 0; i < args.Length; i++) + { + if ((args[i] == "--output" || args[i] == "-o") && i + 1 < args.Length) + { + outputPath = args[++i]; + break; + } + } + outputPath ??= "api-surface.json"; + + var assembly = typeof(Stack).Assembly; + var apiSurface = CollectApiSurface(assembly); + var json = JsonSerializer.Serialize(apiSurface, new JsonSerializerOptions { WriteIndented = true }); + var dir = Path.GetDirectoryName(outputPath); + if (!string.IsNullOrEmpty(dir)) + Directory.CreateDirectory(dir); + File.WriteAllText(outputPath, json); + Console.WriteLine($"api-surface.json written: {Path.GetFullPath(outputPath)} ({apiSurface.Count} methods)"); + return 0; + } + + static List CollectApiSurface(Assembly assembly) + { + var list = new List(); + var responseType = typeof(Contentstack.Management.Core.ContentstackResponse); + var taskType = typeof(Task); + + var typesToScan = new[] + { + typeof(Stack), + typeof(ContentType), + typeof(Asset), + typeof(BulkOperation), + typeof(Locale), + typeof(GlobalField), + typeof(Release), + typeof(ReleaseItem), + typeof(Organization), + typeof(Contentstack.Management.Core.Models.Environment), + typeof(Folder), + typeof(Entry), + typeof(Webhook), + typeof(Workflow), + typeof(Role), + typeof(Label), + typeof(Contentstack.Management.Core.Models.Version), + typeof(User), + typeof(AuditLog), + typeof(VariantGroup), + typeof(Extension), + typeof(PublishRule), + typeof(PublishQueue), + typeof(DeliveryToken), + typeof(ManagementToken), + }; + + foreach (var type in typesToScan) + { + var component = type.Name; + var methods = type.GetMethods(BindingFlags.Public | BindingFlags.Instance) + .Where(m => !m.IsSpecialName) // skip get_*, set_* + .Where(m => + { + var ret = m.ReturnType; + if (ret == responseType) return true; + if (ret.IsGenericType && ret.GetGenericTypeDefinition() == taskType) + { + var arg = ret.GetGenericArguments()[0]; + return arg == responseType; + } + return false; + }); + + foreach (var m in methods.OrderBy(x => x.Name)) + { + var methodName = m.Name; + var key = $"{component}.{methodName}"; + list.Add(new ApiSurfaceEntry + { + Component = component, + Method = methodName, + Key = key + }); + } + } + + return list; + } + + private sealed class ApiSurfaceEntry + { + public string Component { get; set; } = ""; + public string Method { get; set; } = ""; + public string Key { get; set; } = ""; + } +} diff --git a/tools/ApiSurface/api-surface.json b/tools/ApiSurface/api-surface.json new file mode 100644 index 0000000..df2f66d --- /dev/null +++ b/tools/ApiSurface/api-surface.json @@ -0,0 +1,682 @@ +[ + { + "Component": "Stack", + "Method": "AddSettings", + "Key": "Stack.AddSettings" + }, + { + "Component": "Stack", + "Method": "Create", + "Key": "Stack.Create" + }, + { + "Component": "Stack", + "Method": "Fetch", + "Key": "Stack.Fetch" + }, + { + "Component": "Stack", + "Method": "GetAll", + "Key": "Stack.GetAll" + }, + { + "Component": "Stack", + "Method": "ResetSettings", + "Key": "Stack.ResetSettings" + }, + { + "Component": "Stack", + "Method": "Settings", + "Key": "Stack.Settings" + }, + { + "Component": "Stack", + "Method": "Share", + "Key": "Stack.Share" + }, + { + "Component": "Stack", + "Method": "TransferOwnership", + "Key": "Stack.TransferOwnership" + }, + { + "Component": "Stack", + "Method": "UnShare", + "Key": "Stack.UnShare" + }, + { + "Component": "Stack", + "Method": "Update", + "Key": "Stack.Update" + }, + { + "Component": "Stack", + "Method": "UpdateUserRole", + "Key": "Stack.UpdateUserRole" + }, + { + "Component": "ContentType", + "Method": "Create", + "Key": "ContentType.Create" + }, + { + "Component": "ContentType", + "Method": "Delete", + "Key": "ContentType.Delete" + }, + { + "Component": "ContentType", + "Method": "Fetch", + "Key": "ContentType.Fetch" + }, + { + "Component": "ContentType", + "Method": "Update", + "Key": "ContentType.Update" + }, + { + "Component": "Asset", + "Method": "Create", + "Key": "Asset.Create" + }, + { + "Component": "Asset", + "Method": "Delete", + "Key": "Asset.Delete" + }, + { + "Component": "Asset", + "Method": "Fetch", + "Key": "Asset.Fetch" + }, + { + "Component": "Asset", + "Method": "Publish", + "Key": "Asset.Publish" + }, + { + "Component": "Asset", + "Method": "References", + "Key": "Asset.References" + }, + { + "Component": "Asset", + "Method": "Unpublish", + "Key": "Asset.Unpublish" + }, + { + "Component": "Asset", + "Method": "Update", + "Key": "Asset.Update" + }, + { + "Component": "BulkOperation", + "Method": "AddItems", + "Key": "BulkOperation.AddItems" + }, + { + "Component": "BulkOperation", + "Method": "AddItemsWithDeployment", + "Key": "BulkOperation.AddItemsWithDeployment" + }, + { + "Component": "BulkOperation", + "Method": "Delete", + "Key": "BulkOperation.Delete" + }, + { + "Component": "BulkOperation", + "Method": "JobStatus", + "Key": "BulkOperation.JobStatus" + }, + { + "Component": "BulkOperation", + "Method": "Publish", + "Key": "BulkOperation.Publish" + }, + { + "Component": "BulkOperation", + "Method": "ReleaseItems", + "Key": "BulkOperation.ReleaseItems" + }, + { + "Component": "BulkOperation", + "Method": "Unpublish", + "Key": "BulkOperation.Unpublish" + }, + { + "Component": "BulkOperation", + "Method": "Update", + "Key": "BulkOperation.Update" + }, + { + "Component": "BulkOperation", + "Method": "UpdateItems", + "Key": "BulkOperation.UpdateItems" + }, + { + "Component": "BulkOperation", + "Method": "UpdateItemsWithDeployment", + "Key": "BulkOperation.UpdateItemsWithDeployment" + }, + { + "Component": "Locale", + "Method": "Create", + "Key": "Locale.Create" + }, + { + "Component": "Locale", + "Method": "Delete", + "Key": "Locale.Delete" + }, + { + "Component": "Locale", + "Method": "Fetch", + "Key": "Locale.Fetch" + }, + { + "Component": "Locale", + "Method": "Update", + "Key": "Locale.Update" + }, + { + "Component": "GlobalField", + "Method": "Create", + "Key": "GlobalField.Create" + }, + { + "Component": "GlobalField", + "Method": "Delete", + "Key": "GlobalField.Delete" + }, + { + "Component": "GlobalField", + "Method": "Fetch", + "Key": "GlobalField.Fetch" + }, + { + "Component": "GlobalField", + "Method": "Update", + "Key": "GlobalField.Update" + }, + { + "Component": "Release", + "Method": "Clone", + "Key": "Release.Clone" + }, + { + "Component": "Release", + "Method": "Create", + "Key": "Release.Create" + }, + { + "Component": "Release", + "Method": "Delete", + "Key": "Release.Delete" + }, + { + "Component": "Release", + "Method": "Deploy", + "Key": "Release.Deploy" + }, + { + "Component": "Release", + "Method": "Fetch", + "Key": "Release.Fetch" + }, + { + "Component": "Release", + "Method": "Update", + "Key": "Release.Update" + }, + { + "Component": "ReleaseItem", + "Method": "Create", + "Key": "ReleaseItem.Create" + }, + { + "Component": "ReleaseItem", + "Method": "CreateMultiple", + "Key": "ReleaseItem.CreateMultiple" + }, + { + "Component": "ReleaseItem", + "Method": "Delete", + "Key": "ReleaseItem.Delete" + }, + { + "Component": "ReleaseItem", + "Method": "GetAll", + "Key": "ReleaseItem.GetAll" + }, + { + "Component": "ReleaseItem", + "Method": "UpdateReleaseItem", + "Key": "ReleaseItem.UpdateReleaseItem" + }, + { + "Component": "Organization", + "Method": "AddUser", + "Key": "Organization.AddUser" + }, + { + "Component": "Organization", + "Method": "GetInvitations", + "Key": "Organization.GetInvitations" + }, + { + "Component": "Organization", + "Method": "GetOrganizations", + "Key": "Organization.GetOrganizations" + }, + { + "Component": "Organization", + "Method": "GetStacks", + "Key": "Organization.GetStacks" + }, + { + "Component": "Organization", + "Method": "RemoveUser", + "Key": "Organization.RemoveUser" + }, + { + "Component": "Organization", + "Method": "ResendInvitation", + "Key": "Organization.ResendInvitation" + }, + { + "Component": "Organization", + "Method": "Roles", + "Key": "Organization.Roles" + }, + { + "Component": "Organization", + "Method": "TransferOwnership", + "Key": "Organization.TransferOwnership" + }, + { + "Component": "Environment", + "Method": "Create", + "Key": "Environment.Create" + }, + { + "Component": "Environment", + "Method": "Delete", + "Key": "Environment.Delete" + }, + { + "Component": "Environment", + "Method": "Fetch", + "Key": "Environment.Fetch" + }, + { + "Component": "Environment", + "Method": "Update", + "Key": "Environment.Update" + }, + { + "Component": "Folder", + "Method": "Create", + "Key": "Folder.Create" + }, + { + "Component": "Folder", + "Method": "Delete", + "Key": "Folder.Delete" + }, + { + "Component": "Folder", + "Method": "Fetch", + "Key": "Folder.Fetch" + }, + { + "Component": "Folder", + "Method": "Update", + "Key": "Folder.Update" + }, + { + "Component": "Entry", + "Method": "Create", + "Key": "Entry.Create" + }, + { + "Component": "Entry", + "Method": "Delete", + "Key": "Entry.Delete" + }, + { + "Component": "Entry", + "Method": "DeleteMultipleLocal", + "Key": "Entry.DeleteMultipleLocal" + }, + { + "Component": "Entry", + "Method": "Export", + "Key": "Entry.Export" + }, + { + "Component": "Entry", + "Method": "Fetch", + "Key": "Entry.Fetch" + }, + { + "Component": "Entry", + "Method": "Import", + "Key": "Entry.Import" + }, + { + "Component": "Entry", + "Method": "Locales", + "Key": "Entry.Locales" + }, + { + "Component": "Entry", + "Method": "Localize", + "Key": "Entry.Localize" + }, + { + "Component": "Entry", + "Method": "Publish", + "Key": "Entry.Publish" + }, + { + "Component": "Entry", + "Method": "PublishRequest", + "Key": "Entry.PublishRequest" + }, + { + "Component": "Entry", + "Method": "References", + "Key": "Entry.References" + }, + { + "Component": "Entry", + "Method": "SetWorkflow", + "Key": "Entry.SetWorkflow" + }, + { + "Component": "Entry", + "Method": "Unlocalize", + "Key": "Entry.Unlocalize" + }, + { + "Component": "Entry", + "Method": "Unpublish", + "Key": "Entry.Unpublish" + }, + { + "Component": "Entry", + "Method": "Update", + "Key": "Entry.Update" + }, + { + "Component": "Webhook", + "Method": "Create", + "Key": "Webhook.Create" + }, + { + "Component": "Webhook", + "Method": "Delete", + "Key": "Webhook.Delete" + }, + { + "Component": "Webhook", + "Method": "Executions", + "Key": "Webhook.Executions" + }, + { + "Component": "Webhook", + "Method": "Fetch", + "Key": "Webhook.Fetch" + }, + { + "Component": "Webhook", + "Method": "Logs", + "Key": "Webhook.Logs" + }, + { + "Component": "Webhook", + "Method": "Retry", + "Key": "Webhook.Retry" + }, + { + "Component": "Webhook", + "Method": "Update", + "Key": "Webhook.Update" + }, + { + "Component": "Workflow", + "Method": "Create", + "Key": "Workflow.Create" + }, + { + "Component": "Workflow", + "Method": "Delete", + "Key": "Workflow.Delete" + }, + { + "Component": "Workflow", + "Method": "Disable", + "Key": "Workflow.Disable" + }, + { + "Component": "Workflow", + "Method": "Enable", + "Key": "Workflow.Enable" + }, + { + "Component": "Workflow", + "Method": "Fetch", + "Key": "Workflow.Fetch" + }, + { + "Component": "Workflow", + "Method": "FindAll", + "Key": "Workflow.FindAll" + }, + { + "Component": "Workflow", + "Method": "GetPublishRule", + "Key": "Workflow.GetPublishRule" + }, + { + "Component": "Workflow", + "Method": "Update", + "Key": "Workflow.Update" + }, + { + "Component": "Role", + "Method": "Create", + "Key": "Role.Create" + }, + { + "Component": "Role", + "Method": "Delete", + "Key": "Role.Delete" + }, + { + "Component": "Role", + "Method": "Fetch", + "Key": "Role.Fetch" + }, + { + "Component": "Role", + "Method": "Update", + "Key": "Role.Update" + }, + { + "Component": "Label", + "Method": "Create", + "Key": "Label.Create" + }, + { + "Component": "Label", + "Method": "Delete", + "Key": "Label.Delete" + }, + { + "Component": "Label", + "Method": "Fetch", + "Key": "Label.Fetch" + }, + { + "Component": "Label", + "Method": "Update", + "Key": "Label.Update" + }, + { + "Component": "Version", + "Method": "Delete", + "Key": "Version.Delete" + }, + { + "Component": "Version", + "Method": "GetAll", + "Key": "Version.GetAll" + }, + { + "Component": "Version", + "Method": "SetName", + "Key": "Version.SetName" + }, + { + "Component": "User", + "Method": "ForgotPassword", + "Key": "User.ForgotPassword" + }, + { + "Component": "User", + "Method": "ResetPassword", + "Key": "User.ResetPassword" + }, + { + "Component": "AuditLog", + "Method": "Fetch", + "Key": "AuditLog.Fetch" + }, + { + "Component": "AuditLog", + "Method": "FindAll", + "Key": "AuditLog.FindAll" + }, + { + "Component": "VariantGroup", + "Method": "Find", + "Key": "VariantGroup.Find" + }, + { + "Component": "VariantGroup", + "Method": "LinkContentTypes", + "Key": "VariantGroup.LinkContentTypes" + }, + { + "Component": "VariantGroup", + "Method": "UnlinkContentTypes", + "Key": "VariantGroup.UnlinkContentTypes" + }, + { + "Component": "Extension", + "Method": "Create", + "Key": "Extension.Create" + }, + { + "Component": "Extension", + "Method": "Delete", + "Key": "Extension.Delete" + }, + { + "Component": "Extension", + "Method": "Fetch", + "Key": "Extension.Fetch" + }, + { + "Component": "Extension", + "Method": "Update", + "Key": "Extension.Update" + }, + { + "Component": "Extension", + "Method": "Upload", + "Key": "Extension.Upload" + }, + { + "Component": "PublishRule", + "Method": "Create", + "Key": "PublishRule.Create" + }, + { + "Component": "PublishRule", + "Method": "Delete", + "Key": "PublishRule.Delete" + }, + { + "Component": "PublishRule", + "Method": "Fetch", + "Key": "PublishRule.Fetch" + }, + { + "Component": "PublishRule", + "Method": "FindAll", + "Key": "PublishRule.FindAll" + }, + { + "Component": "PublishRule", + "Method": "Update", + "Key": "PublishRule.Update" + }, + { + "Component": "PublishQueue", + "Method": "Cancel", + "Key": "PublishQueue.Cancel" + }, + { + "Component": "PublishQueue", + "Method": "Fetch", + "Key": "PublishQueue.Fetch" + }, + { + "Component": "PublishQueue", + "Method": "FindAll", + "Key": "PublishQueue.FindAll" + }, + { + "Component": "DeliveryToken", + "Method": "Create", + "Key": "DeliveryToken.Create" + }, + { + "Component": "DeliveryToken", + "Method": "Delete", + "Key": "DeliveryToken.Delete" + }, + { + "Component": "DeliveryToken", + "Method": "Fetch", + "Key": "DeliveryToken.Fetch" + }, + { + "Component": "DeliveryToken", + "Method": "Update", + "Key": "DeliveryToken.Update" + }, + { + "Component": "ManagementToken", + "Method": "Create", + "Key": "ManagementToken.Create" + }, + { + "Component": "ManagementToken", + "Method": "Delete", + "Key": "ManagementToken.Delete" + }, + { + "Component": "ManagementToken", + "Method": "Fetch", + "Key": "ManagementToken.Fetch" + }, + { + "Component": "ManagementToken", + "Method": "Update", + "Key": "ManagementToken.Update" + } +] \ No newline at end of file diff --git a/tools/EnhancedTestReport/EnhancedTestReport.csproj b/tools/EnhancedTestReport/EnhancedTestReport.csproj new file mode 100644 index 0000000..02cf7a0 --- /dev/null +++ b/tools/EnhancedTestReport/EnhancedTestReport.csproj @@ -0,0 +1,12 @@ + + + + Exe + net7.0 + EnhancedTestReport + EnhancedTestReport + enable + enable + + + diff --git a/tools/EnhancedTestReport/Program.cs b/tools/EnhancedTestReport/Program.cs new file mode 100644 index 0000000..590c4b9 --- /dev/null +++ b/tools/EnhancedTestReport/Program.cs @@ -0,0 +1,1120 @@ +using System.Globalization; +using System.Linq; +using System.Text.Json; +using System.Text.RegularExpressions; +using System.Xml.Linq; + +namespace EnhancedTestReport; + +static class Program +{ + static int Main(string[] args) + { + var trxPaths = new List(); + var coberturaPaths = new List(); + string? outputPath = null; + string? trxDir = null; + string? coberturaDir = null; + + for (var i = 0; i < args.Length; i++) + { + switch (args[i]) + { + case "--trx" when i + 1 < args.Length: + trxPaths.Add(args[++i]); + break; + case "--trx-dir" when i + 1 < args.Length: + trxDir = args[++i]; + break; + case "--cobertura" when i + 1 < args.Length: + coberturaPaths.Add(args[++i]); + break; + case "--cobertura-dir" when i + 1 < args.Length: + coberturaDir = args[++i]; + break; + case "--output" when i + 1 < args.Length: + outputPath = args[++i]; + break; + case "--help": + case "-h": + PrintUsage(); + return 0; + } + } + + if (trxDir != null) + trxPaths.AddRange(GlobFiles(trxDir, "*.trx")); + if (coberturaDir != null) + coberturaPaths.AddRange(GlobFiles(coberturaDir, "coverage.cobertura.xml")); + + trxPaths = trxPaths.Where(File.Exists).Distinct().ToList(); + coberturaPaths = coberturaPaths.Where(File.Exists).Distinct().ToList(); + + if (trxPaths.Count == 0 && coberturaPaths.Count == 0) + { + Console.Error.WriteLine("No TRX or Cobertura files found. Use --trx, --trx-dir, --cobertura, or --cobertura-dir."); + return 1; + } + + outputPath ??= "EnhancedTestReport.html"; + + var testData = ParseTrxFiles(trxPaths); + EnrichResults(testData); + var coverageData = ParseCoberturaFiles(coberturaPaths); + var html = GenerateHtml(testData, coverageData); + var dir = Path.GetDirectoryName(outputPath); + if (!string.IsNullOrEmpty(dir)) + Directory.CreateDirectory(dir); + File.WriteAllText(outputPath, html); + Console.WriteLine($"Report written to {Path.GetFullPath(outputPath)}"); + return 0; + } + + static void PrintUsage() + { + Console.WriteLine(@"EnhancedTestReport - Generate HTML test report from TRX and Cobertura. + +Usage: + EnhancedTestReport [options] + +Options: + --trx Add a TRX file (can be repeated). + --trx-dir Glob *.trx in directory (recursive). + --cobertura Add a Cobertura XML file (can be repeated). + --cobertura-dir Glob coverage.cobertura.xml in directory (recursive). + --output Output HTML path (default: EnhancedTestReport.html). + --help, -h Show this help. +"); + } + + static List GlobFiles(string dir, string pattern) + { + if (!Directory.Exists(dir)) return new List(); + var list = new List(); + foreach (var f in Directory.GetFiles(dir, pattern, SearchOption.AllDirectories)) + list.Add(Path.GetFullPath(f)); + return list; + } + + static TestReportData ParseTrxFiles(List paths) + { + var results = new List(); + int total = 0, passed = 0, failed = 0, skipped = 0; + var totalDuration = TimeSpan.Zero; + var byAssembly = new Dictionary>(StringComparer.OrdinalIgnoreCase); + var byClass = new Dictionary>(StringComparer.OrdinalIgnoreCase); + + foreach (var path in paths) + { + try + { + var doc = XDocument.Load(path); + var ns = doc.Root?.Name.Namespace ?? XNamespace.None; + + // Build testId → className (short name) from TestDefinitions + var testIdToClassName = new Dictionary(StringComparer.OrdinalIgnoreCase); + foreach (var ut in doc.Descendants().Where(e => e.Name.LocalName == "UnitTest")) + { + var id = (string?)ut.Attribute("id"); + var testMethod = ut.Descendants().FirstOrDefault(e => e.Name.LocalName == "TestMethod"); + var fullClass = (string?)testMethod?.Attribute("className"); + if (!string.IsNullOrEmpty(id) && !string.IsNullOrEmpty(fullClass)) + { + var shortName = fullClass.Split('.').LastOrDefault() ?? fullClass; + testIdToClassName[id] = shortName; + } + } + + foreach (var er in doc.Descendants().Where(e => e.Name.LocalName == "UnitTestResult")) + { + var outcome = (string?)er.Attribute("outcome") ?? ""; + var testId = (string?)er.Attribute("testId") ?? ""; + var testName = (string?)er.Attribute("testName") ?? testId; + var durationStr = (string?)er.Attribute("duration"); + TimeSpan duration = TimeSpan.Zero; + if (!string.IsNullOrEmpty(durationStr)) + TimeSpan.TryParse(durationStr, CultureInfo.InvariantCulture, out duration); + + var output = er.Element(ns + "Output"); + var err = output?.Element(ns + "ErrorInfo"); + var message = (string?)err?.Element(ns + "Message")?.Value ?? ""; + var stack = (string?)err?.Element(ns + "StackTrace")?.Value ?? ""; + var stdOut = (string?)output?.Element(ns + "StdOut")?.Value ?? ""; + var stdErr = (string?)output?.Element(ns + "StdErr")?.Value ?? ""; + var debugTrace = (string?)output?.Element(ns + "DebugTrace")?.Value ?? ""; + + var assembly = Path.GetFileNameWithoutExtension(path).Replace("Report-", "").Replace(".trx", ""); + if (string.IsNullOrEmpty(assembly)) assembly = "Tests"; + + var className = testIdToClassName.TryGetValue(testId, out var cn) ? cn : ""; + + var r = new UnitTestResult + { + TestName = testName, + ClassName = className, + Outcome = outcome, + Duration = duration, + Message = message, + StackTrace = stack, + StdOut = stdOut?.Trim() ?? "", + StdErr = stdErr?.Trim() ?? "", + DebugTrace = debugTrace?.Trim() ?? "", + Assembly = assembly + }; + results.Add(r); + total++; + totalDuration += duration; + if (outcome.Equals("Passed", StringComparison.OrdinalIgnoreCase)) passed++; + else if (outcome.Equals("Failed", StringComparison.OrdinalIgnoreCase)) failed++; + else skipped++; + + if (!byAssembly.TryGetValue(assembly, out var list)) + { + list = new List(); + byAssembly[assembly] = list; + } + list.Add(r); + + var classKey = string.IsNullOrEmpty(className) ? "(Unknown)" : className; + if (!byClass.TryGetValue(classKey, out var classList)) + { + classList = new List(); + byClass[classKey] = classList; + } + classList.Add(r); + } + + var counters = doc.Descendants().FirstOrDefault(e => e.Name.LocalName == "Counters"); + if (counters != null && total == 0) + { + total = GetIntAttr(counters, "total", 0); + passed = GetIntAttr(counters, "passed", 0); + failed = GetIntAttr(counters, "failed", 0); + } + } + catch (Exception ex) + { + Console.Error.WriteLine($"Error reading {path}: {ex.Message}"); + } + } + + if (results.Count > 0 && total == 0) + { + total = results.Count; + passed = results.Count(r => r.Outcome.Equals("Passed", StringComparison.OrdinalIgnoreCase)); + failed = results.Count(r => r.Outcome.Equals("Failed", StringComparison.OrdinalIgnoreCase)); + skipped = total - passed - failed; + } + + return new TestReportData + { + Results = results, + ByAssembly = byAssembly, + ByClass = byClass, + Total = total, + Passed = passed, + Failed = failed, + Skipped = skipped, + TotalDuration = totalDuration + }; + } + + static int GetIntAttr(XElement el, string localName, int defaultValue) + { + var a = el.Attributes().FirstOrDefault(x => x.Name.LocalName == localName); + return a != null && int.TryParse(a.Value, out var v) ? v : defaultValue; + } + + static CoverageReportData ParseCoberturaFiles(List paths) + { + var files = new List(); + double sumStmts = 0, sumBranch = 0, sumFuncs = 0, sumLines = 0; + int countStmts = 0, countBranch = 0, countFuncs = 0, countLines = 0; + + foreach (var path in paths) + { + try + { + var doc = XDocument.Load(path); + foreach (var package in doc.Descendants().Where(e => e.Name.LocalName == "package")) + { + var pkgName = (string?)package.Attribute("name") ?? ""; + foreach (var classEl in package.Descendants().Where(e => e.Name.LocalName == "class")) + { + var fileName = (string?)classEl.Attribute("filename") ?? (string?)classEl.Attribute("name") ?? ""; + var lineRate = GetDoubleAttr(classEl, "line-rate", 0); + var branchRate = GetDoubleAttr(classEl, "branch-rate", 0); + + var methods = classEl.Descendants().Where(x => x.Name.LocalName == "method").ToList(); + var methodCount = methods.Count; + var methodCovered = methods.Count(m => GetDoubleAttr(m, "line-rate", 0) > 0); + var funcRate = methodCount > 0 ? (double)methodCovered / methodCount : 1.0; + + var lines = classEl.Descendants().Where(x => x.Name.LocalName == "line").ToList(); + var uncovered = new List(); + foreach (var line in lines) + { + var num = GetIntAttr(line, "number", 0); + var hits = GetIntAttr(line, "hits", 0); + if (num > 0 && hits == 0) + uncovered.Add(num); + } + uncovered.Sort(); + var uncoveredStr = FormatUncoveredLines(uncovered); + + var displayName = string.IsNullOrEmpty(fileName) ? (string?)classEl.Attribute("name") ?? "?" : fileName; + if (!string.IsNullOrEmpty(pkgName) && !displayName.StartsWith(pkgName, StringComparison.Ordinal)) + displayName = pkgName + "/" + displayName.TrimStart('/'); + + var row = new CoverageFileRow + { + File = displayName, + PctStmts = lineRate * 100, + PctBranch = branchRate * 100, + PctFuncs = funcRate * 100, + PctLines = lineRate * 100, + UncoveredLines = uncoveredStr + }; + files.Add(row); + + sumStmts += row.PctStmts; countStmts++; + sumBranch += row.PctBranch; countBranch++; + sumFuncs += row.PctFuncs; countFuncs++; + sumLines += row.PctLines; countLines++; + } + } + } + catch (Exception ex) + { + Console.Error.WriteLine($"Error reading Cobertura {path}: {ex.Message}"); + } + } + + return new CoverageReportData + { + Files = files, + SummaryStmts = countStmts > 0 ? sumStmts / countStmts : 0, + SummaryBranch = countBranch > 0 ? sumBranch / countBranch : 0, + SummaryFuncs = countFuncs > 0 ? sumFuncs / countFuncs : 0, + SummaryLines = countLines > 0 ? sumLines / countLines : 0 + }; + } + + static double GetDoubleAttr(XElement el, string localName, double defaultValue) + { + var a = el.Attributes().FirstOrDefault(x => x.Name.LocalName == localName); + return a != null && double.TryParse(a.Value, NumberStyles.Any, CultureInfo.InvariantCulture, out var v) ? v : defaultValue; + } + + static string FormatUncoveredLines(List lines) + { + if (lines.Count == 0) return ""; + var ranges = new List(); + int start = lines[0], prev = lines[0]; + for (var i = 1; i < lines.Count; i++) + { + if (lines[i] == prev + 1) { prev = lines[i]; continue; } + ranges.Add(start == prev ? start.ToString() : $"{start}-{prev}"); + start = lines[i]; + prev = lines[i]; + } + ranges.Add(start == prev ? start.ToString() : $"{start}-{prev}"); + return string.Join(", ", ranges); + } + + static void EnrichResults(TestReportData testData) + { + foreach (var r in testData.Results) + { + EnrichOne(r); + } + } + + static void EnrichOne(UnitTestResult r) + { + // ── Structured parse: look for ##TEST_REPORT_START## / ##TEST_REPORT_END## in StdOut ── + var stdOut = r.StdOut ?? ""; + const string startMarker = "##TEST_REPORT_START##"; + const string endMarker = "##TEST_REPORT_END##"; + var sIdx = stdOut.IndexOf(startMarker, StringComparison.Ordinal); + var eIdx = stdOut.IndexOf(endMarker, StringComparison.Ordinal); + if (sIdx >= 0 && eIdx > sIdx) + { + var jsonLine = stdOut.Substring(sIdx + startMarker.Length, eIdx - sIdx - startMarker.Length).Trim(); + try + { + var block = System.Text.Json.JsonSerializer.Deserialize(jsonLine, + new System.Text.Json.JsonSerializerOptions { PropertyNameCaseInsensitive = true }); + if (block != null) + { + if (block.Assertions != null) + foreach (var a in block.Assertions) + r.Assertions.Add(new AssertionRecord + { + Passed = a.Passed, + Name = a.Name ?? "", + Expected = a.Expected ?? "", + Actual = a.Actual ?? "", + AssertionType = a.AssertionType ?? "Assert" + }); + + if (block.HttpRequests != null) + foreach (var req in block.HttpRequests) + { + var requestUrl = NormalizeRequestUrl(req.RequestUrl ?? ""); + var curlCommand = req.CurlCommand ?? ""; + if (string.IsNullOrEmpty(curlCommand) && !string.IsNullOrEmpty(requestUrl)) + curlCommand = BuildCurlCommand(req.HttpMethod ?? "GET", requestUrl, req.QueryParams ?? new(), req.Headers ?? new(), req.Body ?? ""); + r.HttpRequests.Add(new HttpRequestRecord + { + SdkMethod = req.SdkMethod ?? "", + HttpMethod = req.HttpMethod ?? "GET", + RequestUrl = requestUrl, + QueryParams = req.QueryParams ?? new(), + Headers = req.Headers ?? new(), + Body = req.Body ?? "", + CurlCommand = curlCommand + }); + } + + if (block.HttpResponses != null) + foreach (var resp in block.HttpResponses) + r.HttpResponses.Add(new HttpResponseRecord + { + StatusCode = resp.StatusCode, + StatusText = resp.StatusText ?? "", + ResponseTimeMs = resp.ResponseTimeMs ?? "", + Headers = resp.Headers ?? new(), + Body = resp.Body ?? "", + PayloadSize = resp.PayloadSize ?? "" + }); + + if (block.Context != null) + r.Context = new TestContextRecord + { + Environment = block.Context.Environment ?? "", + SdkVersion = block.Context.SdkVersion ?? "", + BuildNumber = block.Context.BuildNumber ?? "", + CommitSha = block.Context.CommitSha ?? "", + TestDataSource = block.Context.TestDataSource ?? "", + Locale = block.Context.Locale ?? "" + }; + + // Derive exception type from TRX message even for structured results + var isFailed2 = r.Outcome.Equals("Failed", StringComparison.OrdinalIgnoreCase); + if (isFailed2 && string.IsNullOrEmpty(r.ExceptionType) && !string.IsNullOrEmpty(r.Message)) + r.ExceptionType = ExtractExceptionType(r.Message); + + return; // structured data wins — skip heuristics + } + } + catch { /* fall through to heuristic parsing */ } + } + + // ── Heuristic fallback (for tests not yet instrumented) ────────────── + var isFailed = r.Outcome.Equals("Failed", StringComparison.OrdinalIgnoreCase); + + if (r.Assertions.Count == 0 && isFailed && !string.IsNullOrEmpty(r.Message)) + { + var assertion = DeriveAssertionFromMessage(r.Message); + if (assertion != null) + r.Assertions.Add(assertion); + } + + if (r.HttpRequests.Count == 0 && !string.IsNullOrEmpty(r.StdOut)) + { + var requests = ParseRequestsFromStdOut(r.StdOut); + foreach (var req in requests) + r.HttpRequests.Add(req); + } + + if (r.HttpResponses.Count == 0 && r.HttpRequests.Count > 0 && isFailed && !string.IsNullOrEmpty(r.Message)) + { + var resp = DeriveResponseFromMessage(r.Message, r.StdOut); + if (resp != null) + r.HttpResponses.Add(resp); + } + + if (isFailed && string.IsNullOrEmpty(r.ExceptionType) && !string.IsNullOrEmpty(r.Message)) + r.ExceptionType = ExtractExceptionType(r.Message); + + if (string.IsNullOrEmpty(r.Context.TestDataSource)) + { + r.Context.SdkVersion = "—"; + r.Context.BuildNumber = "—"; + r.Context.CommitSha = "—"; + r.Context.Environment = "—"; + r.Context.Locale = "—"; + r.Context.TestDataSource = "TRX"; + } + } + + static AssertionRecord? DeriveAssertionFromMessage(string message) + { + var m = message.Trim(); + if (string.IsNullOrEmpty(m)) return null; + var passed = false; + string expected = "", actual = "", type = "Fail"; + var expectedMatch = Regex.Match(m, @"Expected:\s*(.+?)(?:\.|\,|$)", RegexOptions.Singleline | RegexOptions.IgnoreCase); + var actualMatch = Regex.Match(m, @"Actual:\s*(.+?)(?:\.|\,|$)", RegexOptions.Singleline | RegexOptions.IgnoreCase); + if (expectedMatch.Success) expected = expectedMatch.Groups[1].Value.Trim(); + if (actualMatch.Success) actual = actualMatch.Groups[1].Value.Trim(); + if (m.StartsWith("Assert.", StringComparison.OrdinalIgnoreCase)) + { + if (m.Contains("Assert.Fail", StringComparison.OrdinalIgnoreCase)) type = "Fail"; + else if (m.Contains("Assert.AreEqual") || m.Contains("Assert.Equals")) type = "Equals"; + else if (m.Contains("Assert.IsNotNull")) type = "NotNull"; + else if (m.Contains("Assert.IsNull")) type = "IsNull"; + else if (m.Contains("Assert.Contains")) type = "Contains"; + else if (m.Contains("Assert.IsTrue")) type = "IsTrue"; + else if (m.Contains("Assert.IsFalse")) type = "IsFalse"; + } + return new AssertionRecord + { + Passed = passed, + Name = m.Length > 120 ? m.Substring(0, 117) + "..." : m, + Expected = expected, + Actual = actual, + AssertionType = type + }; + } + + /// Normalize request URL: trim trailing sentence period, ensure path has leading slash. + static string NormalizeRequestUrl(string url) + { + if (string.IsNullOrWhiteSpace(url)) return ""; + var u = url.Trim(); + // Trim trailing dot from log lines like "making request /environments." + if (u.Length > 1 && u[u.Length - 1] == '.' && u.IndexOf(' ') < 0) + u = u.Substring(0, u.Length - 1); + // Path-only (no scheme): ensure leading slash for proper cURL + if (u.Length > 0 && !u.StartsWith("http", StringComparison.OrdinalIgnoreCase) && !u.StartsWith("/")) + u = "/" + u; + return u; + } + + static string BuildCurlCommand(string httpMethod, string requestUrl) + { + var url = NormalizeRequestUrl(requestUrl ?? ""); + if (string.IsNullOrEmpty(url)) return ""; + var method = (httpMethod ?? "GET").ToUpperInvariant(); + return $"curl -X {method} \"{url}\""; + } + + /// Builds a full cURL command with URL (including query), headers, and body. + static string BuildCurlCommand(string httpMethod, string requestUrl, Dictionary queryParams, Dictionary headers, string body) + { + var fullUrl = BuildFullRequestUrl(NormalizeRequestUrl(requestUrl ?? ""), queryParams); + if (string.IsNullOrEmpty(fullUrl)) return ""; + var method = (httpMethod ?? "GET").ToUpperInvariant(); + var sb = new System.Text.StringBuilder(); + sb.Append("curl -X ").Append(method).Append(" "); + sb.Append("\"").Append(EscapeCurlUrl(fullUrl)).Append("\""); + foreach (var h in headers.Where(x => !string.IsNullOrEmpty(x.Key))) + sb.Append(" -H \"").Append(EscapeCurlHeader(h.Key)).Append(": ").Append(EscapeCurlHeader(h.Value)).Append("\""); + if (!string.IsNullOrEmpty(body)) + sb.Append(" -d '").Append(EscapeCurlBody(body)).Append("'"); + return sb.ToString(); + } + + static string BuildFullRequestUrl(string requestUrl, Dictionary queryParams) + { + var baseUrl = requestUrl.Split('?')[0].Trim(); + if (string.IsNullOrEmpty(baseUrl)) return requestUrl; + var existingQuery = requestUrl.Contains("?") ? requestUrl.Substring(requestUrl.IndexOf('?') + 1) : ""; + var paramList = new List(); + if (!string.IsNullOrEmpty(existingQuery)) + paramList.Add(existingQuery); + foreach (var kv in queryParams.Where(x => !string.IsNullOrEmpty(x.Key))) + paramList.Add($"{System.Uri.EscapeDataString(kv.Key)}={System.Uri.EscapeDataString(kv.Value ?? "")}"); + return paramList.Count == 0 ? baseUrl : baseUrl + "?" + string.Join("&", paramList); + } + + static string EscapeCurlUrl(string s) + { + if (string.IsNullOrEmpty(s)) return s; + return s.Replace("\\", "\\\\").Replace("\"", "\\\""); + } + + static string EscapeCurlHeader(string s) + { + if (string.IsNullOrEmpty(s)) return s; + return s.Replace("\\", "\\\\").Replace("\"", "\\\""); + } + + static string EscapeCurlBody(string s) + { + if (string.IsNullOrEmpty(s)) return s; + return s.Replace("'", "'\\''"); + } + + static List ParseRequestsFromStdOut(string stdOut) + { + var list = new List(); + var regex = new Regex(@"making request\s+([^\s\n\r]+)", RegexOptions.IgnoreCase); + foreach (Match match in regex.Matches(stdOut)) + { + var path = NormalizeRequestUrl(match.Groups[1].Value); + if (string.IsNullOrEmpty(path)) continue; + list.Add(new HttpRequestRecord + { + SdkMethod = "—", + HttpMethod = "GET", + RequestUrl = path, + CurlCommand = BuildCurlCommand("GET", path) + }); + } + return list; + } + + static HttpResponseRecord? DeriveResponseFromMessage(string message, string stdOut) + { + var codeMatch = Regex.Match(message, @"HTTP\s+(\d+)\s*\(([^)]*)\)", RegexOptions.IgnoreCase); + if (!codeMatch.Success) + codeMatch = Regex.Match(message, @"(\d{3})\s+(\w+)", RegexOptions.IgnoreCase); + int code = 0; + var text = ""; + if (codeMatch.Success) + { + int.TryParse(codeMatch.Groups[1].Value, out code); + text = codeMatch.Groups.Count > 2 ? codeMatch.Groups[2].Value.Trim() : ""; + } + return new HttpResponseRecord + { + StatusCode = code > 0 ? code : 0, + StatusText = text, + Body = message.Length > 500 ? message.Substring(0, 497) + "..." : message + }; + } + + static string ExtractExceptionType(string message) + { + var match = Regex.Match(message, @"([a-zA-Z0-9_.]+Exception)(?:\s*:|\s+was thrown|\.)", RegexOptions.IgnoreCase); + return match.Success ? match.Groups[1].Value : "—"; + } + + static string GenerateHtml(TestReportData testData, CoverageReportData coverageData) + { + var sb = new System.Text.StringBuilder(); + sb.Append(GetReportCss()); + sb.Append("
"); + sb.Append("

Contentstack Management .NET SDK – Enhanced Test Report

"); + + if (testData.Results.Count > 0) + { + sb.Append("

Test results

"); + sb.Append("
"); + sb.Append($"
Total{testData.Total}
"); + sb.Append($"
Passed{testData.Passed}
"); + sb.Append($"
Failed{testData.Failed}
"); + sb.Append($"
Skipped{testData.Skipped}
"); + sb.Append($"
Duration{FormatDuration(testData.TotalDuration)}
"); + sb.Append("
"); + + if (coverageData.Files.Count > 0) + { + sb.Append(@"
+ All files +
+
Statements"); + sb.Append($"{coverageData.SummaryStmts:F1}%
"); + sb.Append(@"
Branches"); + sb.Append($"{coverageData.SummaryBranch:F1}%
"); + sb.Append(@"
Functions"); + sb.Append($"{coverageData.SummaryFuncs:F1}%
"); + sb.Append(@"
Lines"); + sb.Append($"{coverageData.SummaryLines:F1}%
"); + sb.Append(@"
+
"); + } + + var useByClass = testData.ByClass.Count > 0; + var groupDict = useByClass ? testData.ByClass : testData.ByAssembly; + foreach (var kv in groupDict.OrderBy(x => x.Key)) + { + var sectionTitle = useByClass ? kv.Key + ".cs" : kv.Key; + var list = kv.Value; + var failedCount = list.Count(r => r.Outcome.Equals("Failed", StringComparison.OrdinalIgnoreCase)); + var badgeClass = failedCount > 0 ? "failed" : "passed"; + var groupId = Regex.Replace(EscapeAttr(kv.Key), @"[^a-zA-Z0-9_-]", "-"); + sb.Append($@"
+

{Escape(sectionTitle)} {list.Count} tests

+
"); + + foreach (var r in list.OrderBy(x => x.Outcome).ThenBy(x => x.TestName)) + { + AppendTestCase(sb, r); + } + sb.Append("
"); + } + } + else + { + sb.Append("

No test results (TRX) provided.

"); + if (coverageData.Files.Count > 0) + { + sb.Append(@"
+ All files +
+
Statements"); + sb.Append($"{coverageData.SummaryStmts:F1}%
"); + sb.Append(@"
Branches"); + sb.Append($"{coverageData.SummaryBranch:F1}%
"); + sb.Append(@"
Functions"); + sb.Append($"{coverageData.SummaryFuncs:F1}%
"); + sb.Append(@"
Lines"); + sb.Append($"{coverageData.SummaryLines:F1}%
"); + sb.Append(@"
+
"); + } + } + + if (coverageData.Files.Count > 0) + { + sb.Append(@"

Code coverage

+
+

Per-file coverage

+ + +"); + foreach (var row in coverageData.Files.OrderBy(x => x.File)) + { + var pctClassStmts = row.PctStmts < 50 ? " low" : row.PctStmts < 80 ? " mid" : ""; + var pctClassBranch = row.PctBranch < 50 ? " low" : row.PctBranch < 80 ? " mid" : ""; + var pctClassFuncs = row.PctFuncs < 50 ? " low" : row.PctFuncs < 80 ? " mid" : ""; + var pctClassLines = row.PctLines < 50 ? " low" : row.PctLines < 80 ? " mid" : ""; + sb.Append($""); + } + sb.Append("
FileStatementsBranchesFunctionsLinesUncovered line #s
{Escape(row.File)}{row.PctStmts:F1}%{row.PctBranch:F1}%{row.PctFuncs:F1}%{row.PctLines:F1}%{Escape(row.UncoveredLines)}
"); + } + else + { + sb.Append("

Code coverage

Coverage not collected (no Cobertura XML provided).
"); + } + + sb.Append(GetReportScript()); + sb.Append("
"); + return sb.ToString(); + } + + static string GetReportCss() + { + return @" + + + + +Contentstack Management .NET SDK - Enhanced Test Report + + +"; + } + + static void AppendTestCase(System.Text.StringBuilder sb, UnitTestResult r) + { + var outcomeClass = r.Outcome.Equals("Passed", StringComparison.OrdinalIgnoreCase) ? "Passed" : r.Outcome.Equals("Failed", StringComparison.OrdinalIgnoreCase) ? "Failed" : "Skipped"; + var testId = "test-" + Guid.NewGuid().ToString("N")[..8]; + sb.Append($"
"); + sb.Append($"
"); + sb.Append($""); + sb.Append($"{Escape(r.Outcome)}"); + sb.Append($"{FormatDuration(r.Duration)}"); + sb.Append($"{Escape(r.TestName)}"); + sb.Append("
"); + sb.Append($"
"); + AppendDetailSections(sb, r); + sb.Append("
"); + sb.Append("
"); + } + + static void AppendDetailSections(System.Text.StringBuilder sb, UnitTestResult r) + { + sb.Append("
"); + + sb.Append("
Assertions
"); + if (r.Assertions.Count == 0) + sb.Append("
No assertion details in TRX. Failed message is shown in Error Details.
"); + else + { + for (var i = 0; i < r.Assertions.Count; i++) + { + var a = r.Assertions[i]; + var cardClass = a.Passed ? "pass" : "fail"; + sb.Append($"
"); + sb.Append($"{(a.Passed ? "✅" : "❌")} "); + sb.Append($"{Escape(a.Name)}"); + if (!string.IsNullOrEmpty(a.AssertionType)) sb.Append($" Type: {Escape(a.AssertionType)}"); + if (!string.IsNullOrEmpty(a.Expected)) sb.Append($"
{Escape(a.Expected)}
"); + if (!string.IsNullOrEmpty(a.Actual)) sb.Append($"
{Escape(a.Actual)}
"); + sb.Append("
"); + } + } + sb.Append("
"); + + sb.Append("
🌐 HTTP Requests
"); + if (r.HttpRequests.Count == 0) + sb.Append("
No request data captured. Emit structured data from tests to populate.
"); + else + { + foreach (var req in r.HttpRequests) + { + sb.Append("
SDK Method: ").Append(Escape(req.SdkMethod)).Append("
"); + sb.Append("
HTTP Method: ").Append(Escape(req.HttpMethod)).Append("
"); + sb.Append("
Request URL: ").Append(Escape(req.RequestUrl)).Append("
"); + if (req.QueryParams.Count > 0) + { + sb.Append("
Query Parameters:
"); + foreach (var q in req.QueryParams) + sb.Append($"
{Escape(q.Key)} = {Escape(q.Value)}
"); + } + if (req.Headers.Count > 0) + { + sb.Append("
Request Headers:
"); + var headerLines = string.Join("\n", req.Headers.Select(h => $"{h.Key}: {h.Value}")); + sb.Append($"
{Escape(headerLines)}
"); + } + if (!string.IsNullOrEmpty(req.Body)) + sb.Append($"
Request Body:
{Escape(req.Body)}
"); + if (!string.IsNullOrEmpty(req.CurlCommand)) + { + sb.Append("
cURL:
").Append(Escape(req.CurlCommand)).Append("
"); + sb.Append($""); + } + } + } + sb.Append("
"); + + sb.Append("
📥 HTTP Responses
"); + if (r.HttpResponses.Count == 0) + sb.Append("
No response data captured.
"); + else + { + foreach (var res in r.HttpResponses) + { + if (res.StatusCode > 0) sb.Append($"
Status: {res.StatusCode} {Escape(res.StatusText)}
"); + if (!string.IsNullOrEmpty(res.ResponseTimeMs)) sb.Append($"
Response Time: {Escape(res.ResponseTimeMs)}
"); + if (res.Headers.Count > 0) + { + sb.Append("
Response Headers:
"); + var headerLines = string.Join("\n", res.Headers.Select(h => $"{h.Key}: {h.Value}")); + sb.Append($"
{Escape(headerLines)}
"); + } + if (!string.IsNullOrEmpty(res.Body)) sb.Append($"
Response Body:
{Escape(res.Body)}
"); + if (!string.IsNullOrEmpty(res.PayloadSize)) sb.Append($"
Payload Size: {Escape(res.PayloadSize)}
"); + } + } + sb.Append("
"); + + var isFailed = r.Outcome.Equals("Failed", StringComparison.OrdinalIgnoreCase); + if (isFailed) + { + sb.Append("
⚠️ Error Details
"); + if (!string.IsNullOrEmpty(r.Message)) sb.Append($"
Error Message:
{Escape(r.Message)}
"); + if (!string.IsNullOrEmpty(r.ExceptionType)) sb.Append($"
Exception Type: {Escape(r.ExceptionType)}
"); + if (!string.IsNullOrEmpty(r.StackTrace)) sb.Append($"
Stack Trace:
{Escape(r.StackTrace)}
"); + if (r.Assertions.Count > 0) sb.Append($"
Failed Assertion: See Assertions #1 above.
"); + if (r.RetryCount.HasValue) sb.Append($"
Retry Count: {r.RetryCount.Value}
"); + sb.Append("
"); + } + + sb.Append("
ℹ️ Test Context
"); + var ctx = r.Context; + sb.Append($"
Environment: {Escape(ctx.Environment)}
"); + sb.Append($"
Locale: {Escape(ctx.Locale)}
"); + sb.Append($"
SDK Version: {Escape(ctx.SdkVersion)}
"); + sb.Append($"
Build Number: {Escape(ctx.BuildNumber)}
"); + sb.Append($"
Commit SHA: {Escape(ctx.CommitSha)}
"); + sb.Append($"
Test Data Source: {Escape(ctx.TestDataSource)}
"); + foreach (var kv in ctx.Keys) + sb.Append($"
{Escape(kv.Key)}: {Escape(kv.Value)}
"); + sb.Append("
"); + + sb.Append("
"); + } + + static string GetReportScript() + { + return @" +"; + } + + static string FormatDuration(TimeSpan d) => d.TotalMilliseconds < 1000 ? $"{d.TotalMilliseconds:F0} ms" : $"{d.TotalSeconds:F2} s"; + static string Escape(string? s) => string.IsNullOrEmpty(s) ? "" : System.Net.WebUtility.HtmlEncode(s); + static string EscapeAttr(string? s) => Escape(s)?.Replace("\"", """) ?? ""; + + private sealed class AssertionRecord + { + public bool Passed { get; set; } + public string Name { get; set; } = ""; + public string Expected { get; set; } = ""; + public string Actual { get; set; } = ""; + public string AssertionType { get; set; } = ""; + } + + private sealed class HttpRequestRecord + { + public string SdkMethod { get; set; } = ""; + public string HttpMethod { get; set; } = ""; + public string RequestUrl { get; set; } = ""; + public Dictionary QueryParams { get; set; } = new(); + public Dictionary Headers { get; set; } = new(); + public string Body { get; set; } = ""; + public string CurlCommand { get; set; } = ""; + } + + private sealed class HttpResponseRecord + { + public int StatusCode { get; set; } + public string StatusText { get; set; } = ""; + public string ResponseTimeMs { get; set; } = ""; + public Dictionary Headers { get; set; } = new(); + public string Body { get; set; } = ""; + public string PayloadSize { get; set; } = ""; + } + + private sealed class TestContextRecord + { + public Dictionary Keys { get; set; } = new(); + public string Environment { get; set; } = ""; + public string Locale { get; set; } = ""; + public string SdkVersion { get; set; } = ""; + public string BuildNumber { get; set; } = ""; + public string CommitSha { get; set; } = ""; + public string TestDataSource { get; set; } = ""; + } + + private sealed class UnitTestResult + { + public string TestName { get; set; } = ""; + public string ClassName { get; set; } = ""; + public string Outcome { get; set; } = ""; + public TimeSpan Duration { get; set; } + public string Message { get; set; } = ""; + public string StackTrace { get; set; } = ""; + public string StdOut { get; set; } = ""; + public string StdErr { get; set; } = ""; + public string DebugTrace { get; set; } = ""; + public string Assembly { get; set; } = ""; + public List Assertions { get; set; } = new(); + public List HttpRequests { get; set; } = new(); + public List HttpResponses { get; set; } = new(); + public TestContextRecord Context { get; set; } = new(); + public string ExceptionType { get; set; } = ""; + public int? RetryCount { get; set; } + } + + private sealed class CoverageFileRow + { + public string File { get; set; } = ""; + public double PctStmts { get; set; } + public double PctBranch { get; set; } + public double PctFuncs { get; set; } + public double PctLines { get; set; } + public string UncoveredLines { get; set; } = ""; + } + + private sealed class TestReportData + { + public List Results { get; set; } = new(); + public Dictionary> ByAssembly { get; set; } = new(); + public Dictionary> ByClass { get; set; } = new(); + public int Total { get; set; } + public int Passed { get; set; } + public int Failed { get; set; } + public int Skipped { get; set; } + public TimeSpan TotalDuration { get; set; } + } + + private sealed class CoverageReportData + { + public List Files { get; set; } = new(); + public double SummaryStmts { get; set; } + public double SummaryBranch { get; set; } + public double SummaryFuncs { get; set; } + public double SummaryLines { get; set; } + } + + // ── POCOs that mirror the JSON written by TestReportHelper.Flush() ──────── + private sealed class TestBlockPayload + { + public List Assertions { get; set; } + public List HttpRequests { get; set; } + public List HttpResponses { get; set; } + public ContextP Context { get; set; } + } + private sealed class AssertionP + { + public bool Passed { get; set; } + public string Name { get; set; } + public string Expected { get; set; } + public string Actual { get; set; } + public string AssertionType { get; set; } + } + private sealed class RequestP + { + public string SdkMethod { get; set; } + public string HttpMethod { get; set; } + public string RequestUrl { get; set; } + public Dictionary QueryParams { get; set; } + public Dictionary Headers { get; set; } + public string Body { get; set; } + public string CurlCommand { get; set; } + } + private sealed class ResponseP + { + public int StatusCode { get; set; } + public string StatusText { get; set; } + public string ResponseTimeMs { get; set; } + public Dictionary Headers { get; set; } + public string Body { get; set; } + public string PayloadSize { get; set; } + } + private sealed class ContextP + { + public string Environment { get; set; } + public string SdkVersion { get; set; } + public string BuildNumber { get; set; } + public string CommitSha { get; set; } + public string TestDataSource { get; set; } + public string Locale { get; set; } + } +} diff --git a/tools/EnhancedTestReport/sample/coverage.cobertura.xml b/tools/EnhancedTestReport/sample/coverage.cobertura.xml new file mode 100644 index 0000000..72ab4a6 --- /dev/null +++ b/tools/EnhancedTestReport/sample/coverage.cobertura.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/tools/EnhancedTestReport/sample/out-new.html b/tools/EnhancedTestReport/sample/out-new.html new file mode 100644 index 0000000..03950f2 --- /dev/null +++ b/tools/EnhancedTestReport/sample/out-new.html @@ -0,0 +1,138 @@ + + + + + +Contentstack Management .NET SDK - Enhanced Test Report + + +

Contentstack Management .NET SDK – Enhanced Test Report

Test results

Total2
Passed1
Failed1
Skipped0
Duration579 ms
+

sample 2 tests

+
Failed456 msTestTwo
Assertions
Assert failed Type: Fail
🌐 HTTP Requests
No request data captured. Emit structured data from tests to populate.
📥 HTTP Responses
No response data captured.
⚠️ Error Details
Error Message:
Assert failed
Exception Type:
Stack Trace:
at MyTest.TestTwo()
Failed Assertion: See Assertions #1 above.
ℹ️ Test Context
Environment:
Locale:
SDK Version:
Build Number:
Commit SHA:
Test Data Source: TRX
Passed123 msTestOne
Assertions
No assertion details in TRX. Failed message is shown in Error Details.
🌐 HTTP Requests
No request data captured. Emit structured data from tests to populate.
📥 HTTP Responses
No response data captured.
ℹ️ Test Context
Environment:
Locale:
SDK Version:
Build Number:
Commit SHA:
Test Data Source: TRX

Code coverage

Coverage not collected (no Cobertura XML provided).
+
\ No newline at end of file diff --git a/tools/EnhancedTestReport/sample/out.html b/tools/EnhancedTestReport/sample/out.html new file mode 100644 index 0000000..97149b3 --- /dev/null +++ b/tools/EnhancedTestReport/sample/out.html @@ -0,0 +1,73 @@ + + + + + +Contentstack Management .NET SDK - Enhanced Test Report + + + +
+

Contentstack Management .NET SDK – Enhanced Test Report

+

Test results

Total 2
Passed 1
Failed 1
Skipped 0
Duration 579 ms
+

sample 2 tests

+
Failed456 msTestTwo
Message: +Assert failed + +StackTrace: +at MyTest.TestTwo()
Passed123 msTestOne

Code coverage

+
+ + +
File% Stmts% Branch% Funcs% LinesUncovered Line #s
All files90.00%80.00%100.00%90.00%
Contentstack.Management.Core/Foo.cs90.00%80.00%100.00%90.00%11, 15
+ +
+ + \ No newline at end of file diff --git a/tools/js-cma-api.json b/tools/js-cma-api.json new file mode 100644 index 0000000..505a91d --- /dev/null +++ b/tools/js-cma-api.json @@ -0,0 +1,15 @@ +[ + { "key": "Stack.GetAll", "hasTest": true }, + { "key": "Stack.Fetch", "hasTest": true }, + { "key": "Stack.Create", "hasTest": true }, + { "key": "Asset.Create", "hasTest": true }, + { "key": "Asset.Fetch", "hasTest": true }, + { "key": "ContentType.Create", "hasTest": true }, + { "key": "ContentType.Fetch", "hasTest": true }, + { "key": "BulkOperation.Publish", "hasTest": true }, + { "key": "BulkOperation.Unpublish", "hasTest": true }, + { "key": "GlobalField.Create", "hasTest": true }, + { "key": "GlobalField.Fetch", "hasTest": true }, + { "key": "DeliveryToken.Create", "hasTest": true }, + { "key": "DeliveryToken.Delete", "hasTest": true } +]