diff --git a/arangodb-net-standard.Test/IndexApi/IndexApiClientTest.cs b/arangodb-net-standard.Test/IndexApi/IndexApiClientTest.cs index 68dbed33..8a3560e8 100644 --- a/arangodb-net-standard.Test/IndexApi/IndexApiClientTest.cs +++ b/arangodb-net-standard.Test/IndexApi/IndexApiClientTest.cs @@ -142,5 +142,59 @@ public async Task DeleteIndexAsync_ShouldThrow_WhenNotExist() ); Assert.Equal(400, ex.ApiError.ErrorNum); } + + [Fact] + public async Task PostInvertedIndexAsync_ShouldSucceed() + { + var createResponse = await _indexApi.PostInvertedIndexAsync( + new PostIndexQuery() + { + CollectionName = _testCollection, + }, + new PostInvertedIndexBody() + { + Name = "test_inverted_index_" + System.Guid.NewGuid().ToString("N")[..8], + Fields = new InvertedIndexField[] + { + new InvertedIndexField + { + Name = "name", + Analyzer = "text_en" + }, + new InvertedIndexField + { + Name = "description", + Analyzer = "text_en" + } + } + }); + + string indexId = createResponse.Id; + Assert.False(createResponse.Error); + Assert.NotNull(indexId); + Assert.NotNull(createResponse.Fields); + Assert.Equal(2, createResponse.Fields.Count()); + + // Verify that fields contain correct names + var fieldNames = createResponse.Fields.Select(f => f.Name).ToList(); + Assert.Contains("name", fieldNames); + Assert.Contains("description", fieldNames); + + // Verify getting the created inverted index + var getResponse = await _indexApi.GetIndexAsync(indexId); + Assert.NotNull(getResponse); + Assert.Equal(indexId, getResponse.Id); + Assert.Equal("inverted", getResponse.Type); + Assert.NotNull(getResponse.Fields); + + // Verify that IndexFieldsConverter properly handles fields + Assert.Contains("name", getResponse.Fields); + Assert.Contains("description", getResponse.Fields); + + // Clean up - delete the created index + var deleteResponse = await _indexApi.DeleteIndexAsync(indexId); + Assert.False(deleteResponse.Error); + Assert.Equal(indexId, deleteResponse.Id); + } } } diff --git a/arangodb-net-standard.Test/IndexApi/IndexFieldsConverterTest.cs b/arangodb-net-standard.Test/IndexApi/IndexFieldsConverterTest.cs new file mode 100644 index 00000000..579bd1a8 --- /dev/null +++ b/arangodb-net-standard.Test/IndexApi/IndexFieldsConverterTest.cs @@ -0,0 +1,240 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using ArangoDBNetStandard.IndexApi.Converters; +using ArangoDBNetStandard.IndexApi.Models; +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; +using Xunit; + +namespace ArangoDBNetStandardTest.IndexApi +{ + public class IndexFieldsConverterTest + { + private readonly JsonSerializerSettings _settings; + + public IndexFieldsConverterTest() + { + _settings = new JsonSerializerSettings + { + ContractResolver = new CamelCasePropertyNamesContractResolver(), + ReferenceLoopHandling = ReferenceLoopHandling.Ignore, + NullValueHandling = NullValueHandling.Include + }; + _settings.Converters.Add(new IndexFieldsConverter()); + } + + [Fact] + public void ReadJson_ShouldDeserializeStringArray_WhenJsonIsSimpleStringArray() + { + // Arrange + var json = """ + { + "fields": ["field1", "field2", "field3"] + } + """; + + // Act + var result = JsonConvert.DeserializeObject(json, _settings); + + // Assert + Assert.NotNull(result.Fields); + Assert.Equal(3, result.Fields.Count()); + Assert.Contains("field1", result.Fields); + Assert.Contains("field2", result.Fields); + Assert.Contains("field3", result.Fields); + } + + [Fact] + public void ReadJson_ShouldExtractNameFields_WhenJsonIsObjectArrayWithNameProperty() + { + // Arrange + var json = """ + { + "fields": [ + {"name": "field1", "type": "string"}, + {"name": "field2", "type": "number"}, + {"name": "field3", "type": "boolean"} + ] + } + """; + + // Act + var result = JsonConvert.DeserializeObject(json, _settings); + + // Assert + Assert.NotNull(result.Fields); + Assert.Equal(3, result.Fields.Count()); + Assert.Contains("field1", result.Fields); + Assert.Contains("field2", result.Fields); + Assert.Contains("field3", result.Fields); + } + + [Fact] + public void ReadJson_ShouldFilterOutNullNames_WhenObjectArrayHasNullNameValues() + { + // Arrange + var json = """ + { + "fields": [ + {"name": "field1", "type": "string"}, + {"name": null, "type": "number"}, + {"name": "field3", "type": "boolean"} + ] + } + """; + + // Act + var result = JsonConvert.DeserializeObject(json, _settings); + + // Assert + Assert.NotNull(result.Fields); + Assert.Equal(2, result.Fields.Count()); + Assert.Contains("field1", result.Fields); + Assert.Contains("field3", result.Fields); + Assert.DoesNotContain(null, result.Fields); + } + + [Fact] + public void ReadJson_ShouldHandleEmptyArray() + { + // Arrange + var json = """ + { + "fields": [] + } + """; + + // Act + var result = JsonConvert.DeserializeObject(json, _settings); + + // Assert + Assert.NotNull(result.Fields); + Assert.Empty(result.Fields); + } + + [Fact] + public void ReadJson_ShouldThrowJsonSerializationException_WhenTokenIsNotArray() + { + // Arrange + var json = """ + { + "fields": "not an array" + } + """; + + // Act & Assert + Assert.Throws(() => + JsonConvert.DeserializeObject(json, _settings)); + } + + [Fact] + public void ReadJson_ShouldReturnNull_WhenTokenIsNull() + { + // Arrange + var json = """ + { + "fields": null + } + """; + + // Act + var result = JsonConvert.DeserializeObject(json, _settings); + + // Assert + Assert.Null(result.Fields); + } + + [Fact] + public void ReadJson_ShouldHandleMixedObjectAndStringArray_WhenFirstElementIsNotObject() + { + // Arrange - this tests the case where we have an array but the first element is not an object + var json = """ + { + "fields": ["field1", "field2"] + } + """; + + // Act + var result = JsonConvert.DeserializeObject(json, _settings); + + // Assert + Assert.NotNull(result.Fields); + Assert.Equal(2, result.Fields.Count()); + Assert.Contains("field1", result.Fields); + Assert.Contains("field2", result.Fields); + } + + [Fact] + public void WriteJson_ShouldSerializeToStringArray() + { + // Arrange + var indexResponse = new TestIndexResponse + { + Fields = new[] { "field1", "field2", "field3" } + }; + + // Act + var json = JsonConvert.SerializeObject(indexResponse, _settings); + + // Assert + Assert.Contains("\"fields\":[\"field1\",\"field2\",\"field3\"]", json); + } + + [Fact] + public void WriteJson_ShouldSerializeNullFields() + { + // Arrange + var indexResponse = new TestIndexResponse + { + Fields = null + }; + + // Act + var json = JsonConvert.SerializeObject(indexResponse, _settings); + + // Assert + Assert.Contains("\"fields\":null", json); + } + + [Fact] + public void WriteJson_ShouldSerializeEmptyFields() + { + // Arrange + var indexResponse = new TestIndexResponse + { + Fields = new string[0] + }; + + // Act + var json = JsonConvert.SerializeObject(indexResponse, _settings); + + // Assert + Assert.Contains("\"fields\":[]", json); + } + + [Fact] + public void RoundTrip_ShouldPreserveStringArrayData() + { + // Arrange + var originalFields = new List { "field1", "field2", "field3" }; + var indexResponse = new TestIndexResponse { Fields = originalFields }; + + // Act + var json = JsonConvert.SerializeObject(indexResponse, _settings); + var deserializedResponse = JsonConvert.DeserializeObject(json, _settings); + + // Assert + Assert.NotNull(deserializedResponse.Fields); + Assert.Equal(originalFields.Count, deserializedResponse.Fields.Count()); + Assert.True(originalFields.SequenceEqual(deserializedResponse.Fields)); + } + + // Helper class for testing + private class TestIndexResponse + { + [JsonConverter(typeof(IndexFieldsConverter))] + public IEnumerable Fields { get; set; } + } + } +} diff --git a/arangodb-net-standard/IndexApi/Converters/IndexFieldsConverter.cs b/arangodb-net-standard/IndexApi/Converters/IndexFieldsConverter.cs new file mode 100644 index 00000000..eed840d5 --- /dev/null +++ b/arangodb-net-standard/IndexApi/Converters/IndexFieldsConverter.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace ArangoDBNetStandard.IndexApi.Converters +{ + public class IndexFieldsConverter : JsonConverter> + { + public override IEnumerable ReadJson(JsonReader reader, Type objectType, IEnumerable existingValue, bool hasExistingValue, JsonSerializer serializer) + { + var token = JToken.Load(reader); + if (token.Type == JTokenType.Array) + { + var first = token.First; + if (first != null && first.Type == JTokenType.Object) + { + // This is an array of objects, extract name properties + return token.Select(o => o["name"]?.ToString()).Where(n => !string.IsNullOrEmpty(n)); + } + else + { + // This is an array of strings + return token.ToObject>(); + } + } + else if (token.Type == JTokenType.Null) + { + return null; + } + + throw new JsonSerializationException("Unexpected token type for index fields."); + } + + public override void WriteJson(JsonWriter writer, IEnumerable value, JsonSerializer serializer) + { + if (value == null) + { + writer.WriteNull(); + } + else + { + writer.WriteStartArray(); + foreach (var item in value) + { + writer.WriteValue(item); + } + writer.WriteEndArray(); + } + } + } +} \ No newline at end of file diff --git a/arangodb-net-standard/IndexApi/Models/IndexResponseBase.cs b/arangodb-net-standard/IndexApi/Models/IndexResponseBase.cs index e7afdeef..8f6c1b33 100644 --- a/arangodb-net-standard/IndexApi/Models/IndexResponseBase.cs +++ b/arangodb-net-standard/IndexApi/Models/IndexResponseBase.cs @@ -1,4 +1,6 @@ using System.Collections.Generic; +using ArangoDBNetStandard.IndexApi.Converters; +using Newtonsoft.Json; namespace ArangoDBNetStandard.IndexApi.Models { @@ -11,7 +13,7 @@ public class IndexResponseBase : ResponseBase, IIndexResponse public string Id { get; set; } public string Name { get; set; } - + [JsonConverter(typeof(IndexFieldsConverter))] public IEnumerable Fields { get; set; } public bool? Sparse { get; set; }