/* SPDX-License-Identifier: Apache-2.0 * * The OpenSearch Contributors require contributions made to * this file be licensed under the Apache-2.0 license or a * compatible open source license. */ /* * Modifications Copyright OpenSearch Contributors. See * GitHub history for details. * * Licensed to Elasticsearch B.V. under one or more contributor * license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright * ownership. Elasticsearch B.V. licenses this file to you under * the Apache License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ using System; using System.Collections.Generic; using System.Linq; using OpenSearch.Net; using FluentAssertions; using OpenSearch.Client; using Newtonsoft.Json.Linq; using Tests.Core.Client.Settings; using Tests.Core.Extensions; using Tests.Domain.Extensions; namespace Tests.Core.Serialization { public abstract class RoundTripperBase { internal RoundTripperBase( Func settingsModifier = null, ConnectionSettings.SourceSerializerFactory sourceSerializerFactory = null, IPropertyMappingProvider propertyMappingProvider = null, bool preserveNullInExpected = false ) { PreserveNullInExpected = preserveNullInExpected; if (settingsModifier == null && sourceSerializerFactory == null && propertyMappingProvider == null) Tester = SerializationTester.Default; else { var settings = new AlwaysInMemoryConnectionSettings(sourceSerializerFactory: sourceSerializerFactory, propertyMappingProvider: propertyMappingProvider) .ApplyDomainSettings(); if (settingsModifier != null) settings = settingsModifier(settings); Tester = new SerializationTester(new OpenSearchClient(settings)); } } protected bool PreserveNullInExpected { get; set; } protected SerializationTester Tester { get; } } public class ObjectRoundTripper : RoundTripperBase { private readonly T _object; internal ObjectRoundTripper(T @object, Func settingsModifier = null, ConnectionSettings.SourceSerializerFactory sourceSerializerFactory = null, IPropertyMappingProvider propertyMappingProvider = null ) : base(settingsModifier, sourceSerializerFactory, propertyMappingProvider) => _object = @object; public ObjectRoundTripper PreserveNull() { PreserveNullInExpected = true; return this; } public T RoundTrips() => Tester.AssertRoundTrip(_object); public T RoundTrips(object expectedJson) => Tester.AssertRoundTrip(_object, expectedJson, preserveNullInExpected: PreserveNullInExpected); } public class JsonRoundTripper : RoundTripperBase { private readonly object _expectedJson; private bool _noRoundTrip; internal JsonRoundTripper(object expectedJson, Func settingsModifier = null, ConnectionSettings.SourceSerializerFactory sourceSerializerFactory = null, IPropertyMappingProvider propertyMappingProvider = null, bool preserveNullInExpected = false ) : base(settingsModifier, sourceSerializerFactory, propertyMappingProvider, preserveNullInExpected) => _expectedJson = expectedJson; public JsonRoundTripper NoRoundTrip() { _noRoundTrip = true; return this; } public T DeserializesTo(Action assert = null) { var origin = $"{nameof(JsonRoundTripper)}.{nameof(DeserializesTo)}"; var deserializationResult = Tester.Deserializes(_expectedJson, PreserveNullInExpected); deserializationResult.ShouldBeValid($"{origin} did not deserialize into {typeof(T).Name}"); assert?.Invoke("first deserialization", deserializationResult.Result); if (_noRoundTrip) return deserializationResult.Result; var serializationResult = Tester.Serializes(deserializationResult.Result, _expectedJson, PreserveNullInExpected); deserializationResult.ShouldBeValid($"{origin} first serialization"); assert?.Invoke("first deserialization", deserializationResult.Result); deserializationResult = Tester.Deserializes(serializationResult.Serialized, PreserveNullInExpected); deserializationResult.ShouldBeValid($"{origin} did not deserialize into {typeof(T).Name} a 2nd time"); assert?.Invoke("second deserialization", deserializationResult.Result); return deserializationResult.Result; } public void FromRequest(IResponse response) => ToSerializeTo(response.ApiCall.RequestBodyInBytes); public void FromRequest(Func call) where T : IResponse => FromRequest(call(Tester.Client)); public void FromResponse(IResponse response) => ToSerializeTo(response.ApiCall.ResponseBodyInBytes); public void FromResponse(Func call) where T : IResponse => FromResponse(call(Tester.Client)); private void ToSerializeTo(byte[] json) { var result = Tester.Serializes(json, _expectedJson, PreserveNullInExpected); result.ShouldBeValid(); } public JsonRoundTripperContinuation WhenSerializing(T actual) { switch ((object)actual) { case string _: throw new Exception($"{nameof(WhenSerializing)} was passed a string but it only expects objects"); case byte[] _: throw new Exception($"{nameof(WhenSerializing)} was passed a byte[] but it only expects objects"); } var result = Tester.RoundTrips(actual, _expectedJson, PreserveNullInExpected); result.Success.Should().BeTrue(result.ToString()); return new JsonRoundTripperContinuation(result.Serialized, result.Result, Tester); } public JsonRoundTripper WhenInferringIdOn(T project) where T : class { Tester.Client.Infer.Id(project).Should().Be((string)_expectedJson); return this; } public JsonRoundTripper WhenInferringRoutingOn(T project) where T : class { Tester.Client.Infer.Routing(project).Should().Be((string)_expectedJson); return this; } public JsonRoundTripper ForField(Field field) { Tester.Client.Infer.Field(field).Should().Be((string)_expectedJson); return this; } public JsonRoundTripper AsPropertiesOf(T document) where T : class { var client = Tester.Client; var settings = client.ConnectionSettings; var jo = JObject.Parse(client.RequestResponseSerializer.SerializeToString(document, settings.MemoryStreamFactory)); var serializedProperties = jo.Properties().Select(p => p.Name); if (!(_expectedJson is IEnumerable sut)) throw new ArgumentException("Can not call AsPropertiesOf if sut is not IEnumerable"); sut.Should().BeEquivalentTo(serializedProperties); return this; } public class JsonRoundTripperContinuation { private readonly object _expectedJson; internal JsonRoundTripperContinuation(object expectedJson, T result, SerializationTester tester) { Tester = tester; Result = result; _expectedJson = expectedJson; } private T Result { get; set; } private SerializationTester Tester { get; } public JsonRoundTripperContinuation WhenSerializing(T actual) { var result = Tester.RoundTrips(actual, _expectedJson); result.ShouldBeValid(); Result = result.Result; return this; } public JsonRoundTripperContinuation WhenSerializing(TDifferent actual) { var result = Tester.RoundTrips(actual, _expectedJson); result.ShouldBeValid(); return new JsonRoundTripperContinuation(result.Serialized, result.Result, Tester); } public JsonRoundTripperContinuation AssertSubject(Action assert) { assert(Result); return this; } } } }