Skip to content

Commit

Permalink
SIANXSVC-1193: dotnet-e5e does not handle binary requests as expected
Browse files Browse the repository at this point in the history
Closes SIANXSVC-1193
  • Loading branch information
nachtjasmin committed Jan 25, 2024
1 parent 1d955c9 commit ea87705
Show file tree
Hide file tree
Showing 15 changed files with 399 additions and 13 deletions.
10 changes: 10 additions & 0 deletions src/Anexia.E5E.Tests/Anexia.E5E.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
<TargetFrameworks>net6.0;net8.0</TargetFrameworks>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<LangVersion>12</LangVersion>
</PropertyGroup>

<ItemGroup>
Expand All @@ -27,4 +28,13 @@
<ProjectReference Include="..\Anexia.E5E\Anexia.E5E.csproj"/>
</ItemGroup>

<ItemGroup>
<None Update="TestData\binary_request_with_multiple_files.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="TestData\binary_request_unknown_content_type.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>

</Project>
117 changes: 117 additions & 0 deletions src/Anexia.E5E.Tests/Integration/BinaryRequestIntegrationTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
// --------------------------------------------------------------------------------------------
// <copyright file = "BinaryRequestIntegrationTests.cs" company = "ANEXIA® Internetdienstleistungs GmbH">
// Copyright (c) ANEXIA® Internetdienstleistungs GmbH. All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------

using System.Threading.Tasks;

using Anexia.E5E.Exceptions;
using Anexia.E5E.Functions;
using Anexia.E5E.Tests.TestHelpers;

using static Anexia.E5E.Tests.TestData.TestData;

using Xunit;
using Xunit.Abstractions;

namespace Anexia.E5E.Tests.Integration;

public sealed class BinaryRequestIntegrationTests(ITestOutputHelper outputHelper) : IntegrationTestBase(outputHelper)
{
[Fact]
public async Task DecodeToBytesThrowsForMultipleFiles()
{
await Host.StartWithTestEntrypointAsync(request =>
{
Assert.Throws<E5EMultipleFilesInFormDataException>(() => request.Event.AsBytes());
return null!;
});
await Host.WriteOnceAsync(BinaryRequestWithMultipleFiles);
}

[Fact]
public async Task MultipleFilesAreProperlyDecoded()
{
await Host.StartWithTestEntrypointAsync(request =>
{
var files = request.Event.AsFiles();
Assert.Collection(files, first =>
{
Assert.NotNull(first);
Assert.Equal(first, new E5EFileData
{
Base64Encoded = "SGVsbG8gd29ybGQh",
FileSizeInBytes = 12,
Filename = "my-file-1.name",
ContentType = "application/my-content-type-1",
Charset = "utf-8",
});
}, second =>
{
Assert.NotNull(second);
Assert.Equal(second, new E5EFileData
{
Base64Encoded = "SGVsbG8gd29ybGQh",
FileSizeInBytes = 12,
Filename = "my-file-2.name",
ContentType = "application/my-content-type-2",
Charset = "utf-8",
});
});
return null!;
});
await Host.WriteOnceAsync(BinaryRequestWithMultipleFiles);
}

[Fact]
public async Task UnknownContentType()
{
await Host.StartWithTestEntrypointAsync(request =>
{
Assert.Equal("SGVsbG8gd29ybGQh"u8.ToArray(), request.Event.AsBytes());
return null!;
});
await Host.WriteOnceAsync(BinaryRequestWithUnknownContentType);
}

[Fact]
public async Task FallbackForByteArrayReturnsValidResponse()
{
// act
#pragma warning disable CS0618 // Type or member is obsolete
await Host.StartWithTestEntrypointAsync(_ => E5EResponse.From("SGVsbG8gd29ybGQh"u8.ToArray()));
#pragma warning restore CS0618 // Type or member is obsolete
var response = await Host.WriteOnceAsync(x => x.WithData("test"));

// assert
const string expected =
"""
{"data":{"binary":"SGVsbG8gd29ybGQh","type":"binary","size":0,"name":"dotnet-e5e-binary-response.blob","content_type":"application/octet-stream","charset":"utf-8"},"type":"binary"}
""";
Assert.Contains(expected, response.Stdout);
}

[Fact]
public async Task FileDataReturnsValidResponse()
{
// act
await Host.StartWithTestEntrypointAsync(_ => E5EResponse.From(new E5EFileData
{
Base64Encoded = "SGVsbG8gd29ybGQh",
Type = "binary",
FileSizeInBytes = 16,
Filename = "hello-world.txt",
ContentType = "text/plain",
Charset = "utf-8",
}));
var response = await Host.WriteOnceAsync(x => x.WithData("test"));

// assert
const string expected =
"""
{"data":{"binary":"SGVsbG8gd29ybGQh","type":"binary","size":16,"name":"hello-world.txt","content_type":"text/plain","charset":"utf-8"},"type":"binary"}
""";
Assert.Contains(expected, response.Stdout);
}
}
16 changes: 12 additions & 4 deletions src/Anexia.E5E.Tests/Serialization/SerializationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,11 @@ public void ResponseSerializationWorksBidirectional(string _, E5EResponse input)
public void ResponseSerializationRecognisesCorrectType()
{
Assert.Equal(E5EResponseType.Text, E5EResponse.From("test").Type);
Assert.Equal(E5EResponseType.Binary, E5EResponse.From(Encoding.UTF8.GetBytes("test")).Type);
Assert.Equal(E5EResponseType.Binary, E5EResponse.From(Encoding.UTF8.GetBytes("test").AsEnumerable()).Type);
#pragma warning disable CS0618 // Type or member is obsolete
Assert.Equal(E5EResponseType.Binary, E5EResponse.From("test"u8.ToArray()).Type);
Assert.Equal(E5EResponseType.Binary, E5EResponse.From("test"u8.ToArray().AsEnumerable()).Type);
#pragma warning restore CS0618 // Type or member is obsolete
Assert.Equal(E5EResponseType.Binary, E5EResponse.From(new E5EFileData()).Type);
Assert.Equal(E5EResponseType.StructuredObject, E5EResponse.From(new E5ERuntimeMetadata()).Type);
}

Expand Down Expand Up @@ -179,7 +182,10 @@ private class RequestSerializationTestsData : IEnumerable<object[]>
private readonly Dictionary<string, E5EEvent> _tests = new()
{
{ "simple text request", new TestRequestBuilder().WithData("test").BuildEvent() },
{ "simple binary request", new TestRequestBuilder().WithData(Encoding.UTF8.GetBytes("test")).BuildEvent() },
{
"simple binary request",
new TestRequestBuilder().WithData(new E5EFileData { Base64Encoded = "aGVsbG8=" }).BuildEvent()
},
{
"simple object request",
new TestRequestBuilder().WithData(new Dictionary<string, string> { { "test", "value" } }).BuildEvent()
Expand Down Expand Up @@ -210,7 +216,9 @@ private class ResponseSerializationTestsData : IEnumerable<object[]>
private readonly Dictionary<string, E5EResponse> _tests = new()
{
{ "simple text response", E5EResponse.From("test") },
{ "simple binary response", E5EResponse.From(Encoding.UTF8.GetBytes("test")) },
#pragma warning disable CS0618 // Type or member is obsolete
{ "simple binary response", E5EResponse.From("aGVsbG8="u8.ToArray()) },
#pragma warning restore CS0618 // Type or member is obsolete
{ "simple object response", E5EResponse.From(new Dictionary<string, int> { { "a", 1 }, { "b", 2 } }) },
{
"text response with headers and status code", E5EResponse.From("test", HttpStatusCode.Moved,
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"type":"binary","data":"dGVzdA=="}
{"type":"binary","data":{"binary":"aGVsbG8=","type":"binary","size":0,"name":null,"content_type":null,"charset":null}}
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"data":"dGVzdA==","type":"binary"}
{"data":{"binary":"aGVsbG8=","type":"binary","size":0,"name":"dotnet-e5e-binary-response.blob","content_type":"application/octet-stream","charset":"utf-8"},"type":"binary"}
17 changes: 17 additions & 0 deletions src/Anexia.E5E.Tests/TestData/TestData.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// --------------------------------------------------------------------------------------------
// <copyright file = "TestData.cs" company = "ANEXIA® Internetdienstleistungs GmbH">
// Copyright (c) ANEXIA® Internetdienstleistungs GmbH. All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------

using System.IO;

namespace Anexia.E5E.Tests.TestData;

internal static class TestData
{
private static string ReadTestDataFile(string path) => File.ReadAllText(Path.Combine(Directory.GetCurrentDirectory(), "TestData", path));

internal static string BinaryRequestWithMultipleFiles => ReadTestDataFile("binary_request_with_multiple_files.json");
internal static string BinaryRequestWithUnknownContentType => ReadTestDataFile("binary_request_unknown_content_type.json");
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"context": {
"type": "integration-test",
"async": true,
"date": "2024-01-01T00:00:00Z"
},
"event": {
"type": "binary",
"data": {
"binary": "SGVsbG8gd29ybGQh",
"type": "binary",
"name": "my-file-1.name",
"size": 12,
"content_type": "application/my-content-type-1",
"charset": "utf-8"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"context": {
"type": "integration-test",
"async": true,
"date": "2024-01-01T00:00:00Z"
},
"event": {
"type": "binary",
"data": [
{
"binary": "SGVsbG8gd29ybGQh",
"type": "binary",
"name": "my-file-1.name",
"size": 12,
"content_type": "application/my-content-type-1",
"charset": "utf-8"
},
{
"binary": "SGVsbG8gd29ybGQh",
"type": "binary",
"name": "my-file-2.name",
"size": 12,
"content_type": "application/my-content-type-2",
"charset": "utf-8"
}
]
}
}
5 changes: 4 additions & 1 deletion src/Anexia.E5E.Tests/TestHelpers/TestRequestBuilder.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using System.Text.Json;

Expand All @@ -17,7 +18,9 @@ public TestRequestBuilder WithData<T>(T data)
_requestType = data switch
{
string => E5ERequestDataType.Text,
IEnumerable<byte> => E5ERequestDataType.Binary,
IEnumerable<byte> => throw new InvalidOperationException(
$"E5E does not compose binary requests just from the bytes. Please convert this call to use {nameof(E5EFileData)} instead."),
E5EFileData => E5ERequestDataType.Binary,
_ => E5ERequestDataType.StructuredObject,
};
_data = JsonSerializer.SerializeToElement(data);
Expand Down
21 changes: 21 additions & 0 deletions src/Anexia.E5E/Exceptions/E5EMultipleFilesInFormDataException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// --------------------------------------------------------------------------------------------
// <copyright file = "E5EMultipleFilesInFormDataException.cs" company = "ANEXIA® Internetdienstleistungs GmbH">
// Copyright (c) ANEXIA® Internetdienstleistungs GmbH. All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------

using Anexia.E5E.Functions;

namespace Anexia.E5E.Exceptions;

/// <summary>
/// Exception that is thrown if the <see cref="E5EEvent.AsBytes"/> method is called, but there is more than one
/// file attached to the request.
/// </summary>
public sealed class E5EMultipleFilesInFormDataException : E5EException
{
internal E5EMultipleFilesInFormDataException() : base(
$"There were multiple files attached to this request, so there's no unique binary data. Please use the {nameof(E5EEvent)}.{nameof(E5EEvent.AsFiles)} method instead.")
{
}
}
18 changes: 18 additions & 0 deletions src/Anexia.E5E/Exceptions/E5EObsoleteCodeInUseException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// --------------------------------------------------------------------------------------------
// <copyright file = "E5EObsoleteCodeInUseException.cs" company = "ANEXIA® Internetdienstleistungs GmbH">
// Copyright (c) ANEXIA® Internetdienstleistungs GmbH. All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------

namespace Anexia.E5E.Exceptions;

/// <summary>
/// Exception that is thrown if obsolete code is in usage.
/// Usually this shouldn't not happen, as the APIs are backwards compatible. If it gets thrown nonetheless, this indicates a bug in the library.
/// </summary>
public sealed class E5EObsoleteCodeInUseException : E5EException
{
internal E5EObsoleteCodeInUseException(string message) : base(message)
{
}
}
41 changes: 36 additions & 5 deletions src/Anexia.E5E/Functions/E5EEvent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ namespace Anexia.E5E.Functions;
/// <param name="Data">The data, not processed.</param>
/// <param name="RequestHeaders">The request headers, if any.</param>
/// <param name="Params">The request parameters, if any.</param>
public record E5EEvent(E5ERequestDataType Type,
public record E5EEvent(
E5ERequestDataType Type,
JsonElement? Data = null,
E5EHttpHeaders? RequestHeaders = null,
E5ERequestParameters? Params = null)
Expand Down Expand Up @@ -63,15 +64,45 @@ public record E5EEvent(E5ERequestDataType Type,
}

/// <summary>
/// Returns the value as byte enumerable.
/// Returns the bytes of the attached file.
/// </summary>
/// <exception cref="E5EInvalidConversionException">
/// Thrown if <see cref="Type" /> is not
/// <see cref="E5ERequestDataType.Binary" />.
/// Thrown if <see cref="Type" /> is not <see cref="E5ERequestDataType.Binary" />.
/// </exception>
/// <exception cref="E5EMultipleFilesInFormDataException">Thrown if there are multiple files attached to this request.</exception>
public byte[]? AsBytes()
{
E5EInvalidConversionException.ThrowIfNotMatch(E5ERequestDataType.Binary, Type);
return As(E5ESerializationContext.Default.ByteArray);
if (Data.GetValueOrDefault().ValueKind == JsonValueKind.Array)
throw new E5EMultipleFilesInFormDataException();

#if NET8_0_OR_GREATER
var fileData = As(E5ESerializationContext.Default.E5EFileData);
#else
var fileData = As<E5EFileData>();
#endif

return fileData?.GetBytes().ToArray();
}

/// <summary>
/// If this request is a multipart/form-data request, all files attached to this request are deserialized.
/// </summary>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public IEnumerable<E5EFileData> AsFiles()
{
E5EInvalidConversionException.ThrowIfNotMatch(E5ERequestDataType.Binary, Type);
return Data.GetValueOrDefault().ValueKind switch
{
#if NET8_0_OR_GREATER
JsonValueKind.Object => new[] { As(E5ESerializationContext.Default.E5EFileData)! },
JsonValueKind.Array => As(E5ESerializationContext.Default.IEnumerableE5EFileData),
#else
JsonValueKind.Object => new[] { As<E5EFileData>()! },
JsonValueKind.Array => As<IEnumerable<E5EFileData>>(),
#endif
_ => null,
} ?? Enumerable.Empty<E5EFileData>();
}
}
Loading

0 comments on commit ea87705

Please sign in to comment.