Skip to content

Commit

Permalink
Adding test that emulate new extension methods
Browse files Browse the repository at this point in the history
  • Loading branch information
svrooij committed Aug 3, 2024
1 parent e699007 commit b28d524
Show file tree
Hide file tree
Showing 6 changed files with 175 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,17 @@ namespace Microsoft.Kiota.Abstractions.Serialization;

public static partial class KiotaJsonSerializer
{
/// <summary>
/// Serializes the given object into a string based on the content type.
/// </summary>
/// <typeparam name="T">Type of the object to serialize</typeparam>
/// <param name="value">The object to serialize.</param>
/// <returns>The serialized representation as a stream.</returns>
[Obsolete("This method is obsolete, use the extension methods in Microsoft.Kiota.Serialization.Json.IParsableExtensions instead")]
[EditorBrowsable(EditorBrowsableState.Never)]
public static Stream SerializeAsStream<T>(T value) where T : IParsable
=> KiotaSerializer.SerializeAsStream(_jsonContentType, value);

/// <summary>
/// Serializes the given object into a string based on the content type.
/// </summary>
Expand Down Expand Up @@ -72,6 +83,4 @@ public static string SerializeAsString<T>(IEnumerable<T> value) where T : IParsa
[EditorBrowsable(EditorBrowsableState.Never)]
public static Task<string> SerializeAsStringAsync<T>(IEnumerable<T> value, CancellationToken cancellationToken) where T : IParsable =>
KiotaSerializer.SerializeAsStringAsync(_jsonContentType, value, true, cancellationToken);


}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,30 +26,19 @@ public string ValidContentType
/// Default singleton instance of the registry to be used when registering new factories that should be available by default.
/// </summary>
public static readonly SerializationWriterFactoryRegistry DefaultInstance = new();

/// <summary>
/// List of factories that are registered by content type.
/// </summary>
public ConcurrentDictionary<string, ISerializationWriterFactory> ContentTypeAssociatedFactories { get; set; } = new();

/// <summary>
/// Get the relevant <see cref="ISerializationWriter"/> instance for the given content type
/// </summary>
/// <param name="contentType">The content type in use</param>
/// <returns>A <see cref="ISerializationWriter"/> instance to parse the content</returns>
public ISerializationWriter GetSerializationWriter(string contentType)
{
if(string.IsNullOrEmpty(contentType))
throw new ArgumentNullException(nameof(contentType));

var vendorSpecificContentType = contentType.Split(";".ToCharArray(), StringSplitOptions.RemoveEmptyEntries)[0];
if(ContentTypeAssociatedFactories.TryGetValue(vendorSpecificContentType, out var vendorFactory))
return vendorFactory.GetSerializationWriter(vendorSpecificContentType);

var cleanedContentType = ParseNodeFactoryRegistry.contentTypeVendorCleanupRegex.Replace(vendorSpecificContentType, string.Empty);
if(ContentTypeAssociatedFactories.TryGetValue(cleanedContentType, out var factory))
return factory.GetSerializationWriter(cleanedContentType);

throw new InvalidOperationException($"Content type {cleanedContentType} does not have a factory registered to be parsed");
}
=> GetSerializationWriter(contentType, true);

/// <summary>
/// Get the relevant <see cref="ISerializationWriter"/> instance for the given content type
Expand All @@ -59,35 +48,39 @@ public ISerializationWriter GetSerializationWriter(string contentType)
/// <returns>A <see cref="ISerializationWriter"/> instance to parse the content</returns>
public ISerializationWriter GetSerializationWriter(string contentType, bool serializeOnlyChangedValues)
{
if(serializeOnlyChangedValues)
return GetSerializationWriter(contentType);

var factory = GetSerializationWriterFactory(contentType);
if(factory is Store.BackingStoreSerializationWriterProxyFactory backingStoreFactory)
return backingStoreFactory.GetSerializationWriter(contentType, false);
var factory = GetSerializationWriterFactory(contentType, out string actualContentType);
if(!serializeOnlyChangedValues && factory is Store.BackingStoreSerializationWriterProxyFactory backingStoreFactory)
return backingStoreFactory.GetSerializationWriter(actualContentType, false);

return factory.GetSerializationWriter(contentType);
return factory.GetSerializationWriter(actualContentType);
}

/// <summary>
/// Get the relevant <see cref="ISerializationWriterFactory"/> instance for the given content type
/// </summary>
/// <param name="contentType">The content type in use</param>
/// <param name="actualContentType">The content type where a writer factory is found for</param>
/// <returns></returns>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="InvalidOperationException"></exception>
private ISerializationWriterFactory GetSerializationWriterFactory(string contentType)
private ISerializationWriterFactory GetSerializationWriterFactory(string contentType, out string actualContentType)
{
if(string.IsNullOrEmpty(contentType))
throw new ArgumentNullException(nameof(contentType));

var vendorSpecificContentType = contentType.Split(";".ToCharArray(), StringSplitOptions.RemoveEmptyEntries)[0];
if(ContentTypeAssociatedFactories.TryGetValue(vendorSpecificContentType, out var vendorFactory))
{
actualContentType = vendorSpecificContentType;
return vendorFactory;
}

var cleanedContentType = ParseNodeFactoryRegistry.contentTypeVendorCleanupRegex.Replace(vendorSpecificContentType, string.Empty);
if(ContentTypeAssociatedFactories.TryGetValue(cleanedContentType, out var factory))
{
actualContentType = cleanedContentType;
return factory;
}

throw new InvalidOperationException($"Content type {cleanedContentType} does not have a factory registered to be parsed");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,28 +14,28 @@ public class SerializationWriterProxyFactory : ISerializationWriterFactory
/// <summary>
/// The valid content type for the <see cref="SerializationWriterProxyFactory"/>
/// </summary>
public string ValidContentType { get { return _concrete.ValidContentType; } }
public string ValidContentType { get { return ProxiedSerializationWriterFactory.ValidContentType; } }

/// <summary>
/// The concrete factory to wrap.
/// The factory that is being proxied.
/// </summary>
protected readonly ISerializationWriterFactory _concrete;
protected readonly ISerializationWriterFactory ProxiedSerializationWriterFactory;
private readonly Action<IParsable> _onBefore;
private readonly Action<IParsable> _onAfter;
private readonly Action<IParsable, ISerializationWriter> _onStartSerialization;
/// <summary>
/// Creates a new proxy factory that wraps the specified concrete factory while composing the before and after callbacks.
/// </summary>
/// <param name="concrete">The concrete factory to wrap.</param>
/// <param name="factoryToWrap">The concrete factory to wrap.</param>
/// <param name="onBeforeSerialization">The callback to invoke before the serialization of any model object.</param>
/// <param name="onAfterSerialization">The callback to invoke after the serialization of any model object.</param>
/// <param name="onStartSerialization">The callback to invoke when serialization of the entire model has started.</param>
public SerializationWriterProxyFactory(ISerializationWriterFactory concrete,
public SerializationWriterProxyFactory(ISerializationWriterFactory factoryToWrap,
Action<IParsable> onBeforeSerialization,
Action<IParsable> onAfterSerialization,
Action<IParsable, ISerializationWriter> onStartSerialization)
{
_concrete = concrete ?? throw new ArgumentNullException(nameof(concrete));
ProxiedSerializationWriterFactory = factoryToWrap ?? throw new ArgumentNullException(nameof(factoryToWrap));
_onBefore = onBeforeSerialization;
_onAfter = onAfterSerialization;
_onStartSerialization = onStartSerialization;
Expand All @@ -47,7 +47,7 @@ public SerializationWriterProxyFactory(ISerializationWriterFactory concrete,
/// <returns>A new <see cref="ISerializationWriter" /> instance for the given content type.</returns>
public ISerializationWriter GetSerializationWriter(string contentType)
{
var writer = _concrete.GetSerializationWriter(contentType);
var writer = ProxiedSerializationWriterFactory.GetSerializationWriter(contentType);
var originalBefore = writer.OnBeforeObjectSerialization;
var originalAfter = writer.OnAfterObjectSerialization;
var originalStart = writer.OnStartObjectSerialization;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,14 @@ public BackingStoreSerializationWriterProxyFactory(ISerializationWriterFactory c
/// Get the serialization writer for the given content type.
/// </summary>
/// <param name="contentType">The content type for which a serialization writer should be created.</param>
/// <param name="serializeOnlyChangedValues">By default a backing store is used, and you'll only get changed properties</param>
/// <param name="serializeOnlyChangedValues">By default, a backing store is used, and you'll only get changed properties</param>
/// <returns></returns>
public ISerializationWriter GetSerializationWriter(string contentType, bool serializeOnlyChangedValues)
{
if(serializeOnlyChangedValues)
return base.GetSerializationWriter(contentType);

return _concrete.GetSerializationWriter(contentType);
return ProxiedSerializationWriterFactory.GetSerializationWriter(contentType);
}
}
}
99 changes: 99 additions & 0 deletions tests/serialization/json/IParsableExtensionsTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
using System.IO;
using Microsoft.Kiota.Abstractions.Serialization;
using Microsoft.Kiota.Abstractions.Store;
using Microsoft.Kiota.Serialization.Json.Tests.Mocks;
using Xunit;

namespace Microsoft.Kiota.Serialization.Json.Tests
{
public class IParsableExtensionsTests
{
private const string _jsonContentType = "application/json";
private readonly SerializationWriterFactoryRegistry _serializationWriterFactoryRegistry;

public IParsableExtensionsTests()
{
_serializationWriterFactoryRegistry = new SerializationWriterFactoryRegistry();
_serializationWriterFactoryRegistry.ContentTypeAssociatedFactories.TryAdd(_jsonContentType, new BackingStoreSerializationWriterProxyFactory(new JsonSerializationWriterFactory()));
}

[Theory]
[InlineData(null)]
[InlineData(true)]
[InlineData(false)]
public void GetSerializationWriter_RetunsJsonSerializationWriter(bool? serializeOnlyChangedValues)
{
// Arrange

// Act
using var writer = serializeOnlyChangedValues.HasValue
? _serializationWriterFactoryRegistry.GetSerializationWriter(_jsonContentType, serializeOnlyChangedValues.Value)
: _serializationWriterFactoryRegistry.GetSerializationWriter(_jsonContentType);

// Assert
Assert.NotNull(writer);
Assert.IsType<JsonSerializationWriter>(writer);
}

[Fact]
public void GetSerializationWriterSerializedChangedTrue_RetunsEmptyJson()
{
// Arrange
var testUser = new BackedTestEntity { Id = "1", Name = "testUser" };
testUser.BackingStore.InitializationCompleted = true;
using var writer = _serializationWriterFactoryRegistry.GetSerializationWriter(_jsonContentType, true);

// Act
writer.WriteObjectValue(null, testUser);
using var stream = writer.GetSerializedContent();
var serializedContent = GetStringFromStream(stream);

// Assert
Assert.NotNull(serializedContent);
Assert.Equal("{}", serializedContent);
}

[Fact]
public void GetSerializationWriterSerializedChangedTrue_ChangedName_ReturnsJustName()
{
// Arrange
var testUser = new BackedTestEntity { Id = "1", Name = "testUser" };
testUser.BackingStore.InitializationCompleted = true;
testUser.Name = "Stephan";
using var writer = _serializationWriterFactoryRegistry.GetSerializationWriter(_jsonContentType, true);

// Act
writer.WriteObjectValue(null, testUser);
using var stream = writer.GetSerializedContent();
var serializedContent = GetStringFromStream(stream);

// Assert
Assert.NotNull(serializedContent);
Assert.Equal("{\"name\":\"Stephan\"}", serializedContent);
}

[Fact]
public void GetSerializationWriterSerializedChangedFalse_SerializesEntireObject()
{
// Arrange
var testUser = new BackedTestEntity { Id = "1", Name = "testUser" };
testUser.BackingStore.InitializationCompleted = true;
using var writer = _serializationWriterFactoryRegistry.GetSerializationWriter(_jsonContentType, false);

// Act
writer.WriteObjectValue(null, testUser);
using var stream = writer.GetSerializedContent();
var serializedContent = GetStringFromStream(stream);

// Assert
Assert.NotNull(serializedContent);
Assert.Equal("{\"id\":\"1\",\"name\":\"testUser\"}", serializedContent);
}

private static string GetStringFromStream(Stream stream)
{
using var reader = new StreamReader(stream);
return reader.ReadToEnd();
}
}
}
40 changes: 40 additions & 0 deletions tests/serialization/json/Mocks/BackedTestEntity.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using System;
using System.Collections.Generic;
using Microsoft.Kiota.Abstractions.Serialization;
using Microsoft.Kiota.Abstractions.Store;

namespace Microsoft.Kiota.Serialization.Json.Tests.Mocks
{
public class BackedTestEntity : IParsable, IBackedModel
{
public BackedTestEntity()
{
BackingStore = new InMemoryBackingStore();
}

public IBackingStore BackingStore { get; private set; }

public string Id
{
get { return BackingStore?.Get<string>("id"); }
set { BackingStore?.Set("id", value); }
}
public string Name
{
get { return BackingStore?.Get<string>("name"); }
set { BackingStore?.Set("name", value); }
}

public IDictionary<string, Action<IParseNode>> GetFieldDeserializers() =>
new Dictionary<string, Action<IParseNode>> {
{ "id", n => { Id = n.GetStringValue(); } },
{ "name", n => { Name = n.GetStringValue(); } },
};
public void Serialize(ISerializationWriter writer)
{
_ = writer ?? throw new ArgumentNullException(nameof(writer));
writer.WriteStringValue("id", Id);
writer.WriteStringValue("name", Name);
}
}
}

0 comments on commit b28d524

Please sign in to comment.