From 130fbddd2aabbb63b4eec4bf5512ada38f8b5f35 Mon Sep 17 00:00:00 2001 From: OMpawar-21 Date: Tue, 10 Mar 2026 15:25:22 +0530 Subject: [PATCH 1/3] feat: Add full Taxonomy and Terms API to .NET CMA SDK - Add Taxonomy and Term support to match the JavaScript CMA SDK. Models: - TaxonomyModel, TermModel (with TermAncestorDescendant, TermMoveModel), TaxonomyImportModel (IUploadInterface for multipart import). Taxonomy: - CRUD, Query(), Export(), Locales(), Localize(), Import(), Terms(termUid). - Stack.Taxonomy(uid) entry point. Term: - CRUD, Query(), Ancestors(), Descendants(), Move(), Locales(), Localize(), Search(typeahead). - Accessed via Stack.Taxonomy(taxonomyUid).Terms() / .Terms(termUid). Services: - Reuse CreateUpdateService, FetchDeleteService, QueryService; extend UploadService with optional query params for Import. Tests: - Unit: TaxonomyTest, TermTest (20 tests). Integration: Contentstack017_TaxonomyTest (create, fetch, query, update, delete). Use static taxonomy UID in integration test so all steps use the same created taxonomy. --- .../Contentstack017_TaxonomyTest.cs | 108 ++++++++ .../Model/Models.cs | 14 +- .../Models/TaxonomyTest.cs | 134 ++++++++++ .../Models/TermTest.cs | 138 ++++++++++ Contentstack.Management.Core/Models/Stack.cs | 23 +- .../Models/Taxonomy.cs | 203 +++++++++++++++ .../Models/TaxonomyImportModel.cs | 48 ++++ .../Models/TaxonomyModel.cs | 44 ++++ Contentstack.Management.Core/Models/Term.cs | 235 ++++++++++++++++++ .../Models/TermModel.cs | 83 +++++++ .../Services/Models/UploadService.cs | 11 +- 11 files changed, 1036 insertions(+), 5 deletions(-) create mode 100644 Contentstack.Management.Core.Tests/IntegrationTest/Contentstack017_TaxonomyTest.cs create mode 100644 Contentstack.Management.Core.Unit.Tests/Models/TaxonomyTest.cs create mode 100644 Contentstack.Management.Core.Unit.Tests/Models/TermTest.cs create mode 100644 Contentstack.Management.Core/Models/Taxonomy.cs create mode 100644 Contentstack.Management.Core/Models/TaxonomyImportModel.cs create mode 100644 Contentstack.Management.Core/Models/TaxonomyModel.cs create mode 100644 Contentstack.Management.Core/Models/Term.cs create mode 100644 Contentstack.Management.Core/Models/TermModel.cs diff --git a/Contentstack.Management.Core.Tests/IntegrationTest/Contentstack017_TaxonomyTest.cs b/Contentstack.Management.Core.Tests/IntegrationTest/Contentstack017_TaxonomyTest.cs new file mode 100644 index 0000000..1f4138a --- /dev/null +++ b/Contentstack.Management.Core.Tests/IntegrationTest/Contentstack017_TaxonomyTest.cs @@ -0,0 +1,108 @@ +using System; +using System.Threading.Tasks; +using Contentstack.Management.Core.Models; +using Contentstack.Management.Core.Tests.Model; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Contentstack.Management.Core.Tests.IntegrationTest +{ + [TestClass] + public class Contentstack017_TaxonomyTest + { + private static string _taxonomyUid; + private Stack _stack; + private TaxonomyModel _createModel; + + [TestInitialize] + public void Initialize() + { + StackResponse response = StackResponse.getStack(Contentstack.Client.serializer); + _stack = Contentstack.Client.Stack(response.Stack.APIKey); + if (_taxonomyUid == null) + _taxonomyUid = "taxonomy_integration_test_" + Guid.NewGuid().ToString("N").Substring(0, 8); + _createModel = new TaxonomyModel + { + Uid = _taxonomyUid, + Name = "Taxonomy Integration Test", + Description = "Description for taxonomy integration test" + }; + } + + [TestMethod] + [DoNotParallelize] + public void Test001_Should_Create_Taxonomy() + { + ContentstackResponse response = _stack.Taxonomy().Create(_createModel); + Assert.IsTrue(response.IsSuccessStatusCode, $"Create failed: {response.OpenResponse()}"); + + var wrapper = response.OpenTResponse(); + Assert.IsNotNull(wrapper?.Taxonomy); + Assert.AreEqual(_createModel.Uid, wrapper.Taxonomy.Uid); + Assert.AreEqual(_createModel.Name, wrapper.Taxonomy.Name); + Assert.AreEqual(_createModel.Description, wrapper.Taxonomy.Description); + } + + [TestMethod] + [DoNotParallelize] + public void Test002_Should_Fetch_Taxonomy() + { + ContentstackResponse response = _stack.Taxonomy(_taxonomyUid).Fetch(); + Assert.IsTrue(response.IsSuccessStatusCode, $"Fetch failed: {response.OpenResponse()}"); + + var wrapper = response.OpenTResponse(); + Assert.IsNotNull(wrapper?.Taxonomy); + Assert.AreEqual(_taxonomyUid, wrapper.Taxonomy.Uid); + Assert.IsNotNull(wrapper.Taxonomy.Name); + } + + [TestMethod] + [DoNotParallelize] + public void Test003_Should_Query_Taxonomies() + { + ContentstackResponse response = _stack.Taxonomy().Query().Find(); + Assert.IsTrue(response.IsSuccessStatusCode, $"Query failed: {response.OpenResponse()}"); + + var wrapper = response.OpenTResponse(); + Assert.IsNotNull(wrapper?.Taxonomies); + Assert.IsTrue(wrapper.Taxonomies.Count >= 0); + } + + [TestMethod] + [DoNotParallelize] + public void Test004_Should_Update_Taxonomy() + { + var updateModel = new TaxonomyModel + { + Name = "Taxonomy Integration Test Updated", + Description = "Updated description" + }; + ContentstackResponse response = _stack.Taxonomy(_taxonomyUid).Update(updateModel); + Assert.IsTrue(response.IsSuccessStatusCode, $"Update failed: {response.OpenResponse()}"); + + var wrapper = response.OpenTResponse(); + Assert.IsNotNull(wrapper?.Taxonomy); + Assert.AreEqual("Taxonomy Integration Test Updated", wrapper.Taxonomy.Name); + Assert.AreEqual("Updated description", wrapper.Taxonomy.Description); + } + + [TestMethod] + [DoNotParallelize] + public async Task Test005_Should_Fetch_Taxonomy_Async() + { + ContentstackResponse response = await _stack.Taxonomy(_taxonomyUid).FetchAsync(); + Assert.IsTrue(response.IsSuccessStatusCode, $"FetchAsync failed: {response.OpenResponse()}"); + + var wrapper = response.OpenTResponse(); + Assert.IsNotNull(wrapper?.Taxonomy); + Assert.AreEqual(_taxonomyUid, wrapper.Taxonomy.Uid); + } + + [TestMethod] + [DoNotParallelize] + public void Test006_Should_Delete_Taxonomy() + { + ContentstackResponse response = _stack.Taxonomy(_taxonomyUid).Delete(); + Assert.IsTrue(response.IsSuccessStatusCode, $"Delete failed: {response.OpenResponse()}"); + } + } +} diff --git a/Contentstack.Management.Core.Tests/Model/Models.cs b/Contentstack.Management.Core.Tests/Model/Models.cs index b644b7a..e4bf00e 100644 --- a/Contentstack.Management.Core.Tests/Model/Models.cs +++ b/Contentstack.Management.Core.Tests/Model/Models.cs @@ -1,4 +1,4 @@ -using Newtonsoft.Json; +using Newtonsoft.Json; using Contentstack.Management.Core.Models; using System.Collections.Generic; @@ -25,5 +25,17 @@ public class ContentTypesModel [JsonProperty("content_types")] public List Modellings { get; set; } } + + public class TaxonomyResponseModel + { + [JsonProperty("taxonomy")] + public TaxonomyModel Taxonomy { get; set; } + } + + public class TaxonomiesResponseModel + { + [JsonProperty("taxonomies")] + public List Taxonomies { get; set; } + } } diff --git a/Contentstack.Management.Core.Unit.Tests/Models/TaxonomyTest.cs b/Contentstack.Management.Core.Unit.Tests/Models/TaxonomyTest.cs new file mode 100644 index 0000000..14cd4ee --- /dev/null +++ b/Contentstack.Management.Core.Unit.Tests/Models/TaxonomyTest.cs @@ -0,0 +1,134 @@ +using System; +using AutoFixture; +using Contentstack.Management.Core.Models; +using Contentstack.Management.Core.Queryable; +using Contentstack.Management.Core.Unit.Tests.Mokes; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Contentstack.Management.Core.Unit.Tests.Models +{ + [TestClass] + public class TaxonomyTest + { + private Stack _stack; + private readonly IFixture _fixture = new Fixture(); + private ContentstackResponse _contentstackResponse; + + [TestInitialize] + public void Initialize() + { + var client = new ContentstackClient(); + _contentstackResponse = MockResponse.CreateContentstackResponse("MockResponse.txt"); + client.ContentstackPipeline.ReplaceHandler(new MockHttpHandler(_contentstackResponse)); + client.contentstackOptions.Authtoken = _fixture.Create(); + _stack = new Stack(client, _fixture.Create()); + } + + [TestMethod] + public void Initialize_Taxonomy() + { + Taxonomy taxonomy = _stack.Taxonomy(); + + Assert.IsNull(taxonomy.Uid); + Assert.AreEqual("/taxonomies", taxonomy.resourcePath); + Assert.ThrowsException(() => taxonomy.Fetch()); + Assert.ThrowsExceptionAsync(() => taxonomy.FetchAsync()); + Assert.ThrowsException(() => taxonomy.Update(_fixture.Create())); + Assert.ThrowsExceptionAsync(() => taxonomy.UpdateAsync(_fixture.Create())); + Assert.ThrowsException(() => taxonomy.Delete()); + Assert.ThrowsExceptionAsync(() => taxonomy.DeleteAsync()); + Assert.ThrowsException(() => taxonomy.Terms()); + Assert.AreEqual(typeof(Query), taxonomy.Query().GetType()); + } + + [TestMethod] + public void Initialize_Taxonomy_With_Uid() + { + string uid = _fixture.Create(); + Taxonomy taxonomy = _stack.Taxonomy(uid); + + Assert.AreEqual(uid, taxonomy.Uid); + Assert.AreEqual($"/taxonomies/{uid}", taxonomy.resourcePath); + Assert.ThrowsException(() => taxonomy.Create(_fixture.Create())); + Assert.ThrowsExceptionAsync(() => taxonomy.CreateAsync(_fixture.Create())); + Assert.ThrowsException(() => taxonomy.Query()); + } + + [TestMethod] + public void Should_Create_Taxonomy() + { + ContentstackResponse response = _stack.Taxonomy().Create(_fixture.Create()); + + Assert.AreEqual(_contentstackResponse.OpenResponse(), response.OpenResponse()); + Assert.AreEqual(_contentstackResponse.OpenJObjectResponse().ToString(), response.OpenJObjectResponse().ToString()); + } + + [TestMethod] + public async System.Threading.Tasks.Task Should_Create_Taxonomy_Async() + { + ContentstackResponse response = await _stack.Taxonomy().CreateAsync(_fixture.Create()); + + Assert.AreEqual(_contentstackResponse.OpenResponse(), response.OpenResponse()); + Assert.AreEqual(_contentstackResponse.OpenJObjectResponse().ToString(), response.OpenJObjectResponse().ToString()); + } + + [TestMethod] + public void Should_Query_Taxonomy() + { + ContentstackResponse response = _stack.Taxonomy().Query().Find(); + + Assert.AreEqual(_contentstackResponse.OpenResponse(), response.OpenResponse()); + Assert.AreEqual(_contentstackResponse.OpenJObjectResponse().ToString(), response.OpenJObjectResponse().ToString()); + } + + [TestMethod] + public async System.Threading.Tasks.Task Should_Query_Taxonomy_Async() + { + ContentstackResponse response = await _stack.Taxonomy().Query().FindAsync(); + + Assert.AreEqual(_contentstackResponse.OpenResponse(), response.OpenResponse()); + Assert.AreEqual(_contentstackResponse.OpenJObjectResponse().ToString(), response.OpenJObjectResponse().ToString()); + } + + [TestMethod] + public void Should_Fetch_Taxonomy() + { + ContentstackResponse response = _stack.Taxonomy(_fixture.Create()).Fetch(); + + Assert.AreEqual(_contentstackResponse.OpenResponse(), response.OpenResponse()); + Assert.AreEqual(_contentstackResponse.OpenJObjectResponse().ToString(), response.OpenJObjectResponse().ToString()); + } + + [TestMethod] + public async System.Threading.Tasks.Task Should_Fetch_Taxonomy_Async() + { + ContentstackResponse response = await _stack.Taxonomy(_fixture.Create()).FetchAsync(); + + Assert.AreEqual(_contentstackResponse.OpenResponse(), response.OpenResponse()); + Assert.AreEqual(_contentstackResponse.OpenJObjectResponse().ToString(), response.OpenJObjectResponse().ToString()); + } + + [TestMethod] + public void Should_Get_Terms_From_Taxonomy() + { + string taxonomyUid = _fixture.Create(); + Term terms = _stack.Taxonomy(taxonomyUid).Terms(); + + Assert.IsNotNull(terms); + Assert.IsNull(terms.Uid); + Assert.AreEqual($"/taxonomies/{taxonomyUid}/terms", terms.resourcePath); + } + + [TestMethod] + public void Should_Get_Single_Term_From_Taxonomy() + { + string taxonomyUid = _fixture.Create(); + string termUid = _fixture.Create(); + Term term = _stack.Taxonomy(taxonomyUid).Terms(termUid); + + Assert.IsNotNull(term); + Assert.AreEqual(termUid, term.Uid); + Assert.AreEqual($"/taxonomies/{taxonomyUid}/terms/{termUid}", term.resourcePath); + } + } +} diff --git a/Contentstack.Management.Core.Unit.Tests/Models/TermTest.cs b/Contentstack.Management.Core.Unit.Tests/Models/TermTest.cs new file mode 100644 index 0000000..223840e --- /dev/null +++ b/Contentstack.Management.Core.Unit.Tests/Models/TermTest.cs @@ -0,0 +1,138 @@ +using System; +using AutoFixture; +using Contentstack.Management.Core.Models; +using Contentstack.Management.Core.Queryable; +using Contentstack.Management.Core.Unit.Tests.Mokes; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Contentstack.Management.Core.Unit.Tests.Models +{ + [TestClass] + public class TermTest + { + private Stack _stack; + private readonly IFixture _fixture = new Fixture(); + private ContentstackResponse _contentstackResponse; + + [TestInitialize] + public void Initialize() + { + var client = new ContentstackClient(); + _contentstackResponse = MockResponse.CreateContentstackResponse("MockResponse.txt"); + client.ContentstackPipeline.ReplaceHandler(new MockHttpHandler(_contentstackResponse)); + client.contentstackOptions.Authtoken = _fixture.Create(); + _stack = new Stack(client, _fixture.Create()); + } + + [TestMethod] + public void Initialize_Term_Collection() + { + string taxonomyUid = _fixture.Create(); + Term term = _stack.Taxonomy(taxonomyUid).Terms(); + + Assert.IsNull(term.Uid); + Assert.AreEqual($"/taxonomies/{taxonomyUid}/terms", term.resourcePath); + Assert.ThrowsException(() => term.Fetch()); + Assert.ThrowsExceptionAsync(() => term.FetchAsync()); + Assert.AreEqual(typeof(Query), term.Query().GetType()); + } + + [TestMethod] + public void Initialize_Term_With_Uid() + { + string taxonomyUid = _fixture.Create(); + string termUid = _fixture.Create(); + Term term = _stack.Taxonomy(taxonomyUid).Terms(termUid); + + Assert.AreEqual(termUid, term.Uid); + Assert.AreEqual($"/taxonomies/{taxonomyUid}/terms/{termUid}", term.resourcePath); + Assert.ThrowsException(() => term.Create(_fixture.Create())); + Assert.ThrowsExceptionAsync(() => term.CreateAsync(_fixture.Create())); + Assert.ThrowsException(() => term.Query()); + Assert.ThrowsException(() => term.Search("x")); + Assert.ThrowsExceptionAsync(() => term.SearchAsync("x")); + } + + [TestMethod] + public void Should_Create_Term() + { + string taxonomyUid = _fixture.Create(); + ContentstackResponse response = _stack.Taxonomy(taxonomyUid).Terms().Create(_fixture.Create()); + + Assert.AreEqual(_contentstackResponse.OpenResponse(), response.OpenResponse()); + Assert.AreEqual(_contentstackResponse.OpenJObjectResponse().ToString(), response.OpenJObjectResponse().ToString()); + } + + [TestMethod] + public async System.Threading.Tasks.Task Should_Create_Term_Async() + { + string taxonomyUid = _fixture.Create(); + ContentstackResponse response = await _stack.Taxonomy(taxonomyUid).Terms().CreateAsync(_fixture.Create()); + + Assert.AreEqual(_contentstackResponse.OpenResponse(), response.OpenResponse()); + Assert.AreEqual(_contentstackResponse.OpenJObjectResponse().ToString(), response.OpenJObjectResponse().ToString()); + } + + [TestMethod] + public void Should_Query_Terms() + { + string taxonomyUid = _fixture.Create(); + ContentstackResponse response = _stack.Taxonomy(taxonomyUid).Terms().Query().Find(); + + Assert.AreEqual(_contentstackResponse.OpenResponse(), response.OpenResponse()); + Assert.AreEqual(_contentstackResponse.OpenJObjectResponse().ToString(), response.OpenJObjectResponse().ToString()); + } + + [TestMethod] + public async System.Threading.Tasks.Task Should_Query_Terms_Async() + { + string taxonomyUid = _fixture.Create(); + ContentstackResponse response = await _stack.Taxonomy(taxonomyUid).Terms().Query().FindAsync(); + + Assert.AreEqual(_contentstackResponse.OpenResponse(), response.OpenResponse()); + Assert.AreEqual(_contentstackResponse.OpenJObjectResponse().ToString(), response.OpenJObjectResponse().ToString()); + } + + [TestMethod] + public void Should_Fetch_Term() + { + string taxonomyUid = _fixture.Create(); + string termUid = _fixture.Create(); + ContentstackResponse response = _stack.Taxonomy(taxonomyUid).Terms(termUid).Fetch(); + + Assert.AreEqual(_contentstackResponse.OpenResponse(), response.OpenResponse()); + Assert.AreEqual(_contentstackResponse.OpenJObjectResponse().ToString(), response.OpenJObjectResponse().ToString()); + } + + [TestMethod] + public async System.Threading.Tasks.Task Should_Fetch_Term_Async() + { + string taxonomyUid = _fixture.Create(); + string termUid = _fixture.Create(); + ContentstackResponse response = await _stack.Taxonomy(taxonomyUid).Terms(termUid).FetchAsync(); + + Assert.AreEqual(_contentstackResponse.OpenResponse(), response.OpenResponse()); + Assert.AreEqual(_contentstackResponse.OpenJObjectResponse().ToString(), response.OpenJObjectResponse().ToString()); + } + + [TestMethod] + public void Should_Search_Terms() + { + string taxonomyUid = _fixture.Create(); + ContentstackResponse response = _stack.Taxonomy(taxonomyUid).Terms().Search("test"); + + Assert.AreEqual(_contentstackResponse.OpenResponse(), response.OpenResponse()); + Assert.AreEqual(_contentstackResponse.OpenJObjectResponse().ToString(), response.OpenJObjectResponse().ToString()); + } + + [TestMethod] + public async System.Threading.Tasks.Task Should_Search_Terms_Async() + { + string taxonomyUid = _fixture.Create(); + ContentstackResponse response = await _stack.Taxonomy(taxonomyUid).Terms().SearchAsync("test"); + + Assert.AreEqual(_contentstackResponse.OpenResponse(), response.OpenResponse()); + Assert.AreEqual(_contentstackResponse.OpenJObjectResponse().ToString(), response.OpenJObjectResponse().ToString()); + } + } +} diff --git a/Contentstack.Management.Core/Models/Stack.cs b/Contentstack.Management.Core/Models/Stack.cs index 2fbad64..2c6420b 100644 --- a/Contentstack.Management.Core/Models/Stack.cs +++ b/Contentstack.Management.Core/Models/Stack.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Threading.Tasks; using Contentstack.Management.Core.Queryable; @@ -702,6 +702,27 @@ public Label Label(string uid = null) return new Label(this, uid); } + /// + /// allows you to organize and categorize content using a hierarchical structure of terms. + /// + /// Optional, taxonomy uid. + /// + ///

+        /// ContentstackClient client = new ContentstackClient("<AUTHTOKEN>", "<API_HOST>");
+        /// Stack stack = client.Stack("<API_KEY>");
+        /// ContentstackResponse response = stack.Taxonomy("<TAXONOMY_UID>").Fetch();
+        /// ContentstackResponse list = stack.Taxonomy().Query().Find();
+        /// 
+ ///
+ /// The + public Taxonomy Taxonomy(string uid = null) + { + ThrowIfNotLoggedIn(); + ThrowIfAPIKeyEmpty(); + + return new Taxonomy(this, uid); + } + /// /// A publishing corresponds to one or more deployment servers or a content delivery destination where the entries need to be published. /// diff --git a/Contentstack.Management.Core/Models/Taxonomy.cs b/Contentstack.Management.Core/Models/Taxonomy.cs new file mode 100644 index 0000000..dbbaa01 --- /dev/null +++ b/Contentstack.Management.Core/Models/Taxonomy.cs @@ -0,0 +1,203 @@ +using System; +using System.Threading.Tasks; +using Contentstack.Management.Core.Queryable; +using Contentstack.Management.Core.Services.Models; + +namespace Contentstack.Management.Core.Models +{ + /// + /// Taxonomy allows you to organize and categorize content using a hierarchical structure of terms. + /// + public class Taxonomy : BaseModel + { + internal Taxonomy(Stack stack, string uid = null) + : base(stack, "taxonomy", uid) + { + resourcePath = uid == null ? "/taxonomies" : $"/taxonomies/{uid}"; + } + + /// + /// Query taxonomies. Fetches all taxonomies with optional filters. + /// + /// + /// + /// ContentstackResponse response = stack.Taxonomy().Query().Find(); + /// + /// + public Query Query() + { + ThrowIfUidNotEmpty(); + return new Query(stack, resourcePath); + } + + /// + /// Create a taxonomy. + /// + public override ContentstackResponse Create(TaxonomyModel model, ParameterCollection collection = null) + { + return base.Create(model, collection); + } + + /// + /// Create a taxonomy asynchronously. + /// + public override Task CreateAsync(TaxonomyModel model, ParameterCollection collection = null) + { + return base.CreateAsync(model, collection); + } + + /// + /// Update an existing taxonomy. + /// + public override ContentstackResponse Update(TaxonomyModel model, ParameterCollection collection = null) + { + return base.Update(model, collection); + } + + /// + /// Update an existing taxonomy asynchronously. + /// + public override Task UpdateAsync(TaxonomyModel model, ParameterCollection collection = null) + { + return base.UpdateAsync(model, collection); + } + + /// + /// Fetch a single taxonomy. + /// + public override ContentstackResponse Fetch(ParameterCollection collection = null) + { + return base.Fetch(collection); + } + + /// + /// Fetch a single taxonomy asynchronously. + /// + public override Task FetchAsync(ParameterCollection collection = null) + { + return base.FetchAsync(collection); + } + + /// + /// Delete a taxonomy. + /// + public override ContentstackResponse Delete(ParameterCollection collection = null) + { + return base.Delete(collection); + } + + /// + /// Delete a taxonomy asynchronously. + /// + public override Task DeleteAsync(ParameterCollection collection = null) + { + return base.DeleteAsync(collection); + } + + /// + /// Export taxonomy. GET {resourcePath}/export with optional query parameters. + /// + public ContentstackResponse Export(ParameterCollection collection = null) + { + stack.ThrowIfNotLoggedIn(); + ThrowIfUidEmpty(); + var service = new FetchDeleteService(stack.client.serializer, stack, resourcePath + "/export", "GET", collection); + return stack.client.InvokeSync(service); + } + + /// + /// Export taxonomy asynchronously. + /// + public Task ExportAsync(ParameterCollection collection = null) + { + stack.ThrowIfNotLoggedIn(); + ThrowIfUidEmpty(); + var service = new FetchDeleteService(stack.client.serializer, stack, resourcePath + "/export", "GET", collection); + return stack.client.InvokeAsync(service); + } + + /// + /// Get taxonomy locales. GET {resourcePath}/locales. + /// + public ContentstackResponse Locales(ParameterCollection collection = null) + { + stack.ThrowIfNotLoggedIn(); + ThrowIfUidEmpty(); + var service = new FetchDeleteService(stack.client.serializer, stack, resourcePath + "/locales", "GET", collection); + return stack.client.InvokeSync(service); + } + + /// + /// Get taxonomy locales asynchronously. + /// + public Task LocalesAsync(ParameterCollection collection = null) + { + stack.ThrowIfNotLoggedIn(); + ThrowIfUidEmpty(); + var service = new FetchDeleteService(stack.client.serializer, stack, resourcePath + "/locales", "GET", collection); + return stack.client.InvokeAsync(service); + } + + /// + /// Localize taxonomy. POST to resourcePath with body { taxonomy: model } and query params (e.g. locale). + /// + public ContentstackResponse Localize(TaxonomyModel model, ParameterCollection collection = null) + { + stack.ThrowIfNotLoggedIn(); + ThrowIfUidEmpty(); + var service = new CreateUpdateService(stack.client.serializer, stack, resourcePath, model, "taxonomy", "POST", collection); + return stack.client.InvokeSync(service); + } + + /// + /// Localize taxonomy asynchronously. + /// + public Task LocalizeAsync(TaxonomyModel model, ParameterCollection collection = null) + { + stack.ThrowIfNotLoggedIn(); + ThrowIfUidEmpty(); + var service = new CreateUpdateService(stack.client.serializer, stack, resourcePath, model, "taxonomy", "POST", collection); + return stack.client.InvokeAsync, ContentstackResponse>(service); + } + + /// + /// Import taxonomy. POST /taxonomies/import with multipart form (taxonomy file). + /// + public ContentstackResponse Import(TaxonomyImportModel model, ParameterCollection collection = null) + { + stack.ThrowIfNotLoggedIn(); + ThrowIfUidNotEmpty(); + var path = resourcePath + "/import"; + var service = new UploadService(stack.client.serializer, stack, path, model, "POST", collection); + return stack.client.InvokeSync(service); + } + + /// + /// Import taxonomy asynchronously. + /// + public Task ImportAsync(TaxonomyImportModel model, ParameterCollection collection = null) + { + stack.ThrowIfNotLoggedIn(); + ThrowIfUidNotEmpty(); + var path = resourcePath + "/import"; + var service = new UploadService(stack.client.serializer, stack, path, model, "POST", collection); + return stack.client.InvokeAsync(service); + } + + /// + /// Get Terms instance for this taxonomy. When termUid is provided, returns a single-term context; otherwise collection for query/create. + /// + /// Optional term UID. If null, returns Terms for querying all terms or creating. + /// + /// + /// stack.Taxonomy("taxonomy_uid").Terms().Query().Find(); + /// stack.Taxonomy("taxonomy_uid").Terms("term_uid").Fetch(); + /// + /// + public Term Terms(string termUid = null) + { + ThrowIfUidEmpty(); + return new Term(stack, Uid, termUid); + } + } +} diff --git a/Contentstack.Management.Core/Models/TaxonomyImportModel.cs b/Contentstack.Management.Core/Models/TaxonomyImportModel.cs new file mode 100644 index 0000000..9630593 --- /dev/null +++ b/Contentstack.Management.Core/Models/TaxonomyImportModel.cs @@ -0,0 +1,48 @@ +using System; +using System.IO; +using System.Net.Http; +using Contentstack.Management.Core.Abstractions; + +namespace Contentstack.Management.Core.Models +{ + /// + /// Model for Taxonomy import (file upload). Implements IUploadInterface for multipart form with key "taxonomy". + /// + public class TaxonomyImportModel : IUploadInterface + { + private readonly Stream _fileStream; + private readonly string _fileName; + + public string ContentType { get; set; } = "multipart/form-data"; + + /// + /// Creates an import model from a file path. + /// + public TaxonomyImportModel(string filePath) + { + if (string.IsNullOrEmpty(filePath)) + throw new ArgumentNullException(nameof(filePath)); + _fileName = Path.GetFileName(filePath); + _fileStream = File.OpenRead(filePath); + } + + /// + /// Creates an import model from a stream (e.g. JSON or CSV taxonomy file). + /// + /// Stream containing taxonomy file content. + /// Name to use for the form part (e.g. "taxonomy.json"). + public TaxonomyImportModel(Stream stream, string fileName = "taxonomy.json") + { + _fileStream = stream ?? throw new ArgumentNullException(nameof(stream)); + _fileName = fileName ?? "taxonomy.json"; + } + + public HttpContent GetHttpContent() + { + var streamContent = new StreamContent(_fileStream); + var content = new MultipartFormDataContent(); + content.Add(streamContent, "taxonomy", _fileName); + return content; + } + } +} diff --git a/Contentstack.Management.Core/Models/TaxonomyModel.cs b/Contentstack.Management.Core/Models/TaxonomyModel.cs new file mode 100644 index 0000000..ee17c90 --- /dev/null +++ b/Contentstack.Management.Core/Models/TaxonomyModel.cs @@ -0,0 +1,44 @@ +using Newtonsoft.Json; + +namespace Contentstack.Management.Core.Models +{ + /// + /// Model for Taxonomy create/update and API response. + /// + [JsonObject(ItemNullValueHandling = NullValueHandling.Ignore)] + public class TaxonomyModel + { + [JsonProperty(propertyName: "uid")] + public string Uid { get; set; } + + [JsonProperty(propertyName: "name")] + public string Name { get; set; } + + [JsonProperty(propertyName: "description")] + public string Description { get; set; } + + [JsonProperty(propertyName: "locale")] + public string Locale { get; set; } + + [JsonProperty(propertyName: "terms_count")] + public int? TermsCount { get; set; } + + [JsonProperty(propertyName: "referenced_terms_count")] + public int? ReferencedTermsCount { get; set; } + + [JsonProperty(propertyName: "referenced_entries_count")] + public int? ReferencedEntriesCount { get; set; } + + [JsonProperty(propertyName: "referenced_content_type_count")] + public int? ReferencedContentTypeCount { get; set; } + + [JsonProperty(propertyName: "created_at")] + public string CreatedAt { get; set; } + + [JsonProperty(propertyName: "updated_at")] + public string UpdatedAt { get; set; } + + [JsonProperty(propertyName: "uuid")] + public string Uuid { get; set; } + } +} diff --git a/Contentstack.Management.Core/Models/Term.cs b/Contentstack.Management.Core/Models/Term.cs new file mode 100644 index 0000000..e48df74 --- /dev/null +++ b/Contentstack.Management.Core/Models/Term.cs @@ -0,0 +1,235 @@ +using System; +using System.Threading.Tasks; +using Contentstack.Management.Core.Queryable; +using Contentstack.Management.Core.Services.Models; + +namespace Contentstack.Management.Core.Models +{ + /// + /// Term represents a single node in a taxonomy hierarchy. Terms can have parent/child relationships. + /// + public class Term : BaseModel + { + private readonly string _taxonomyUid; + + internal Term(Stack stack, string taxonomyUid, string termUid = null) + : base(stack, "term", termUid) + { + _taxonomyUid = taxonomyUid ?? throw new ArgumentNullException(nameof(taxonomyUid)); + resourcePath = $"/taxonomies/{_taxonomyUid}/terms"; + if (!string.IsNullOrEmpty(termUid)) + resourcePath += $"/{termUid}"; + } + + /// + /// Query terms in this taxonomy. Call only when no specific term UID is set (collection). + /// + public Query Query() + { + ThrowIfUidNotEmpty(); + return new Query(stack, resourcePath); + } + + /// + /// Create a term in this taxonomy. + /// + public override ContentstackResponse Create(TermModel model, ParameterCollection collection = null) + { + return base.Create(model, collection); + } + + /// + /// Create a term asynchronously. + /// + public override Task CreateAsync(TermModel model, ParameterCollection collection = null) + { + return base.CreateAsync(model, collection); + } + + /// + /// Update an existing term. + /// + public override ContentstackResponse Update(TermModel model, ParameterCollection collection = null) + { + return base.Update(model, collection); + } + + /// + /// Update an existing term asynchronously. + /// + public override Task UpdateAsync(TermModel model, ParameterCollection collection = null) + { + return base.UpdateAsync(model, collection); + } + + /// + /// Fetch a single term. + /// + public override ContentstackResponse Fetch(ParameterCollection collection = null) + { + return base.Fetch(collection); + } + + /// + /// Fetch a single term asynchronously. + /// + public override Task FetchAsync(ParameterCollection collection = null) + { + return base.FetchAsync(collection); + } + + /// + /// Delete a term. + /// + public override ContentstackResponse Delete(ParameterCollection collection = null) + { + return base.Delete(collection); + } + + /// + /// Delete a term asynchronously. + /// + public override Task DeleteAsync(ParameterCollection collection = null) + { + return base.DeleteAsync(collection); + } + + /// + /// Get ancestor terms of this term. GET {resourcePath}/ancestors. + /// + public ContentstackResponse Ancestors(ParameterCollection collection = null) + { + stack.ThrowIfNotLoggedIn(); + ThrowIfUidEmpty(); + var service = new FetchDeleteService(stack.client.serializer, stack, resourcePath + "/ancestors", "GET", collection); + return stack.client.InvokeSync(service); + } + + /// + /// Get ancestor terms asynchronously. + /// + public Task AncestorsAsync(ParameterCollection collection = null) + { + stack.ThrowIfNotLoggedIn(); + ThrowIfUidEmpty(); + var service = new FetchDeleteService(stack.client.serializer, stack, resourcePath + "/ancestors", "GET", collection); + return stack.client.InvokeAsync(service); + } + + /// + /// Get descendant terms of this term. GET {resourcePath}/descendants. + /// + public ContentstackResponse Descendants(ParameterCollection collection = null) + { + stack.ThrowIfNotLoggedIn(); + ThrowIfUidEmpty(); + var service = new FetchDeleteService(stack.client.serializer, stack, resourcePath + "/descendants", "GET", collection); + return stack.client.InvokeSync(service); + } + + /// + /// Get descendant terms asynchronously. + /// + public Task DescendantsAsync(ParameterCollection collection = null) + { + stack.ThrowIfNotLoggedIn(); + ThrowIfUidEmpty(); + var service = new FetchDeleteService(stack.client.serializer, stack, resourcePath + "/descendants", "GET", collection); + return stack.client.InvokeAsync(service); + } + + /// + /// Move term to a new parent and/or order. PUT {resourcePath}/move with body { term: moveModel }. + /// + public ContentstackResponse Move(TermMoveModel moveModel, ParameterCollection collection = null) + { + stack.ThrowIfNotLoggedIn(); + ThrowIfUidEmpty(); + var service = new CreateUpdateService(stack.client.serializer, stack, resourcePath + "/move", moveModel, "term", "PUT", collection); + return stack.client.InvokeSync(service); + } + + /// + /// Move term asynchronously. + /// + public Task MoveAsync(TermMoveModel moveModel, ParameterCollection collection = null) + { + stack.ThrowIfNotLoggedIn(); + ThrowIfUidEmpty(); + var service = new CreateUpdateService(stack.client.serializer, stack, resourcePath + "/move", moveModel, "term", "PUT", collection); + return stack.client.InvokeAsync, ContentstackResponse>(service); + } + + /// + /// Get term locales. GET {resourcePath}/locales. + /// + public ContentstackResponse Locales(ParameterCollection collection = null) + { + stack.ThrowIfNotLoggedIn(); + ThrowIfUidEmpty(); + var service = new FetchDeleteService(stack.client.serializer, stack, resourcePath + "/locales", "GET", collection); + return stack.client.InvokeSync(service); + } + + /// + /// Get term locales asynchronously. + /// + public Task LocalesAsync(ParameterCollection collection = null) + { + stack.ThrowIfNotLoggedIn(); + ThrowIfUidEmpty(); + var service = new FetchDeleteService(stack.client.serializer, stack, resourcePath + "/locales", "GET", collection); + return stack.client.InvokeAsync(service); + } + + /// + /// Localize term. POST to resourcePath with body { term: model } and query params (e.g. locale). + /// + public ContentstackResponse Localize(TermModel model, ParameterCollection collection = null) + { + stack.ThrowIfNotLoggedIn(); + ThrowIfUidEmpty(); + var service = new CreateUpdateService(stack.client.serializer, stack, resourcePath, model, "term", "POST", collection); + return stack.client.InvokeSync(service); + } + + /// + /// Localize term asynchronously. + /// + public Task LocalizeAsync(TermModel model, ParameterCollection collection = null) + { + stack.ThrowIfNotLoggedIn(); + ThrowIfUidEmpty(); + var service = new CreateUpdateService(stack.client.serializer, stack, resourcePath, model, "term", "POST", collection); + return stack.client.InvokeAsync, ContentstackResponse>(service); + } + + /// + /// Search terms across all taxonomies. GET /taxonomies/$all/terms with typeahead query param. Callable only when no specific term UID is set. + /// + /// Search string for typeahead. + /// Optional additional query parameters. + public ContentstackResponse Search(string typeahead, ParameterCollection collection = null) + { + stack.ThrowIfNotLoggedIn(); + ThrowIfUidNotEmpty(); + var coll = collection ?? new ParameterCollection(); + coll.Add("typeahead", typeahead ?? string.Empty); + var service = new FetchDeleteService(stack.client.serializer, stack, "/taxonomies/$all/terms", "GET", coll); + return stack.client.InvokeSync(service); + } + + /// + /// Search terms across all taxonomies asynchronously. + /// + public Task SearchAsync(string typeahead, ParameterCollection collection = null) + { + stack.ThrowIfNotLoggedIn(); + ThrowIfUidNotEmpty(); + var coll = collection ?? new ParameterCollection(); + coll.Add("typeahead", typeahead ?? string.Empty); + var service = new FetchDeleteService(stack.client.serializer, stack, "/taxonomies/$all/terms", "GET", coll); + return stack.client.InvokeAsync(service); + } + } +} diff --git a/Contentstack.Management.Core/Models/TermModel.cs b/Contentstack.Management.Core/Models/TermModel.cs new file mode 100644 index 0000000..7e9b58d --- /dev/null +++ b/Contentstack.Management.Core/Models/TermModel.cs @@ -0,0 +1,83 @@ +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace Contentstack.Management.Core.Models +{ + /// + /// Model for Term create/update and API response. + /// + [JsonObject(ItemNullValueHandling = NullValueHandling.Ignore)] + public class TermModel + { + [JsonProperty(propertyName: "uid")] + public string Uid { get; set; } + + [JsonProperty(propertyName: "name")] + public string Name { get; set; } + + [JsonProperty(propertyName: "taxonomy_uid")] + public string TaxonomyUid { get; set; } + + [JsonProperty(propertyName: "parent_uid")] + public string ParentUid { get; set; } + + [JsonProperty(propertyName: "depth")] + public int? Depth { get; set; } + + [JsonProperty(propertyName: "children_count")] + public int? ChildrenCount { get; set; } + + [JsonProperty(propertyName: "referenced_entries_count")] + public int? ReferencedEntriesCount { get; set; } + + [JsonProperty(propertyName: "ancestors")] + public List Ancestors { get; set; } + + [JsonProperty(propertyName: "descendants")] + public List Descendants { get; set; } + + [JsonProperty(propertyName: "created_at")] + public string CreatedAt { get; set; } + + [JsonProperty(propertyName: "updated_at")] + public string UpdatedAt { get; set; } + } + + /// + /// Represents an ancestor or descendant term in hierarchy. + /// + [JsonObject(ItemNullValueHandling = NullValueHandling.Ignore)] + public class TermAncestorDescendant + { + [JsonProperty(propertyName: "uid")] + public string Uid { get; set; } + + [JsonProperty(propertyName: "name")] + public string Name { get; set; } + + [JsonProperty(propertyName: "parent_uid")] + public string ParentUid { get; set; } + + [JsonProperty(propertyName: "depth")] + public int? Depth { get; set; } + + [JsonProperty(propertyName: "children_count")] + public int? ChildrenCount { get; set; } + + [JsonProperty(propertyName: "referenced_entries_count")] + public int? ReferencedEntriesCount { get; set; } + } + + /// + /// Model for Term move operation (parent_uid, order). + /// + [JsonObject(ItemNullValueHandling = NullValueHandling.Ignore)] + public class TermMoveModel + { + [JsonProperty(propertyName: "parent_uid")] + public string ParentUid { get; set; } + + [JsonProperty(propertyName: "order")] + public int? Order { get; set; } + } +} diff --git a/Contentstack.Management.Core/Services/Models/UploadService.cs b/Contentstack.Management.Core/Services/Models/UploadService.cs index d62c948..7520af6 100644 --- a/Contentstack.Management.Core/Services/Models/UploadService.cs +++ b/Contentstack.Management.Core/Services/Models/UploadService.cs @@ -1,7 +1,8 @@ -using System; +using System; using System.IO; using System.Net.Http; using Contentstack.Management.Core.Abstractions; +using Contentstack.Management.Core.Queryable; using Newtonsoft.Json; using Contentstack.Management.Core.Utils; @@ -11,8 +12,8 @@ internal class UploadService: ContentstackService { private readonly IUploadInterface _uploadInterface; - internal UploadService(JsonSerializer serializer, Core.Models.Stack stack, string resourcePath, IUploadInterface uploadInterface, string httpMethod = "POST") - : base(serializer, stack: stack) + internal UploadService(JsonSerializer serializer, Core.Models.Stack stack, string resourcePath, IUploadInterface uploadInterface, string httpMethod = "POST", ParameterCollection collection = null) + : base(serializer, stack: stack, collection) { if (stack.APIKey == null) { @@ -29,6 +30,10 @@ internal UploadService(JsonSerializer serializer, Core.Models.Stack stack, strin this.ResourcePath = resourcePath; this.HttpMethod = httpMethod; _uploadInterface = uploadInterface; + if (collection != null && collection.Count > 0) + { + this.UseQueryString = true; + } } public override void ContentBody() From 5d8ebaae3df717e8586578c1a9bc6dc6b100028c Mon Sep 17 00:00:00 2001 From: OMpawar-21 Date: Tue, 10 Mar 2026 18:51:53 +0530 Subject: [PATCH 2/3] Added the negative test flow in both integration and unit testcases. --- .../Contentstack017_TaxonomyTest.cs | 17 +++++ .../Models/TaxonomyImportModelTest.cs | 32 ++++++++++ .../Models/TaxonomyTest.cs | 64 +++++++++++++++++++ .../Models/TermTest.cs | 40 ++++++++++++ 4 files changed, 153 insertions(+) create mode 100644 Contentstack.Management.Core.Unit.Tests/Models/TaxonomyImportModelTest.cs diff --git a/Contentstack.Management.Core.Tests/IntegrationTest/Contentstack017_TaxonomyTest.cs b/Contentstack.Management.Core.Tests/IntegrationTest/Contentstack017_TaxonomyTest.cs index 1f4138a..29a8823 100644 --- a/Contentstack.Management.Core.Tests/IntegrationTest/Contentstack017_TaxonomyTest.cs +++ b/Contentstack.Management.Core.Tests/IntegrationTest/Contentstack017_TaxonomyTest.cs @@ -1,5 +1,6 @@ using System; using System.Threading.Tasks; +using Contentstack.Management.Core.Exceptions; using Contentstack.Management.Core.Models; using Contentstack.Management.Core.Tests.Model; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -104,5 +105,21 @@ public void Test006_Should_Delete_Taxonomy() ContentstackResponse response = _stack.Taxonomy(_taxonomyUid).Delete(); Assert.IsTrue(response.IsSuccessStatusCode, $"Delete failed: {response.OpenResponse()}"); } + + [TestMethod] + [DoNotParallelize] + public void Test007_Should_Throw_When_Fetch_NonExistent_Taxonomy() + { + Assert.ThrowsException(() => + _stack.Taxonomy("non_existent_taxonomy_uid_12345").Fetch()); + } + + [TestMethod] + [DoNotParallelize] + public void Test008_Should_Throw_When_Delete_NonExistent_Taxonomy() + { + Assert.ThrowsException(() => + _stack.Taxonomy("non_existent_taxonomy_uid_12345").Delete()); + } } } diff --git a/Contentstack.Management.Core.Unit.Tests/Models/TaxonomyImportModelTest.cs b/Contentstack.Management.Core.Unit.Tests/Models/TaxonomyImportModelTest.cs new file mode 100644 index 0000000..72f131f --- /dev/null +++ b/Contentstack.Management.Core.Unit.Tests/Models/TaxonomyImportModelTest.cs @@ -0,0 +1,32 @@ +using System; +using System.IO; +using Contentstack.Management.Core.Models; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Contentstack.Management.Core.Unit.Tests.Models +{ + [TestClass] + public class TaxonomyImportModelTest + { + [TestMethod] + public void Throws_When_FilePath_Is_Null() + { + var ex = Assert.ThrowsException(() => new TaxonomyImportModel((string)null)); + Assert.AreEqual("filePath", ex.ParamName); + } + + [TestMethod] + public void Throws_When_FilePath_Is_Empty() + { + var ex = Assert.ThrowsException(() => new TaxonomyImportModel("")); + Assert.AreEqual("filePath", ex.ParamName); + } + + [TestMethod] + public void Throws_When_Stream_Is_Null() + { + var ex = Assert.ThrowsException(() => new TaxonomyImportModel((Stream)null, "taxonomy.json")); + Assert.AreEqual("stream", ex.ParamName); + } + } +} diff --git a/Contentstack.Management.Core.Unit.Tests/Models/TaxonomyTest.cs b/Contentstack.Management.Core.Unit.Tests/Models/TaxonomyTest.cs index 14cd4ee..864cd94 100644 --- a/Contentstack.Management.Core.Unit.Tests/Models/TaxonomyTest.cs +++ b/Contentstack.Management.Core.Unit.Tests/Models/TaxonomyTest.cs @@ -1,9 +1,11 @@ using System; +using System.Net; using AutoFixture; using Contentstack.Management.Core.Models; using Contentstack.Management.Core.Queryable; using Contentstack.Management.Core.Unit.Tests.Mokes; using Microsoft.VisualStudio.TestTools.UnitTesting; +using Newtonsoft.Json; namespace Contentstack.Management.Core.Unit.Tests.Models { @@ -130,5 +132,67 @@ public void Should_Get_Single_Term_From_Taxonomy() Assert.AreEqual(termUid, term.Uid); Assert.AreEqual($"/taxonomies/{taxonomyUid}/terms/{termUid}", term.resourcePath); } + + [TestMethod] + public void Export_Throws_When_Uid_Is_Empty() + { + Assert.ThrowsException(() => _stack.Taxonomy().Export()); + Assert.ThrowsExceptionAsync(() => _stack.Taxonomy().ExportAsync()); + } + + [TestMethod] + public void Locales_Throws_When_Uid_Is_Empty() + { + Assert.ThrowsException(() => _stack.Taxonomy().Locales()); + Assert.ThrowsExceptionAsync(() => _stack.Taxonomy().LocalesAsync()); + } + + [TestMethod] + public void Localize_Throws_When_Uid_Is_Empty() + { + Assert.ThrowsException(() => _stack.Taxonomy().Localize(_fixture.Create())); + Assert.ThrowsExceptionAsync(() => _stack.Taxonomy().LocalizeAsync(_fixture.Create())); + } + + [TestMethod] + public void Import_Throws_When_Uid_Is_Set() + { + using (var stream = new System.IO.MemoryStream(System.Text.Encoding.UTF8.GetBytes("{}"))) + { + var model = new TaxonomyImportModel(stream, "taxonomy.json"); + Assert.ThrowsException(() => _stack.Taxonomy("some_uid").Import(model)); + Assert.ThrowsExceptionAsync(() => _stack.Taxonomy("some_uid").ImportAsync(model)); + } + } + + [TestMethod] + public void Create_Throws_When_Uid_Is_Set() + { + Assert.ThrowsException(() => _stack.Taxonomy(_fixture.Create()).Create(_fixture.Create())); + Assert.ThrowsExceptionAsync(() => _stack.Taxonomy(_fixture.Create()).CreateAsync(_fixture.Create())); + } + + [TestMethod] + public void Query_Throws_When_Uid_Is_Set() + { + Assert.ThrowsException(() => _stack.Taxonomy(_fixture.Create()).Query()); + } + + [TestMethod] + public void Localize_When_Api_Returns_400_Returns_Unsuccessful_Response() + { + var httpMsg = MockResponse.Create(HttpStatusCode.BadRequest, null, "{\"error_message\":\"Invalid locale\",\"error_code\":400}"); + var badResponse = new ContentstackResponse(httpMsg, JsonSerializer.Create(new JsonSerializerSettings())); + var client = new ContentstackClient(); + client.ContentstackPipeline.ReplaceHandler(new MockHttpHandler(badResponse)); + client.contentstackOptions.Authtoken = _fixture.Create(); + var stack = new Stack(client, _fixture.Create()); + string uid = _fixture.Create(); + + ContentstackResponse response = stack.Taxonomy(uid).Localize(_fixture.Create()); + + Assert.IsFalse(response.IsSuccessStatusCode); + Assert.AreEqual(HttpStatusCode.BadRequest, response.StatusCode); + } } } diff --git a/Contentstack.Management.Core.Unit.Tests/Models/TermTest.cs b/Contentstack.Management.Core.Unit.Tests/Models/TermTest.cs index 223840e..9b62686 100644 --- a/Contentstack.Management.Core.Unit.Tests/Models/TermTest.cs +++ b/Contentstack.Management.Core.Unit.Tests/Models/TermTest.cs @@ -134,5 +134,45 @@ public async System.Threading.Tasks.Task Should_Search_Terms_Async() Assert.AreEqual(_contentstackResponse.OpenResponse(), response.OpenResponse()); Assert.AreEqual(_contentstackResponse.OpenJObjectResponse().ToString(), response.OpenJObjectResponse().ToString()); } + + [TestMethod] + public void Ancestors_Throws_When_Term_Uid_Is_Empty() + { + string taxonomyUid = _fixture.Create(); + Assert.ThrowsException(() => _stack.Taxonomy(taxonomyUid).Terms().Ancestors()); + Assert.ThrowsExceptionAsync(() => _stack.Taxonomy(taxonomyUid).Terms().AncestorsAsync()); + } + + [TestMethod] + public void Descendants_Throws_When_Term_Uid_Is_Empty() + { + string taxonomyUid = _fixture.Create(); + Assert.ThrowsException(() => _stack.Taxonomy(taxonomyUid).Terms().Descendants()); + Assert.ThrowsExceptionAsync(() => _stack.Taxonomy(taxonomyUid).Terms().DescendantsAsync()); + } + + [TestMethod] + public void Move_Throws_When_Term_Uid_Is_Empty() + { + string taxonomyUid = _fixture.Create(); + Assert.ThrowsException(() => _stack.Taxonomy(taxonomyUid).Terms().Move(_fixture.Create())); + Assert.ThrowsExceptionAsync(() => _stack.Taxonomy(taxonomyUid).Terms().MoveAsync(_fixture.Create())); + } + + [TestMethod] + public void Locales_Throws_When_Term_Uid_Is_Empty() + { + string taxonomyUid = _fixture.Create(); + Assert.ThrowsException(() => _stack.Taxonomy(taxonomyUid).Terms().Locales()); + Assert.ThrowsExceptionAsync(() => _stack.Taxonomy(taxonomyUid).Terms().LocalesAsync()); + } + + [TestMethod] + public void Localize_Throws_When_Term_Uid_Is_Empty() + { + string taxonomyUid = _fixture.Create(); + Assert.ThrowsException(() => _stack.Taxonomy(taxonomyUid).Terms().Localize(_fixture.Create())); + Assert.ThrowsExceptionAsync(() => _stack.Taxonomy(taxonomyUid).Terms().LocalizeAsync(_fixture.Create())); + } } } From 0c4c9c89c04471d4193251ddf021156b0e345786 Mon Sep 17 00:00:00 2001 From: OMpawar-21 Date: Wed, 11 Mar 2026 12:36:31 +0530 Subject: [PATCH 3/3] feat: taxonomy feature, test cases --- .../Contentstack017_TaxonomyTest.cs | 629 +++++++++++++++++- .../Model/Models.cs | 15 + .../contentstack.management.core.csproj | 4 +- 3 files changed, 642 insertions(+), 6 deletions(-) diff --git a/Contentstack.Management.Core.Tests/IntegrationTest/Contentstack017_TaxonomyTest.cs b/Contentstack.Management.Core.Tests/IntegrationTest/Contentstack017_TaxonomyTest.cs index 29a8823..e57c031 100644 --- a/Contentstack.Management.Core.Tests/IntegrationTest/Contentstack017_TaxonomyTest.cs +++ b/Contentstack.Management.Core.Tests/IntegrationTest/Contentstack017_TaxonomyTest.cs @@ -1,9 +1,15 @@ using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Text; using System.Threading.Tasks; using Contentstack.Management.Core.Exceptions; using Contentstack.Management.Core.Models; +using Contentstack.Management.Core.Queryable; using Contentstack.Management.Core.Tests.Model; using Microsoft.VisualStudio.TestTools.UnitTesting; +using Newtonsoft.Json.Linq; namespace Contentstack.Management.Core.Tests.IntegrationTest { @@ -11,6 +17,13 @@ namespace Contentstack.Management.Core.Tests.IntegrationTest public class Contentstack017_TaxonomyTest { private static string _taxonomyUid; + private static string _asyncCreatedTaxonomyUid; + private static string _importedTaxonomyUid; + private static string _testLocaleCode; + private static bool _weCreatedTestLocale; + private static List _createdTermUids; + private static string _rootTermUid; + private static string _childTermUid; private Stack _stack; private TaxonomyModel _createModel; @@ -21,6 +34,7 @@ public void Initialize() _stack = Contentstack.Client.Stack(response.Stack.APIKey); if (_taxonomyUid == null) _taxonomyUid = "taxonomy_integration_test_" + Guid.NewGuid().ToString("N").Substring(0, 8); + _createdTermUids = _createdTermUids ?? new List(); _createModel = new TaxonomyModel { Uid = _taxonomyUid, @@ -100,15 +114,500 @@ public async Task Test005_Should_Fetch_Taxonomy_Async() [TestMethod] [DoNotParallelize] - public void Test006_Should_Delete_Taxonomy() + public async Task Test006_Should_Create_Taxonomy_Async() { - ContentstackResponse response = _stack.Taxonomy(_taxonomyUid).Delete(); - Assert.IsTrue(response.IsSuccessStatusCode, $"Delete failed: {response.OpenResponse()}"); + _asyncCreatedTaxonomyUid = "taxonomy_async_" + Guid.NewGuid().ToString("N").Substring(0, 8); + var model = new TaxonomyModel + { + Uid = _asyncCreatedTaxonomyUid, + Name = "Taxonomy Async Create Test", + Description = "Created via CreateAsync" + }; + ContentstackResponse response = await _stack.Taxonomy().CreateAsync(model); + Assert.IsTrue(response.IsSuccessStatusCode, $"CreateAsync failed: {response.OpenResponse()}"); + var wrapper = response.OpenTResponse(); + Assert.IsNotNull(wrapper?.Taxonomy); + Assert.AreEqual(_asyncCreatedTaxonomyUid, wrapper.Taxonomy.Uid); } [TestMethod] [DoNotParallelize] - public void Test007_Should_Throw_When_Fetch_NonExistent_Taxonomy() + public async Task Test007_Should_Update_Taxonomy_Async() + { + var updateModel = new TaxonomyModel + { + Name = "Taxonomy Integration Test Updated Async", + Description = "Updated via UpdateAsync" + }; + ContentstackResponse response = await _stack.Taxonomy(_taxonomyUid).UpdateAsync(updateModel); + Assert.IsTrue(response.IsSuccessStatusCode, $"UpdateAsync failed: {response.OpenResponse()}"); + var wrapper = response.OpenTResponse(); + Assert.IsNotNull(wrapper?.Taxonomy); + Assert.AreEqual("Taxonomy Integration Test Updated Async", wrapper.Taxonomy.Name); + } + + [TestMethod] + [DoNotParallelize] + public async Task Test008_Should_Query_Taxonomies_Async() + { + ContentstackResponse response = await _stack.Taxonomy().Query().FindAsync(); + Assert.IsTrue(response.IsSuccessStatusCode, $"Query FindAsync failed: {response.OpenResponse()}"); + var wrapper = response.OpenTResponse(); + Assert.IsNotNull(wrapper?.Taxonomies); + Assert.IsTrue(wrapper.Taxonomies.Count >= 0); + } + + [TestMethod] + [DoNotParallelize] + public void Test009_Should_Get_Taxonomy_Locales() + { + ContentstackResponse response = _stack.Taxonomy(_taxonomyUid).Locales(); + Assert.IsTrue(response.IsSuccessStatusCode, $"Locales failed: {response.OpenResponse()}"); + var jobj = response.OpenJObjectResponse(); + Assert.IsNotNull(jobj["taxonomies"]); + } + + [TestMethod] + [DoNotParallelize] + public async Task Test010_Should_Get_Taxonomy_Locales_Async() + { + ContentstackResponse response = await _stack.Taxonomy(_taxonomyUid).LocalesAsync(); + Assert.IsTrue(response.IsSuccessStatusCode, $"LocalesAsync failed: {response.OpenResponse()}"); + var jobj = response.OpenJObjectResponse(); + Assert.IsNotNull(jobj["taxonomies"]); + } + + [TestMethod] + [DoNotParallelize] + public void Test011_Should_Localize_Taxonomy() + { + _weCreatedTestLocale = false; + ContentstackResponse localesResponse = _stack.Locale().Query().Find(); + Assert.IsTrue(localesResponse.IsSuccessStatusCode, $"Query locales failed: {localesResponse.OpenResponse()}"); + var jobj = localesResponse.OpenJObjectResponse(); + var localesArray = jobj["locales"] as JArray ?? jobj["items"] as JArray; + if (localesArray == null || localesArray.Count == 0) + { + Assert.Inconclusive("Stack has no locales; skipping taxonomy localize tests."); + return; + } + string masterLocale = "en-us"; + _testLocaleCode = null; + foreach (var item in localesArray) + { + var code = item["code"]?.ToString(); + if (string.IsNullOrEmpty(code)) continue; + if (!string.Equals(code, masterLocale, StringComparison.OrdinalIgnoreCase)) + { + _testLocaleCode = code; + break; + } + } + if (string.IsNullOrEmpty(_testLocaleCode)) + { + try + { + _testLocaleCode = "hi-in"; + var localeModel = new LocaleModel + { + Code = _testLocaleCode, + Name = "Hindi (India)" + }; + ContentstackResponse createResponse = _stack.Locale().Create(localeModel); + if (createResponse.IsSuccessStatusCode) + _weCreatedTestLocale = true; + else + _testLocaleCode = null; + } + catch (ContentstackErrorException) + { + _testLocaleCode = null; + } + } + if (string.IsNullOrEmpty(_testLocaleCode)) + { + Assert.Inconclusive("Stack has no non-master locale and could not create one; skipping taxonomy localize tests."); + return; + } + + var localizeModel = new TaxonomyModel + { + Uid = _taxonomyUid, + Name = "Taxonomy Localized", + Description = "Localized description" + }; + var coll = new ParameterCollection(); + coll.Add("locale", _testLocaleCode); + ContentstackResponse response = _stack.Taxonomy(_taxonomyUid).Localize(localizeModel, coll); + Assert.IsTrue(response.IsSuccessStatusCode, $"Localize failed: {response.OpenResponse()}"); + var wrapper = response.OpenTResponse(); + Assert.IsNotNull(wrapper?.Taxonomy); + if (!string.IsNullOrEmpty(wrapper.Taxonomy.Locale)) + Assert.AreEqual(_testLocaleCode, wrapper.Taxonomy.Locale); + } + + [TestMethod] + [DoNotParallelize] + public void Test013_Should_Throw_When_Localize_With_Invalid_Locale() + { + var localizeModel = new TaxonomyModel + { + Uid = _taxonomyUid, + Name = "Invalid", + Description = "Invalid" + }; + var coll = new ParameterCollection(); + coll.Add("locale", "invalid_locale_code_xyz"); + Assert.ThrowsException(() => + _stack.Taxonomy(_taxonomyUid).Localize(localizeModel, coll)); + } + + [TestMethod] + [DoNotParallelize] + public void Test014_Should_Import_Taxonomy() + { + string importUid = "taxonomy_import_" + Guid.NewGuid().ToString("N").Substring(0, 8); + string json = $"{{\"taxonomy\":{{\"uid\":\"{importUid}\",\"name\":\"Imported Taxonomy\",\"description\":\"Imported\"}}}}"; + using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(json))) + { + var importModel = new TaxonomyImportModel(stream, "taxonomy.json"); + ContentstackResponse response = _stack.Taxonomy().Import(importModel); + Assert.IsTrue(response.IsSuccessStatusCode, $"Import failed: {response.OpenResponse()}"); + var wrapper = response.OpenTResponse(); + Assert.IsNotNull(wrapper?.Taxonomy); + _importedTaxonomyUid = wrapper.Taxonomy.Uid ?? importUid; + } + } + + [TestMethod] + [DoNotParallelize] + public async Task Test015_Should_Import_Taxonomy_Async() + { + string importUid = "taxonomy_import_async_" + Guid.NewGuid().ToString("N").Substring(0, 8); + string json = $"{{\"taxonomy\":{{\"uid\":\"{importUid}\",\"name\":\"Imported Async\",\"description\":\"Imported via Async\"}}}}"; + using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(json))) + { + var importModel = new TaxonomyImportModel(stream, "taxonomy.json"); + ContentstackResponse response = await _stack.Taxonomy().ImportAsync(importModel); + Assert.IsTrue(response.IsSuccessStatusCode, $"ImportAsync failed: {response.OpenResponse()}"); + var wrapper = response.OpenTResponse(); + Assert.IsNotNull(wrapper?.Taxonomy); + } + } + + [TestMethod] + [DoNotParallelize] + public void Test016_Should_Create_Root_Term() + { + _rootTermUid = "term_root_" + Guid.NewGuid().ToString("N").Substring(0, 8); + var termModel = new TermModel + { + Uid = _rootTermUid, + Name = "Root Term", + ParentUid = null + }; + ContentstackResponse response = _stack.Taxonomy(_taxonomyUid).Terms().Create(termModel); + Assert.IsTrue(response.IsSuccessStatusCode, $"Create term failed: {response.OpenResponse()}"); + var wrapper = response.OpenTResponse(); + Assert.IsNotNull(wrapper?.Term); + Assert.AreEqual(_rootTermUid, wrapper.Term.Uid); + _createdTermUids.Add(_rootTermUid); + } + + [TestMethod] + [DoNotParallelize] + public void Test017_Should_Create_Child_Term() + { + _childTermUid = "term_child_" + Guid.NewGuid().ToString("N").Substring(0, 8); + var termModel = new TermModel + { + Uid = _childTermUid, + Name = "Child Term", + ParentUid = _rootTermUid + }; + ContentstackResponse response = _stack.Taxonomy(_taxonomyUid).Terms().Create(termModel); + Assert.IsTrue(response.IsSuccessStatusCode, $"Create child term failed: {response.OpenResponse()}"); + var wrapper = response.OpenTResponse(); + Assert.IsNotNull(wrapper?.Term); + Assert.AreEqual(_childTermUid, wrapper.Term.Uid); + _createdTermUids.Add(_childTermUid); + } + + [TestMethod] + [DoNotParallelize] + public void Test018_Should_Fetch_Term() + { + ContentstackResponse response = _stack.Taxonomy(_taxonomyUid).Terms(_rootTermUid).Fetch(); + Assert.IsTrue(response.IsSuccessStatusCode, $"Fetch term failed: {response.OpenResponse()}"); + var wrapper = response.OpenTResponse(); + Assert.IsNotNull(wrapper?.Term); + Assert.AreEqual(_rootTermUid, wrapper.Term.Uid); + } + + [TestMethod] + [DoNotParallelize] + public async Task Test019_Should_Fetch_Term_Async() + { + ContentstackResponse response = await _stack.Taxonomy(_taxonomyUid).Terms(_rootTermUid).FetchAsync(); + Assert.IsTrue(response.IsSuccessStatusCode, $"FetchAsync term failed: {response.OpenResponse()}"); + var wrapper = response.OpenTResponse(); + Assert.IsNotNull(wrapper?.Term); + Assert.AreEqual(_rootTermUid, wrapper.Term.Uid); + } + + [TestMethod] + [DoNotParallelize] + public void Test020_Should_Query_Terms() + { + ContentstackResponse response = _stack.Taxonomy(_taxonomyUid).Terms().Query().Find(); + Assert.IsTrue(response.IsSuccessStatusCode, $"Query terms failed: {response.OpenResponse()}"); + var wrapper = response.OpenTResponse(); + Assert.IsNotNull(wrapper?.Terms); + Assert.IsTrue(wrapper.Terms.Count >= 0); + } + + [TestMethod] + [DoNotParallelize] + public async Task Test021_Should_Query_Terms_Async() + { + ContentstackResponse response = await _stack.Taxonomy(_taxonomyUid).Terms().Query().FindAsync(); + Assert.IsTrue(response.IsSuccessStatusCode, $"Query terms Async failed: {response.OpenResponse()}"); + var wrapper = response.OpenTResponse(); + Assert.IsNotNull(wrapper?.Terms); + Assert.IsTrue(wrapper.Terms.Count >= 0); + } + + [TestMethod] + [DoNotParallelize] + public void Test022_Should_Update_Term() + { + var updateModel = new TermModel + { + Name = "Root Term Updated", + ParentUid = null + }; + ContentstackResponse response = _stack.Taxonomy(_taxonomyUid).Terms(_rootTermUid).Update(updateModel); + Assert.IsTrue(response.IsSuccessStatusCode, $"Update term failed: {response.OpenResponse()}"); + var wrapper = response.OpenTResponse(); + Assert.IsNotNull(wrapper?.Term); + Assert.AreEqual("Root Term Updated", wrapper.Term.Name); + } + + [TestMethod] + [DoNotParallelize] + public async Task Test023_Should_Update_Term_Async() + { + var updateModel = new TermModel + { + Name = "Root Term Updated Async", + ParentUid = null + }; + ContentstackResponse response = await _stack.Taxonomy(_taxonomyUid).Terms(_rootTermUid).UpdateAsync(updateModel); + Assert.IsTrue(response.IsSuccessStatusCode, $"UpdateAsync term failed: {response.OpenResponse()}"); + var wrapper = response.OpenTResponse(); + Assert.IsNotNull(wrapper?.Term); + Assert.AreEqual("Root Term Updated Async", wrapper.Term.Name); + } + + [TestMethod] + [DoNotParallelize] + public void Test024_Should_Get_Term_Ancestors() + { + ContentstackResponse response = _stack.Taxonomy(_taxonomyUid).Terms(_childTermUid).Ancestors(); + Assert.IsTrue(response.IsSuccessStatusCode, $"Ancestors failed: {response.OpenResponse()}"); + var jobj = response.OpenJObjectResponse(); + Assert.IsNotNull(jobj); + } + + [TestMethod] + [DoNotParallelize] + public async Task Test025_Should_Get_Term_Ancestors_Async() + { + ContentstackResponse response = await _stack.Taxonomy(_taxonomyUid).Terms(_childTermUid).AncestorsAsync(); + Assert.IsTrue(response.IsSuccessStatusCode, $"AncestorsAsync failed: {response.OpenResponse()}"); + var jobj = response.OpenJObjectResponse(); + Assert.IsNotNull(jobj); + } + + [TestMethod] + [DoNotParallelize] + public void Test026_Should_Get_Term_Descendants() + { + ContentstackResponse response = _stack.Taxonomy(_taxonomyUid).Terms(_rootTermUid).Descendants(); + Assert.IsTrue(response.IsSuccessStatusCode, $"Descendants failed: {response.OpenResponse()}"); + var jobj = response.OpenJObjectResponse(); + Assert.IsNotNull(jobj); + } + + [TestMethod] + [DoNotParallelize] + public async Task Test027_Should_Get_Term_Descendants_Async() + { + ContentstackResponse response = await _stack.Taxonomy(_taxonomyUid).Terms(_rootTermUid).DescendantsAsync(); + Assert.IsTrue(response.IsSuccessStatusCode, $"DescendantsAsync failed: {response.OpenResponse()}"); + var jobj = response.OpenJObjectResponse(); + Assert.IsNotNull(jobj); + } + + [TestMethod] + [DoNotParallelize] + public void Test028_Should_Get_Term_Locales() + { + ContentstackResponse response = _stack.Taxonomy(_taxonomyUid).Terms(_rootTermUid).Locales(); + Assert.IsTrue(response.IsSuccessStatusCode, $"Term Locales failed: {response.OpenResponse()}"); + var jobj = response.OpenJObjectResponse(); + Assert.IsNotNull(jobj["terms"]); + } + + [TestMethod] + [DoNotParallelize] + public async Task Test029_Should_Get_Term_Locales_Async() + { + ContentstackResponse response = await _stack.Taxonomy(_taxonomyUid).Terms(_rootTermUid).LocalesAsync(); + Assert.IsTrue(response.IsSuccessStatusCode, $"Term LocalesAsync failed: {response.OpenResponse()}"); + var jobj = response.OpenJObjectResponse(); + Assert.IsNotNull(jobj["terms"]); + } + + [TestMethod] + [DoNotParallelize] + public void Test030_Should_Localize_Term() + { + if (string.IsNullOrEmpty(_testLocaleCode)) + { + Assert.Inconclusive("No non-master locale available."); + return; + } + var localizeModel = new TermModel + { + Uid = _rootTermUid, + Name = "Root Term Localized", + ParentUid = null + }; + var coll = new ParameterCollection(); + coll.Add("locale", _testLocaleCode); + ContentstackResponse response = _stack.Taxonomy(_taxonomyUid).Terms(_rootTermUid).Localize(localizeModel, coll); + Assert.IsTrue(response.IsSuccessStatusCode, $"Term Localize failed: {response.OpenResponse()}"); + var wrapper = response.OpenTResponse(); + Assert.IsNotNull(wrapper?.Term); + } + + [TestMethod] + [DoNotParallelize] + public void Test032_Should_Move_Term() + { + var moveModel = new TermMoveModel + { + ParentUid = _rootTermUid, + Order = 0 + }; + ContentstackResponse response = null; + try + { + response = _stack.Taxonomy(_taxonomyUid).Terms(_childTermUid).Move(moveModel, null); + } + catch (ContentstackErrorException) + { + try + { + var coll = new ParameterCollection(); + coll.Add("force", true); + response = _stack.Taxonomy(_taxonomyUid).Terms(_childTermUid).Move(moveModel, coll); + } + catch (ContentstackErrorException ex) + { + Assert.Inconclusive("Move term failed: {0}", ex.Message); + return; + } + } + Assert.IsTrue(response.IsSuccessStatusCode, $"Move term failed: {response.OpenResponse()}"); + var wrapper = response.OpenTResponse(); + Assert.IsNotNull(wrapper?.Term); + } + + [TestMethod] + [DoNotParallelize] + public async Task Test033_Should_Move_Term_Async() + { + var moveModel = new TermMoveModel + { + ParentUid = _rootTermUid, + Order = 1 + }; + ContentstackResponse response = null; + try + { + response = await _stack.Taxonomy(_taxonomyUid).Terms(_childTermUid).MoveAsync(moveModel, null); + } + catch (ContentstackErrorException) + { + try + { + var coll = new ParameterCollection(); + coll.Add("force", true); + response = await _stack.Taxonomy(_taxonomyUid).Terms(_childTermUid).MoveAsync(moveModel, coll); + } + catch (ContentstackErrorException ex) + { + Assert.Inconclusive("Move term failed: {0}", ex.Message); + return; + } + } + Assert.IsTrue(response.IsSuccessStatusCode, $"MoveAsync term failed: {response.OpenResponse()}"); + var wrapper = response.OpenTResponse(); + Assert.IsNotNull(wrapper?.Term); + } + + [TestMethod] + [DoNotParallelize] + public void Test034_Should_Search_Terms() + { + ContentstackResponse response = _stack.Taxonomy(_taxonomyUid).Terms().Search("Root"); + Assert.IsTrue(response.IsSuccessStatusCode, $"Search terms failed: {response.OpenResponse()}"); + var jobj = response.OpenJObjectResponse(); + Assert.IsNotNull(jobj["terms"] ?? jobj["items"]); + } + + [TestMethod] + [DoNotParallelize] + public async Task Test035_Should_Search_Terms_Async() + { + ContentstackResponse response = await _stack.Taxonomy(_taxonomyUid).Terms().SearchAsync("Root"); + Assert.IsTrue(response.IsSuccessStatusCode, $"SearchAsync terms failed: {response.OpenResponse()}"); + var jobj = response.OpenJObjectResponse(); + Assert.IsNotNull(jobj["terms"] ?? jobj["items"]); + } + + [TestMethod] + [DoNotParallelize] + public void Test036_Should_Create_Term_Async() + { + string termUid = "term_async_" + Guid.NewGuid().ToString("N").Substring(0, 8); + var termModel = new TermModel + { + Uid = termUid, + Name = "Async Term", + ParentUid = _rootTermUid + }; + ContentstackResponse response = _stack.Taxonomy(_taxonomyUid).Terms().CreateAsync(termModel).GetAwaiter().GetResult(); + Assert.IsTrue(response.IsSuccessStatusCode, $"CreateAsync term failed: {response.OpenResponse()}"); + var wrapper = response.OpenTResponse(); + Assert.IsNotNull(wrapper?.Term); + _createdTermUids.Add(termUid); + } + + [TestMethod] + [DoNotParallelize] + public void Test037_Should_Throw_When_Update_NonExistent_Taxonomy() + { + var updateModel = new TaxonomyModel { Name = "No", Description = "No" }; + Assert.ThrowsException(() => + _stack.Taxonomy("non_existent_taxonomy_uid_12345").Update(updateModel)); + } + + [TestMethod] + [DoNotParallelize] + public void Test038_Should_Throw_When_Fetch_NonExistent_Taxonomy() { Assert.ThrowsException(() => _stack.Taxonomy("non_existent_taxonomy_uid_12345").Fetch()); @@ -116,10 +615,130 @@ public void Test007_Should_Throw_When_Fetch_NonExistent_Taxonomy() [TestMethod] [DoNotParallelize] - public void Test008_Should_Throw_When_Delete_NonExistent_Taxonomy() + public void Test039_Should_Throw_When_Delete_NonExistent_Taxonomy() { Assert.ThrowsException(() => _stack.Taxonomy("non_existent_taxonomy_uid_12345").Delete()); } + + [TestMethod] + [DoNotParallelize] + public void Test040_Should_Throw_When_Fetch_NonExistent_Term() + { + Assert.ThrowsException(() => + _stack.Taxonomy(_taxonomyUid).Terms("non_existent_term_uid_12345").Fetch()); + } + + [TestMethod] + [DoNotParallelize] + public void Test041_Should_Throw_When_Update_NonExistent_Term() + { + var updateModel = new TermModel { Name = "No", ParentUid = null }; + Assert.ThrowsException(() => + _stack.Taxonomy(_taxonomyUid).Terms("non_existent_term_uid_12345").Update(updateModel)); + } + + [TestMethod] + [DoNotParallelize] + public void Test042_Should_Throw_When_Delete_NonExistent_Term() + { + Assert.ThrowsException(() => + _stack.Taxonomy(_taxonomyUid).Terms("non_existent_term_uid_12345").Delete()); + } + + private static Stack GetStack() + { + StackResponse response = StackResponse.getStack(Contentstack.Client.serializer); + return Contentstack.Client.Stack(response.Stack.APIKey); + } + + [ClassCleanup] + public static void Cleanup() + { + try + { + Stack stack = GetStack(); + + if (_createdTermUids != null && _createdTermUids.Count > 0 && !string.IsNullOrEmpty(_taxonomyUid)) + { + var coll = new ParameterCollection(); + coll.Add("force", true); + foreach (var termUid in _createdTermUids) + { + try + { + stack.Taxonomy(_taxonomyUid).Terms(termUid).Delete(coll); + } + catch (ContentstackErrorException ex) when (ex.StatusCode == HttpStatusCode.NotFound) + { + // Term already deleted + } + catch (Exception ex) + { + Console.WriteLine($"[Cleanup] Failed to delete term {termUid}: {ex.Message}"); + } + } + _createdTermUids.Clear(); + } + + if (!string.IsNullOrEmpty(_importedTaxonomyUid)) + { + try + { + stack.Taxonomy(_importedTaxonomyUid).Delete(); + Console.WriteLine($"[Cleanup] Deleted imported taxonomy: {_importedTaxonomyUid}"); + _importedTaxonomyUid = null; + } + catch (Exception ex) + { + Console.WriteLine($"[Cleanup] Failed to delete imported taxonomy {_importedTaxonomyUid}: {ex.Message}"); + } + } + + if (!string.IsNullOrEmpty(_asyncCreatedTaxonomyUid)) + { + try + { + stack.Taxonomy(_asyncCreatedTaxonomyUid).Delete(); + Console.WriteLine($"[Cleanup] Deleted async-created taxonomy: {_asyncCreatedTaxonomyUid}"); + _asyncCreatedTaxonomyUid = null; + } + catch (Exception ex) + { + Console.WriteLine($"[Cleanup] Failed to delete async taxonomy {_asyncCreatedTaxonomyUid}: {ex.Message}"); + } + } + + if (!string.IsNullOrEmpty(_taxonomyUid)) + { + try + { + stack.Taxonomy(_taxonomyUid).Delete(); + Console.WriteLine($"[Cleanup] Deleted main taxonomy: {_taxonomyUid}"); + } + catch (Exception ex) + { + Console.WriteLine($"[Cleanup] Failed to delete main taxonomy {_taxonomyUid}: {ex.Message}"); + } + } + + if (_weCreatedTestLocale && !string.IsNullOrEmpty(_testLocaleCode)) + { + try + { + stack.Locale(_testLocaleCode).Delete(); + Console.WriteLine($"[Cleanup] Deleted test locale: {_testLocaleCode}"); + } + catch (Exception ex) + { + Console.WriteLine($"[Cleanup] Failed to delete test locale {_testLocaleCode}: {ex.Message}"); + } + } + } + catch (Exception ex) + { + Console.WriteLine($"[Cleanup] Cleanup failed: {ex.Message}"); + } + } } } diff --git a/Contentstack.Management.Core.Tests/Model/Models.cs b/Contentstack.Management.Core.Tests/Model/Models.cs index e4bf00e..6e05f70 100644 --- a/Contentstack.Management.Core.Tests/Model/Models.cs +++ b/Contentstack.Management.Core.Tests/Model/Models.cs @@ -37,5 +37,20 @@ public class TaxonomiesResponseModel [JsonProperty("taxonomies")] public List Taxonomies { get; set; } } + + public class TermResponseModel + { + [JsonProperty("term")] + public TermModel Term { get; set; } + } + + public class TermsResponseModel + { + [JsonProperty("terms")] + public List Terms { get; set; } + + [JsonProperty("count")] + public int? Count { get; set; } + } } diff --git a/Contentstack.Management.Core/contentstack.management.core.csproj b/Contentstack.Management.Core/contentstack.management.core.csproj index 32cb066..be21ae3 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