diff --git a/.talismanrc b/.talismanrc index 58aaa15..3871661 100644 --- a/.talismanrc +++ b/.talismanrc @@ -3,11 +3,11 @@ fileignoreconfig: ignore_detectors: - filecontent - filename: Contentstack.Core/Internals/HttpRequestHandler.cs - checksum: 93c1659f3bc7527956f0fd12db46441297fac3a4366d273bcbb3425d2351300e + checksum: 94288e483056f41ff3a4c2ab652c3ce9ecb53dc0b9d4029456b34baed4f34891 - filename: Contentstack.Core/Models/Entry.cs - checksum: a6226f755dde69be62c21737ca75569ba6ea7cb4b7a125dc460c047dbe741b9e + checksum: 78a09b03b9fd6aefd0251353b2d8c70962bdfced16a6e1e28d10dc9af43da244 - filename: Contentstack.Core/ContentstackClient.cs - checksum: 761a5d65bfa12d16641aa66e5431b2eb52b0909eff904dca5c2f607ee439cba0 + checksum: 687dc0a5f20037509731cfe540dcec9c3cc2b6cf50373cd183ece4f3249dc88e - filename: Contentstack.Core/Models/AssetLibrary.cs checksum: 92ff3feaf730b57c50bb8429f08dd4cddedb42cd89f2507e9746f8237b65fb11 - filename: Contentstack.Core/Models/Asset.cs @@ -18,11 +18,5 @@ fileignoreconfig: checksum: 751a725d94eff7d845bb22a5ce80a5529bb62971373de39288149fff3d024930 - filename: .github/workflows/nuget-publish.yml checksum: 53ba4ce874c4d2362ad00deb23f5a6ec219318860352f997b945e9161a580651 -- filename: Contentstack.Core/Models/ContentType.cs - checksum: 53e3b8330183445d100b32c545073f281b869ee238514c7ab8c9a4500a140166 -- filename: Contentstack.Core.Tests/ContentTypeTest.cs - checksum: a2549638af21492d5a299dce35a2994ec4721b211af6956b955602d7065c47dc -- filename: Contentstack.Core/Models/GlobalFieldQuery.cs - checksum: 16fb3c5fb4de2b686f338b0666c7c86c3d37f2a276f85698a59a1ac1a02d359d -- filename: Contentstack.Core/Models/GlobalField.cs - checksum: 49cc6ec8b55408a3e71ba19551dbe01709ec70bc673fae21c280a1f74968e395 \ No newline at end of file +- filename: Contentstack.Core.Tests/ContentstackClientTest.cs + checksum: b63897181a8cb5993d1305248cfc3e711c4039b5677b6c1e4e2a639e4ecb391b diff --git a/CHANGELOG.md b/CHANGELOG.md index b77db77..14b0d61 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +### Version: 2.22.1 +#### Date: June-13-2025 + +##### Fix: +- Fixed Timeline issue of Entry not getting updated + ### Version: 2.22.0 #### Date: March-03-2025 diff --git a/Contentstack.Core.Tests/ContentstackClientTest.cs b/Contentstack.Core.Tests/ContentstackClientTest.cs new file mode 100644 index 0000000..be8983a --- /dev/null +++ b/Contentstack.Core.Tests/ContentstackClientTest.cs @@ -0,0 +1,242 @@ +// Contentstack.Core/ContentstackClientTest.cs +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Threading.Tasks; +using Xunit; +using Contentstack.Core.Configuration; + +namespace Contentstack.Core.Tests +{ + public class ContentstackClientTest + { + private ContentstackClient CreateClient() + { + var options = new ContentstackOptions() + { + ApiKey = "api_key", + DeliveryToken = "delivery_token", + Environment = "environment", + Version = "1.2.3", + LivePreview = new LivePreviewConfig() + { + Enable = true, + Host = "https://preview.contentstack.com", + ReleaseId = "rid", + PreviewTimestamp = "ts", + PreviewToken = "pt" + + } + }; + return new ContentstackClient(options); + } + + private string GetPrivateField(ContentstackClient client, string fieldName) + { + var field = typeof(ContentstackClient).GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance); + return (string)field.GetValue(client); + } + + [Fact] + public void SetEntryUid_SetsValue_WhenNonEmpty() + { + var client = CreateClient(); + client.SetEntryUid("entry123"); + Assert.Equal("entry123", GetPrivateField(client, "currentEntryUid")); + } + + [Fact] + public void SetEntryUid_DoesNotSet_WhenEmpty() + { + var client = CreateClient(); + client.SetEntryUid("entry123"); + client.SetEntryUid(""); + Assert.Equal("entry123", GetPrivateField(client, "currentEntryUid")); + } + + [Fact] + public void SetEntryUid_DoesNotSet_WhenNull() + { + var client = CreateClient(); + client.SetEntryUid("entry123"); + client.SetEntryUid(null); + Assert.Equal("entry123", GetPrivateField(client, "currentEntryUid")); + } + + [Fact] + public void ContentType_SetscurrentContenttypeUid_WhenNonEmpty() + { + var client = CreateClient(); + client.ContentType("blog"); + Assert.Equal("blog", GetPrivateField(client, "currentContenttypeUid")); + } + + [Fact] + public void ContentType_DoesNotSet_WhenEmpty() + { + var client = CreateClient(); + client.ContentType("blog"); + client.ContentType(""); + Assert.Equal("blog", GetPrivateField(client, "currentContenttypeUid")); + } + + [Fact] + public void ContentType_DoesNotSet_WhenNull() + { + var client = CreateClient(); + client.ContentType("blog"); + client.ContentType(null); + Assert.Equal("blog", GetPrivateField(client, "currentContenttypeUid")); + } + + [Fact] + public void ContentType_ReturnsContentTypeInstance() + { + var client = CreateClient(); + var contentType = client.ContentType("blog"); + Assert.NotNull(contentType); + Assert.Equal("blog", contentType.ContentTypeId); + } + + [Fact] + public void GlobalField_ReturnsGlobalFieldInstance() + { + var client = CreateClient(); + var globalField = client.GlobalField("author"); + Assert.NotNull(globalField); + Assert.Equal("author", globalField.GlobalFieldId); + } + + [Fact] + public void Asset_ReturnsAssetInstance() + { + var client = CreateClient(); + var asset = client.Asset("asset_uid"); + Assert.NotNull(asset); + Assert.Equal("asset_uid", asset.Uid); + } + + [Fact] + public void AssetLibrary_ReturnsAssetLibraryInstance() + { + var client = CreateClient(); + var assetLibrary = client.AssetLibrary(); + Assert.NotNull(assetLibrary); + } + + [Fact] + public void Taxonomies_ReturnsTaxonomyInstance() + { + var client = CreateClient(); + var taxonomy = client.Taxonomies(); + Assert.NotNull(taxonomy); + } + + [Fact] + public void GetVersion_ReturnsVersion() + { + var client = CreateClient(); + var t = client.GetVersion(); + Assert.Equal("1.2.3", client.GetVersion()); + } + + [Fact] + public void GetApplicationKey_ReturnsApiKey() + { + var client = CreateClient(); + Assert.Equal("api_key", client.GetApplicationKey()); + } + + [Fact] + public void GetAccessToken_ReturnsDeliveryToken() + { + var client = CreateClient(); + Assert.Equal("delivery_token", client.GetAccessToken()); + } + + [Fact] + public void GetLivePreviewConfig_ReturnsConfig() + { + var client = CreateClient(); + Assert.NotNull(client.GetLivePreviewConfig()); + } + + [Fact] + public void RemoveHeader_RemovesHeader() + { + var client = CreateClient(); + client.SetHeader("custom", "value"); + client.RemoveHeader("custom"); + var localHeaders = typeof(ContentstackClient) + .GetField("_LocalHeaders", BindingFlags.NonPublic | BindingFlags.Instance) + .GetValue(client) as Dictionary; + Assert.False(localHeaders.ContainsKey("custom")); + } + + [Fact] + public void SetHeader_AddsHeader() + { + var client = CreateClient(); + client.SetHeader("custom", "value"); + var localHeaders = typeof(ContentstackClient) + .GetField("_LocalHeaders", BindingFlags.NonPublic | BindingFlags.Instance) + .GetValue(client) as Dictionary; + Assert.True(localHeaders.ContainsKey("custom")); + Assert.Equal("value", localHeaders["custom"]); + } + + [Fact] + public async Task LivePreviewQueryAsync_SetsLivePreviewConfigFields() + { + var client = CreateClient(); + client.ContentType("ctuid"); + client.ContentType("ctuid").Entry("euid"); + // Mock GetLivePreviewData to avoid actual HTTP call + var method = typeof(ContentstackClient).GetMethod("GetLivePreviewData", BindingFlags.NonPublic | BindingFlags.Instance); + method.Invoke(client, null); // Just to ensure method exists + + var query = new Dictionary + { + { "live_preview", "hash" }, + { "release_id", "rid" }, + { "preview_timestamp", "ts" } + }; + + // Patch GetLivePreviewData to return a dummy JObject + + // Since GetLivePreviewData is private and does a real HTTP call, + // we only test the config fields are set correctly before the call + await client.LivePreviewQueryAsync(query); + var v = client.GetLivePreviewConfig(); + Assert.Equal("ctuid", GetPrivateField(client, "currentContenttypeUid")); + Assert.Equal("euid", GetPrivateField(client, "currentEntryUid")); + Assert.Equal(true, v.Enable ); + Assert.Equal("rid", v.ReleaseId); + Assert.Equal("ts", v.PreviewTimestamp); + Assert.Equal("pt", v.PreviewToken); + } + + // For SyncRecursive, SyncPaginationToken, SyncToken, you should mock HTTP calls. + // Here we just check that the methods exist and can be called (will throw if not configured). + [Fact] + public async Task SyncRecursive_ThrowsOrReturns() + { + var client = CreateClient(); + await Assert.ThrowsAnyAsync(() => client.SyncRecursive()); + } + + [Fact] + public async Task SyncPaginationToken_ThrowsOrReturns() + { + var client = CreateClient(); + await Assert.ThrowsAnyAsync(() => client.SyncPaginationToken("pagetoken")); + } + + [Fact] + public async Task SyncToken_ThrowsOrReturns() + { + var client = CreateClient(); + await Assert.ThrowsAnyAsync(() => client.SyncToken("synctoken")); + } + } +} \ No newline at end of file diff --git a/Contentstack.Core/Configuration/Config.cs b/Contentstack.Core/Configuration/Config.cs index 58901e9..4c50718 100644 --- a/Contentstack.Core/Configuration/Config.cs +++ b/Contentstack.Core/Configuration/Config.cs @@ -101,7 +101,7 @@ internal string getBaseUrl (LivePreviewConfig livePreviewConfig, string contentT { if (livePreviewConfig != null && livePreviewConfig.Enable - && livePreviewConfig.LivePreview != "init" + && livePreviewConfig.LivePreview != "init" && !string.IsNullOrEmpty(livePreviewConfig.LivePreview) && livePreviewConfig.ContentTypeUID == contentTypeUID) { return getLivePreviewUrl(livePreviewConfig); diff --git a/Contentstack.Core/Contentstack.Core.csproj b/Contentstack.Core/Contentstack.Core.csproj index 030a3a7..63bf128 100644 --- a/Contentstack.Core/Contentstack.Core.csproj +++ b/Contentstack.Core/Contentstack.Core.csproj @@ -31,7 +31,7 @@ - + diff --git a/Contentstack.Core/ContentstackClient.cs b/Contentstack.Core/ContentstackClient.cs index 1bd1a1e..69dd881 100644 --- a/Contentstack.Core/ContentstackClient.cs +++ b/Contentstack.Core/ContentstackClient.cs @@ -56,6 +56,10 @@ private string _Url } } private Dictionary _StackHeaders = new Dictionary(); + + // This is used to store the last content type UID for live preview + private string currentContenttypeUid = null; + private string currentEntryUid = null; public List Plugins { get; set; } = new List(); /// /// Initializes a instance of the class. @@ -349,14 +353,14 @@ private async Task GetLivePreviewData() foreach (var header in headers) { if (this.LivePreviewConfig.Enable == true - && header.Key == "access_token") + && header.Key == "access_token" && !string.IsNullOrEmpty(LivePreviewConfig.LivePreview)) { continue; } headerAll.Add(header.Key, (String)header.Value); } } - mainJson.Add("live_preview", this.LivePreviewConfig.LivePreview ?? "init"); + mainJson.Add("live_preview", string.IsNullOrEmpty(this.LivePreviewConfig.LivePreview) ? "init" : this.LivePreviewConfig.LivePreview); if (!string.IsNullOrEmpty(this.LivePreviewConfig.ManagementToken)) { @@ -384,7 +388,8 @@ private async Task GetLivePreviewData() { HttpRequestHandler RequestHandler = new HttpRequestHandler(this); //string branch = this.Config.Branch ? this.Config.Branch : "main"; - var outputResult = await RequestHandler.ProcessRequest(String.Format("{0}/content_types/{1}/entries/{2}", this.Config.getLivePreviewUrl(this.LivePreviewConfig), this.LivePreviewConfig.ContentTypeUID, this.LivePreviewConfig.EntryUID), headerAll, mainJson, Branch: this.Config.Branch, isLivePreview: true, timeout: this.Config.Timeout, proxy: this.Config.Proxy); + string URL = String.Format("{0}/content_types/{1}/entries/{2}", this.Config.getBaseUrl(this.LivePreviewConfig, this.LivePreviewConfig.ContentTypeUID), this.LivePreviewConfig.ContentTypeUID, this.LivePreviewConfig.EntryUID); + var outputResult = await RequestHandler.ProcessRequest(URL, headerAll, mainJson, Branch: this.Config.Branch, isLivePreview: true, timeout: this.Config.Timeout, proxy: this.Config.Proxy); JObject data = JsonConvert.DeserializeObject(outputResult.Replace("\r\n", ""), this.SerializerSettings); return (JObject)data["entry"]; } @@ -394,6 +399,13 @@ private async Task GetLivePreviewData() } } + public void SetEntryUid(string entryUid) + { + if (!string.IsNullOrEmpty(entryUid)) + { + this.currentEntryUid = entryUid; + } + } /// /// Represents a ContentType. Creates ContenntType Instance. /// @@ -407,6 +419,10 @@ private async Task GetLivePreviewData() /// public ContentType ContentType(String contentTypeName) { + if (!string.IsNullOrEmpty(contentTypeName)) + { + this.currentContenttypeUid = contentTypeName; + } ContentType contentType = new ContentType(contentTypeName); contentType.SetStackInstance(this); @@ -488,7 +504,7 @@ public Taxonomy Taxonomies() /// public string GetVersion() { - return Version; + return this.Config.Version; } /// @@ -595,27 +611,39 @@ public void SetHeader(string key, string value) public async Task LivePreviewQueryAsync(Dictionary query) { this.LivePreviewConfig.LivePreview = null; - this.LivePreviewConfig.ContentTypeUID = null; - this.LivePreviewConfig.EntryUID = null; - + this.LivePreviewConfig.PreviewTimestamp = null; + this.LivePreviewConfig.ReleaseId = null; if (query.Keys.Contains("content_type_uid")) { string contentTypeUID = null; query.TryGetValue("content_type_uid", out contentTypeUID); this.LivePreviewConfig.ContentTypeUID = contentTypeUID; } + else if (!string.IsNullOrEmpty(this.currentContenttypeUid)) + { + this.LivePreviewConfig.ContentTypeUID = this.currentContenttypeUid; + } + + if (query.Keys.Contains("entry_uid")) { - string contentTypeUID = null; - query.TryGetValue("entry_uid", out contentTypeUID); - this.LivePreviewConfig.EntryUID = contentTypeUID; + string entryUID = null; + query.TryGetValue("entry_uid", out entryUID); + this.LivePreviewConfig.EntryUID = entryUID; } + else if (!string.IsNullOrEmpty(this.currentEntryUid)) + { + this.LivePreviewConfig.EntryUID = this.currentEntryUid; + } + + if (query.Keys.Contains("live_preview")) { string hash = null; query.TryGetValue("live_preview", out hash); this.LivePreviewConfig.LivePreview = hash; } + if (query.Keys.Contains("release_id")) { string ReleaseId = null; @@ -628,7 +656,10 @@ public async Task LivePreviewQueryAsync(Dictionary query) query.TryGetValue("preview_timestamp", out PreviewTimestamp); this.LivePreviewConfig.PreviewTimestamp = PreviewTimestamp; } - this.LivePreviewConfig.PreviewResponse = await GetLivePreviewData(); + //if (!string.IsNullOrEmpty(this.LivePreviewConfig.LivePreview)) + //{ + // this.LivePreviewConfig.PreviewResponse = await GetLivePreviewData(); + //} } /// diff --git a/Contentstack.Core/Internals/HttpRequestHandler.cs b/Contentstack.Core/Internals/HttpRequestHandler.cs index 00ffaf1..76416e8 100644 --- a/Contentstack.Core/Internals/HttpRequestHandler.cs +++ b/Contentstack.Core/Internals/HttpRequestHandler.cs @@ -48,7 +48,7 @@ public async Task ProcessRequest(string Url, Dictionary var request = (HttpWebRequest)WebRequest.Create(uri); request.Method = "GET"; request.ContentType = "application/json"; - request.Headers["x-user-agent"]="contentstack-delivery-dotnet/2.22.0"; + request.Headers["x-user-agent"]="contentstack-delivery-dotnet/2.22.1"; request.Timeout = timeout; if (proxy != null) @@ -108,44 +108,44 @@ public async Task ProcessRequest(string Url, Dictionary } - internal void updateLivePreviewContent(JObject response) - { - if (response.ContainsKey("uid") && response["uid"].ToString() == this.client.LivePreviewConfig.EntryUID) - { - response.Merge(this.client.LivePreviewConfig.PreviewResponse, new JsonMergeSettings() - { - MergeArrayHandling = MergeArrayHandling.Replace - }); - } - else - { - foreach (var content in response) - { - if (content.Value.Type == JTokenType.Array) - { - updateArray((JArray)response[content.Key]); - } - else if (content.Value.Type == JTokenType.Object) - { - updateLivePreviewContent((JObject)response[content.Key]); - } - } - } - } + //internal void updateLivePreviewContent(JObject response) + //{ + // if (response.ContainsKey("uid") && response["uid"].ToString() == this.client.LivePreviewConfig.EntryUID) + // { + // response.Merge(this.client.LivePreviewConfig.PreviewResponse, new JsonMergeSettings() + // { + // MergeArrayHandling = MergeArrayHandling.Replace + // }); + // } + // else + // { + // foreach (var content in response) + // { + // if (content.Value.Type == JTokenType.Array) + // { + // updateArray((JArray)response[content.Key]); + // } + // else if (content.Value.Type == JTokenType.Object) + // { + // updateLivePreviewContent((JObject)response[content.Key]); + // } + // } + // } + //} - internal void updateArray(JArray array) - { - for (var i = 0; i < array.Count(); i++) - { - if (array[i].Type == JTokenType.Array) - { - updateArray((JArray)array[i]); - } - else if (array[i].Type == JTokenType.Object) - { - updateLivePreviewContent((JObject)array[i]); - } - } - } + //internal void updateArray(JArray array) + //{ + // for (var i = 0; i < array.Count(); i++) + // { + // if (array[i].Type == JTokenType.Array) + // { + // updateArray((JArray)array[i]); + // } + // else if (array[i].Type == JTokenType.Object) + // { + // updateLivePreviewContent((JObject)array[i]); + // } + // } + //} } } diff --git a/Contentstack.Core/Models/ContentType.cs b/Contentstack.Core/Models/ContentType.cs index b01760f..bfd47a7 100644 --- a/Contentstack.Core/Models/ContentType.cs +++ b/Contentstack.Core/Models/ContentType.cs @@ -240,6 +240,7 @@ public Entry Entry(String entryUid) entry._FormHeaders = GetHeader(_Headers); entry.SetContentTypeInstance(this); entry.SetUid(entryUid); + this.StackInstance.SetEntryUid(entryUid); return entry; } diff --git a/Contentstack.Core/Models/Entry.cs b/Contentstack.Core/Models/Entry.cs index 24bac9c..1551a4b 100644 --- a/Contentstack.Core/Models/Entry.cs +++ b/Contentstack.Core/Models/Entry.cs @@ -1388,7 +1388,7 @@ public async Task Fetch() { if (this.ContentTypeInstance.StackInstance.LivePreviewConfig.Enable == true && this.ContentTypeInstance.StackInstance.LivePreviewConfig.ContentTypeUID == this.ContentTypeInstance.ContentTypeId - && header.Key == "access_token") + && header.Key == "access_token" && !string.IsNullOrEmpty(this.ContentTypeInstance.StackInstance.LivePreviewConfig.LivePreview)) { continue; } @@ -1398,7 +1398,7 @@ public async Task Fetch() bool isLivePreview = false; if (this.ContentTypeInstance.StackInstance.LivePreviewConfig.Enable == true && this.ContentTypeInstance.StackInstance.LivePreviewConfig.ContentTypeUID == this.ContentTypeInstance.ContentTypeId) { - mainJson.Add("live_preview", this.ContentTypeInstance.StackInstance.LivePreviewConfig.LivePreview ?? "init"); + mainJson.Add("live_preview", string.IsNullOrEmpty(this.ContentTypeInstance.StackInstance.LivePreviewConfig.LivePreview)? "init" : this.ContentTypeInstance.StackInstance.LivePreviewConfig.LivePreview); if (!string.IsNullOrEmpty(this.ContentTypeInstance.StackInstance.LivePreviewConfig.ManagementToken)) { headerAll["authorization"] = this.ContentTypeInstance.StackInstance.LivePreviewConfig.ManagementToken; diff --git a/Contentstack.Core/Models/Query.cs b/Contentstack.Core/Models/Query.cs index 0ca904b..252cd42 100644 --- a/Contentstack.Core/Models/Query.cs +++ b/Contentstack.Core/Models/Query.cs @@ -1857,7 +1857,7 @@ private async Task Exec() if (this.ContentTypeInstance!=null && this.ContentTypeInstance.StackInstance.LivePreviewConfig.Enable == true && this.ContentTypeInstance.StackInstance?.LivePreviewConfig.ContentTypeUID == this.ContentTypeInstance.ContentTypeId) { - mainJson.Add("live_preview", this.ContentTypeInstance.StackInstance.LivePreviewConfig.LivePreview ?? "init"); + mainJson.Add("live_preview", string.IsNullOrEmpty(this.ContentTypeInstance.StackInstance.LivePreviewConfig.LivePreview) ? "init" : this.ContentTypeInstance.StackInstance.LivePreviewConfig.LivePreview); if (!string.IsNullOrEmpty(this.ContentTypeInstance.StackInstance.LivePreviewConfig.ManagementToken)) { headerAll["authorization"] = this.ContentTypeInstance.StackInstance.LivePreviewConfig.ManagementToken; diff --git a/Directory.Build.props b/Directory.Build.props index cf65843..bec8fed 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,5 +1,5 @@ - 2.22.0 + 2.22.1