From 0ed1c30dbad1bcb1c42c83f5269c2dc7b3406b7e Mon Sep 17 00:00:00 2001 From: Edvin Dackelid Johansson Date: Tue, 18 Mar 2025 17:34:08 +0100 Subject: [PATCH 1/5] CG-11483 remove line brakes in bulk content on Linux --- .../Repositories/GraphSourceRepository.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Optimizely.Graph.Source.Sdk/Optimizely.Graph.Source.Sdk/Repositories/GraphSourceRepository.cs b/Optimizely.Graph.Source.Sdk/Optimizely.Graph.Source.Sdk/Repositories/GraphSourceRepository.cs index ee23347..e92856c 100644 --- a/Optimizely.Graph.Source.Sdk/Optimizely.Graph.Source.Sdk/Repositories/GraphSourceRepository.cs +++ b/Optimizely.Graph.Source.Sdk/Optimizely.Graph.Source.Sdk/Repositories/GraphSourceRepository.cs @@ -100,7 +100,7 @@ public async Task SaveContentAsync(Func generateId, params itemJson += $"{{ \"index\": {{ \"_id\": \"{id}\", \"language_routing\": \"{language}\" }} }}"; itemJson += Environment.NewLine; - itemJson += JsonSerializer.Serialize(item, serializeOptions).Replace("\r\n", ""); + itemJson += JsonSerializer.Serialize(item, serializeOptions).Replace("\r\n", "").Replace("\n", ""); itemJson += Environment.NewLine; } From e2c33ccd9001ecd853577548b70acfed8366f06a Mon Sep 17 00:00:00 2001 From: Edvin Dackelid Johansson Date: Wed, 19 Mar 2025 12:29:29 +0100 Subject: [PATCH 2/5] CG-11483 Add tests checking exactly 2 newlines and verifying minified content in CreateContent --- .../GraphSourceRepositoryTests.cs | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/Optimizely.Graph.Source.Sdk/Optimizely.Graph.Source.Sdk.Tests/RepositoryTests/GraphSourceRepositoryTests.cs b/Optimizely.Graph.Source.Sdk/Optimizely.Graph.Source.Sdk.Tests/RepositoryTests/GraphSourceRepositoryTests.cs index 94682b4..acadc60 100644 --- a/Optimizely.Graph.Source.Sdk/Optimizely.Graph.Source.Sdk.Tests/RepositoryTests/GraphSourceRepositoryTests.cs +++ b/Optimizely.Graph.Source.Sdk/Optimizely.Graph.Source.Sdk.Tests/RepositoryTests/GraphSourceRepositoryTests.cs @@ -8,6 +8,7 @@ using Optimizely.Graph.Source.Sdk.RestClientHelpers; using Optimizely.Graph.Source.Sdk.SourceConfiguration; using Optimizely.Graph.Source.Sdk.Tests.ExampleObjects; +using System.Text.RegularExpressions; namespace Optimizely.Graph.Source.Sdk.Tests.RepositoryTests { @@ -395,6 +396,78 @@ public async Task SaveContentAsync_WithMultipleTypes_ShouldGenerateJsonForConten mockRestClient.VerifyAll(); } + [TestMethod] + public async Task CreateContent_ShouldContainTwoNewLines() + { + // Arrange + repository.ConfigureContentType() + .Field(x => x.FirstName, IndexingType.Searchable) + .Field(x => x.LastName, IndexingType.Searchable) + .Field(x => x.Age, IndexingType.Queryable) + .Field(x => x.SubType, IndexingType.PropertyType); + + repository.ConfigurePropertyType() + .Field(x => x.One, IndexingType.Searchable) + .Field(x => x.Two, IndexingType.Queryable); + + var exampleData = new ExampleClassObject + { + FirstName = "First", + LastName = "Last", + Age = 99, + SubType = new ExampleClassObject.SubType1 + { + One = "type one", + Two = 13 + } + }; + + // Act + var createdContent = repository.CreateContent(generateId: (x) => x.ToString(), exampleData); + var result = createdContent.ReadAsStringAsync().Result; + + // Assert + Assert.AreEqual(2, Regex.Matches(result, "\r?\n").Count, "Expected exactly 2 windows- or unix newlines."); + } + + [TestMethod] + public async Task CreateContent_ShouldProduceMinifiedContent() + { + // Arrange + repository.ConfigureContentType() + .Field(x => x.FirstName, IndexingType.Searchable) + .Field(x => x.LastName, IndexingType.Searchable) + .Field(x => x.Age, IndexingType.Queryable) + .Field(x => x.SubType, IndexingType.PropertyType); + + repository.ConfigurePropertyType() + .Field(x => x.One, IndexingType.Searchable) + .Field(x => x.Two, IndexingType.Queryable); + + var exampleData = new ExampleClassObject + { + FirstName = "First", + LastName = "Last", + Age = 99, + SubType = new ExampleClassObject.SubType1 + { + One = "type one", + Two = 13 + } + }; + + // Act + var createdContent = repository.CreateContent(generateId: (x) => x.ToString(), exampleData); + var result = createdContent.ReadAsStringAsync().Result; + + // Assert + var lines = result.Split(new[] { "\r\n", "\n" }, StringSplitOptions.None); + Assert.AreEqual(3, lines.Length, "Expected exactly 3 lines (1 index, 1 content and 1 empty)."); + Assert.AreEqual("""{"index":{"_id":"Optimizely.Graph.Source.Sdk.Tests.ExampleObjects.ExampleClassObject","language_routing":"en"}}""", lines[0], "Expected index line to be minified (to not contain spaces)."); + Assert.AreEqual("""{"Status$$String":"Published","__typename":"ExampleClassObject","_rbac":"r:Everyone:Read","ContentType$$String":["ExampleClassObject"],"Language":{"Name$$String":"en"},"FirstName$$String___searchable":"First","LastName$$String___searchable":"Last","Age$$Int":99,"SubType":{"One$$String___searchable":"type one","Two$$Int":13}}""", lines[1], "Expected content line to be minified (to not contain spaces)."); + Assert.AreEqual("", lines[2], "Expected empty line to be empty."); + } + [TestMethod] public async Task DeleteContentAsync_ThrowsNotImplementedException() { From 09472a0c156c0d31f72c1861fb01277392e31ad6 Mon Sep 17 00:00:00 2001 From: Edvin Dackelid Johansson Date: Wed, 19 Mar 2025 12:31:19 +0100 Subject: [PATCH 3/5] CG-11483 Update old tests to expect minified content instead of semi-pretty printed JSON --- .../GraphSourceRepositoryTests.cs | 106 +++--------------- 1 file changed, 18 insertions(+), 88 deletions(-) diff --git a/Optimizely.Graph.Source.Sdk/Optimizely.Graph.Source.Sdk.Tests/RepositoryTests/GraphSourceRepositoryTests.cs b/Optimizely.Graph.Source.Sdk/Optimizely.Graph.Source.Sdk.Tests/RepositoryTests/GraphSourceRepositoryTests.cs index acadc60..86e350a 100644 --- a/Optimizely.Graph.Source.Sdk/Optimizely.Graph.Source.Sdk.Tests/RepositoryTests/GraphSourceRepositoryTests.cs +++ b/Optimizely.Graph.Source.Sdk/Optimizely.Graph.Source.Sdk.Tests/RepositoryTests/GraphSourceRepositoryTests.cs @@ -147,77 +147,7 @@ public async Task SaveTypesAsync_WithLink_ShouldGenerateTypesAndLink() x => x.LocationName ); - var expectedJsonString = @"{ - ""useTypedFieldNames"": true, - ""languages"": [], - ""links"": { - ""NameToLocationName"": { - ""from"": ""Name$$String___searchable"", - ""to"": ""LocationName$$String"" - } - }, - ""contentTypes"": { - ""Location"": { - ""contentType"": [], - ""properties"": { - ""Longitude"": { - ""type"": ""Float"", - ""searchable"": false, - ""skip"": false - }, - ""Latitude"": { - ""type"": ""Float"", - ""searchable"": false, - ""skip"": false - }, - ""Name"": { - ""type"": ""String"", - ""searchable"": true, - ""skip"": false - } - } - }, - ""Event"": { - ""contentType"": [], - ""properties"": { - ""LocationName"": { - ""type"": ""String"", - ""searchable"": false, - ""skip"": false - }, - ""Time"": { - ""type"": ""DateTime"", - ""searchable"": false, - ""skip"": false - }, - ""Name"": { - ""type"": ""String"", - ""searchable"": true, - ""skip"": false - }, - ""AdditionalInfo"": { - ""type"": ""ExtraInfo"" - } - } - }, - ""ExtraInfo"": { - ""contentType"": [], - ""properties"": { - ""Example1"": { - ""type"": ""String"", - ""searchable"": false, - ""skip"": true - }, - ""Example2"": { - ""type"": ""Int"", - ""searchable"": false, - ""skip"": false - } - } - } - }, - ""propertyTypes"": {} -}"; + var expectedJsonString = @"{""useTypedFieldNames"":true,""languages"":[],""links"":{""NameToLocationName"":{""from"":""Name$$String___searchable"",""to"":""LocationName$$String""}},""contentTypes"":{""Location"":{""contentType"":[],""properties"":{""Longitude"":{""type"":""Float"",""searchable"":false,""skip"":false},""Latitude"":{""type"":""Float"",""searchable"":false,""skip"":false},""Name"":{""type"":""String"",""searchable"":true,""skip"":false}}},""Event"":{""contentType"":[],""properties"":{""LocationName"":{""type"":""String"",""searchable"":false,""skip"":false},""Time"":{""type"":""DateTime"",""searchable"":false,""skip"":false},""Name"":{""type"":""String"",""searchable"":true,""skip"":false},""AdditionalInfo"":{""type"":""ExtraInfo""}}},""ExtraInfo"":{""contentType"":[],""properties"":{""Example1"":{""type"":""String"",""searchable"":false,""skip"":true},""Example2"":{""type"":""Int"",""searchable"":false,""skip"":false}}}},""propertyTypes"":{}}"; var jsonString = BuildExpectedTypeJsonString(); var content = new StringContent(jsonString, Encoding.UTF8, "application/json"); @@ -265,7 +195,7 @@ public async Task SaveContentAsync_SerializesData_AndCallsGraphClient() } }; - var expectedJsonString = BuildExpextedContentJsonString(x => x.ToString(), exampleData); + var expectedJsonString = BuildExpectedContentJsonString(x => x.ToString(), exampleData); var content = new StringContent(expectedJsonString, Encoding.UTF8, "application/json"); @@ -349,16 +279,16 @@ public async Task SaveContentAsync_WithMultipleTypes_ShouldGenerateJsonForConten } }; - var expectedJsonString = @"{ ""index"": { ""_id"": ""Location-Stockholm"", ""language_routing"": ""en"" } } -{ ""Status$$String"": ""Published"", ""__typename"": ""Location"", ""_rbac"": ""r:Everyone:Read"", ""ContentType$$String"": [ ""Location"" ], ""Language"": { ""Name$$String"": ""en"" }, ""Longitude$$Float"": 18.063241, ""Latitude$$Float"": 59.334591, ""Name$$String___searchable"": ""Stockholm""} -{ ""index"": { ""_id"": ""Location-London"", ""language_routing"": ""en"" } } -{ ""Status$$String"": ""Published"", ""__typename"": ""Location"", ""_rbac"": ""r:Everyone:Read"", ""ContentType$$String"": [ ""Location"" ], ""Language"": { ""Name$$String"": ""en"" }, ""Longitude$$Float"": 0.1275, ""Latitude$$Float"": 51.5072, ""Name$$String___searchable"": ""London""} -{ ""index"": { ""_id"": ""Event-Future of Project Management"", ""language_routing"": ""en"" } } -{ ""Status$$String"": ""Published"", ""__typename"": ""Event"", ""_rbac"": ""r:Everyone:Read"", ""ContentType$$String"": [ ""Event"" ], ""Language"": { ""Name$$String"": ""en"" }, ""LocationName$$String"": ""Stockholm"", ""Time$$DateTime"": ""2024-10-21T22:00:00Z"", ""Name$$String___searchable"": ""Future of Project Management"", ""AdditionalInfo"": { ""Example1$$String___skip"": ""test1"", ""Example2$$Int"": 1 }} -{ ""index"": { ""_id"": ""Event-Week of Hope: Football Camp for Homeless Children in Hanoi!"", ""language_routing"": ""en"" } } -{ ""Status$$String"": ""Published"", ""__typename"": ""Event"", ""_rbac"": ""r:Everyone:Read"", ""ContentType$$String"": [ ""Event"" ], ""Language"": { ""Name$$String"": ""en"" }, ""LocationName$$String"": ""Hanoi"", ""Time$$DateTime"": ""2024-10-26T22:00:00Z"", ""Name$$String___searchable"": ""Week of Hope: Football Camp for Homeless Children in Hanoi!"", ""AdditionalInfo"": { ""Example1$$String___skip"": ""test2"", ""Example2$$Int"": 2 }} -{ ""index"": { ""_id"": ""Event-Optimizing Project Management: Strategies for Success"", ""language_routing"": ""en"" } } -{ ""Status$$String"": ""Published"", ""__typename"": ""Event"", ""_rbac"": ""r:Everyone:Read"", ""ContentType$$String"": [ ""Event"" ], ""Language"": { ""Name$$String"": ""en"" }, ""LocationName$$String"": ""London"", ""Time$$DateTime"": ""2024-11-02T23:00:00Z"", ""Name$$String___searchable"": ""Optimizing Project Management: Strategies for Success"", ""AdditionalInfo"": { ""Example1$$String___skip"": ""test3"", ""Example2$$Int"": 3 }} + var expectedJsonString = @"{""index"":{""_id"":""Location-Stockholm"",""language_routing"":""en""}} +{""Status$$String"":""Published"",""__typename"":""Location"",""_rbac"":""r:Everyone:Read"",""ContentType$$String"":[""Location""],""Language"":{""Name$$String"":""en""},""Longitude$$Float"":18.063241,""Latitude$$Float"":59.334591,""Name$$String___searchable"":""Stockholm""} +{""index"":{""_id"":""Location-London"",""language_routing"":""en""}} +{""Status$$String"":""Published"",""__typename"":""Location"",""_rbac"":""r:Everyone:Read"",""ContentType$$String"":[""Location""],""Language"":{""Name$$String"":""en""},""Longitude$$Float"":0.1275,""Latitude$$Float"":51.5072,""Name$$String___searchable"":""London""} +{""index"":{""_id"":""Event-Future of Project Management"",""language_routing"":""en""}} +{""Status$$String"":""Published"",""__typename"":""Event"",""_rbac"":""r:Everyone:Read"",""ContentType$$String"":[""Event""],""Language"":{""Name$$String"":""en""},""LocationName$$String"":""Stockholm"",""Time$$DateTime"":""2024-10-21T22:00:00Z"",""Name$$String___searchable"":""Future of Project Management"",""AdditionalInfo"":{""Example1$$String___skip"":""test1"",""Example2$$Int"":1}} +{""index"":{""_id"":""Event-Week of Hope: Football Camp for Homeless Children in Hanoi!"",""language_routing"":""en""}} +{""Status$$String"":""Published"",""__typename"":""Event"",""_rbac"":""r:Everyone:Read"",""ContentType$$String"":[""Event""],""Language"":{""Name$$String"":""en""},""LocationName$$String"":""Hanoi"",""Time$$DateTime"":""2024-10-26T22:00:00Z"",""Name$$String___searchable"":""Week of Hope: Football Camp for Homeless Children in Hanoi!"",""AdditionalInfo"":{""Example1$$String___skip"":""test2"",""Example2$$Int"":2}} +{""index"":{""_id"":""Event-Optimizing Project Management: Strategies for Success"",""language_routing"":""en""}} +{""Status$$String"":""Published"",""__typename"":""Event"",""_rbac"":""r:Everyone:Read"",""ContentType$$String"":[""Event""],""Language"":{""Name$$String"":""en""},""LocationName$$String"":""London"",""Time$$DateTime"":""2024-11-02T23:00:00Z"",""Name$$String___searchable"":""Optimizing Project Management: Strategies for Success"",""AdditionalInfo"":{""Example1$$String___skip"":""test3"",""Example2$$Int"":3}} "; Func generateId = (x) => @@ -375,7 +305,7 @@ public async Task SaveContentAsync_WithMultipleTypes_ShouldGenerateJsonForConten }; - var jsonString = BuildExpextedContentJsonString(generateId, locationStockholm, locationLondon, event1, event2, event3); + var jsonString = BuildExpectedContentJsonString(generateId, locationStockholm, locationLondon, event1, event2, event3); var content = new StringContent(expectedJsonString, Encoding.UTF8, "application/json"); @@ -492,7 +422,7 @@ private string BuildExpectedTypeJsonString() { var serializeOptions = new JsonSerializerOptions { - WriteIndented = true, + WriteIndented = false, Converters = { new SourceSdkContentTypeConverter() @@ -502,11 +432,11 @@ private string BuildExpectedTypeJsonString() return JsonSerializer.Serialize(SourceConfigurationModel.GetTypeFieldConfiguration(), serializeOptions); } - private string BuildExpextedContentJsonString(Func generateId, params T[] items) + private string BuildExpectedContentJsonString(Func generateId, params T[] items) { var serializeOptions = new JsonSerializerOptions { - WriteIndented = true, + WriteIndented = false, Converters = { new SourceSdkContentConverter() @@ -517,9 +447,9 @@ private string BuildExpextedContentJsonString(Func generateId, par foreach (var data in items) { - itemJson += $"{{ \"index\": {{ \"_id\": \"{generateId(data)}\", \"language_routing\": \"en\" }} }}"; + itemJson += $"{{\"index\":{{\"_id\":\"{generateId(data)}\",\"language_routing\":\"en\"}}}}"; itemJson += Environment.NewLine; - itemJson += JsonSerializer.Serialize(data, serializeOptions).Replace("\r\n", ""); + itemJson += JsonSerializer.Serialize(data, serializeOptions); itemJson += Environment.NewLine; } return itemJson; From 4917ba9f469cf984255375d24beb10f7715ae3bd Mon Sep 17 00:00:00 2001 From: Edvin Dackelid Johansson Date: Wed, 19 Mar 2025 12:33:05 +0100 Subject: [PATCH 4/5] Add VSCode config to be able to run tests from inside the IDE --- Optimizely.Graph.Source.Sdk/.vscode/settings.json | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 Optimizely.Graph.Source.Sdk/.vscode/settings.json diff --git a/Optimizely.Graph.Source.Sdk/.vscode/settings.json b/Optimizely.Graph.Source.Sdk/.vscode/settings.json new file mode 100644 index 0000000..7bb4b11 --- /dev/null +++ b/Optimizely.Graph.Source.Sdk/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + "dotnet-test-explorer.testProjectPath": + [ + "Optimizely.Graph.Source.Sdk.Tests/RepositoryTests/", + ], +} \ No newline at end of file From 0f37b8ac4e549aa6dcb188dfb406d15628058c80 Mon Sep 17 00:00:00 2001 From: Edvin Dackelid Johansson Date: Wed, 19 Mar 2025 12:36:05 +0100 Subject: [PATCH 5/5] CG-11483 Refactor and change SaveContentAsync to send minified JSON instead of semi-pretty JSON --- .../Repositories/GraphSourceRepository.cs | 32 +++++++++++-------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/Optimizely.Graph.Source.Sdk/Optimizely.Graph.Source.Sdk/Repositories/GraphSourceRepository.cs b/Optimizely.Graph.Source.Sdk/Optimizely.Graph.Source.Sdk/Repositories/GraphSourceRepository.cs index e92856c..594a0ef 100644 --- a/Optimizely.Graph.Source.Sdk/Optimizely.Graph.Source.Sdk/Repositories/GraphSourceRepository.cs +++ b/Optimizely.Graph.Source.Sdk/Optimizely.Graph.Source.Sdk/Repositories/GraphSourceRepository.cs @@ -82,10 +82,25 @@ public async Task SaveTypesAsync() /// public async Task SaveContentAsync(Func generateId, params T[] data) where T : class, new() + { + var content = CreateContent(generateId, data); + + using (var requestMessage = new HttpRequestMessage(HttpMethod.Post, $"{DataUrl}?id={source}")) + { + requestMessage.Content = content; + using (var responseMessage = await client.SendAsync(requestMessage)) + { + await client.HandleResponse(responseMessage); + } + } + return string.Empty; + } + + public StringContent CreateContent(Func generateId, params T[] data) { var serializeOptions = new JsonSerializerOptions { - WriteIndented = true, + WriteIndented = false, Converters = { new SourceSdkContentConverter() @@ -98,23 +113,14 @@ public async Task SaveContentAsync(Func generateId, params var id = generateId(item); var language = "en"; - itemJson += $"{{ \"index\": {{ \"_id\": \"{id}\", \"language_routing\": \"{language}\" }} }}"; + itemJson += $"{{\"index\":{{\"_id\":\"{id}\",\"language_routing\":\"{language}\"}}}}"; itemJson += Environment.NewLine; - itemJson += JsonSerializer.Serialize(item, serializeOptions).Replace("\r\n", "").Replace("\n", ""); + itemJson += JsonSerializer.Serialize(item, serializeOptions); itemJson += Environment.NewLine; } var content = new StringContent(itemJson, Encoding.UTF8, "application/json"); - - using (var requestMessage = new HttpRequestMessage(HttpMethod.Post, $"{DataUrl}?id={source}")) - { - requestMessage.Content = content; - using (var responseMessage = await client.SendAsync(requestMessage)) - { - await client.HandleResponse(responseMessage); - } - } - return string.Empty; + return content; } ///