mirror of https://github.com/abpframework/abp.git
3 changed files with 323 additions and 4 deletions
@ -0,0 +1,285 @@ |
|||
using System.Collections.Generic; |
|||
using System.IO; |
|||
using System.Threading.Tasks; |
|||
using Shouldly; |
|||
using Volo.Abp.Content; |
|||
using Volo.Abp.Http.Modeling; |
|||
using Xunit; |
|||
|
|||
namespace Volo.Abp.Http.Modeling; |
|||
|
|||
public class ReturnValueApiDescriptionModel_Tests |
|||
{ |
|||
[Fact] |
|||
public void Create_Without_ContentTypes_Should_Leave_Property_Null() |
|||
{ |
|||
var model = ReturnValueApiDescriptionModel.Create(typeof(string)); |
|||
|
|||
model.ShouldNotBeNull(); |
|||
model.TypeSimple.ShouldBe("string"); |
|||
model.ContentTypes.ShouldBeNull(); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Create_With_ContentTypes_Should_Populate_The_Property() |
|||
{ |
|||
var model = ReturnValueApiDescriptionModel.Create( |
|||
typeof(string), |
|||
new[] { "application/json", "text/plain" }); |
|||
|
|||
model.ContentTypes.ShouldNotBeNull(); |
|||
model.ContentTypes!.ShouldBe(new[] { "application/json", "text/plain" }); |
|||
} |
|||
} |
|||
|
|||
public class ReturnValueApiDescriptionModel_IsRemoteStream_Tests |
|||
{ |
|||
[Fact] |
|||
public void Direct_IRemoteStreamContent_Should_Be_True() |
|||
{ |
|||
var model = ReturnValueApiDescriptionModel.Create(typeof(IRemoteStreamContent)); |
|||
model.IsRemoteStream.ShouldBeTrue(); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Concrete_RemoteStreamContent_Should_Be_True() |
|||
{ |
|||
var model = ReturnValueApiDescriptionModel.Create(typeof(RemoteStreamContent)); |
|||
model.IsRemoteStream.ShouldBeTrue(); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Custom_Subclass_Of_IRemoteStreamContent_Should_Be_True() |
|||
{ |
|||
var model = ReturnValueApiDescriptionModel.Create(typeof(MyCustomStreamContent)); |
|||
model.IsRemoteStream.ShouldBeTrue(); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Task_Of_IRemoteStreamContent_Should_Be_True_After_UnwrapTask() |
|||
{ |
|||
var model = ReturnValueApiDescriptionModel.Create(typeof(Task<IRemoteStreamContent>)); |
|||
model.IsRemoteStream.ShouldBeTrue(); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Task_Of_Custom_Stream_Subclass_Should_Be_True_After_UnwrapTask() |
|||
{ |
|||
var model = ReturnValueApiDescriptionModel.Create(typeof(Task<MyCustomStreamContent>)); |
|||
model.IsRemoteStream.ShouldBeTrue(); |
|||
} |
|||
|
|||
[Fact] |
|||
public void IRemoteStreamContent_Array_Should_Be_True() |
|||
{ |
|||
var model = ReturnValueApiDescriptionModel.Create(typeof(IRemoteStreamContent[])); |
|||
model.IsRemoteStream.ShouldBeTrue(); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Concrete_RemoteStreamContent_Array_Should_Be_True() |
|||
{ |
|||
var model = ReturnValueApiDescriptionModel.Create(typeof(RemoteStreamContent[])); |
|||
model.IsRemoteStream.ShouldBeTrue(); |
|||
} |
|||
|
|||
[Fact] |
|||
public void List_Of_IRemoteStreamContent_Should_Be_True() |
|||
{ |
|||
var model = ReturnValueApiDescriptionModel.Create(typeof(List<IRemoteStreamContent>)); |
|||
model.IsRemoteStream.ShouldBeTrue(); |
|||
} |
|||
|
|||
[Fact] |
|||
public void IEnumerable_Of_IRemoteStreamContent_Should_Be_True() |
|||
{ |
|||
var model = ReturnValueApiDescriptionModel.Create(typeof(IEnumerable<IRemoteStreamContent>)); |
|||
model.IsRemoteStream.ShouldBeTrue(); |
|||
} |
|||
|
|||
[Fact] |
|||
public void IReadOnlyCollection_Of_IRemoteStreamContent_Should_Be_True() |
|||
{ |
|||
var model = ReturnValueApiDescriptionModel.Create(typeof(IReadOnlyCollection<IRemoteStreamContent>)); |
|||
model.IsRemoteStream.ShouldBeTrue(); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Plain_String_Should_Be_False() |
|||
{ |
|||
var model = ReturnValueApiDescriptionModel.Create(typeof(string)); |
|||
model.IsRemoteStream.ShouldBeFalse(); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Int_Should_Be_False() |
|||
{ |
|||
var model = ReturnValueApiDescriptionModel.Create(typeof(int)); |
|||
model.IsRemoteStream.ShouldBeFalse(); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Plain_Dto_Should_Be_False() |
|||
{ |
|||
var model = ReturnValueApiDescriptionModel.Create(typeof(PlainDto)); |
|||
model.IsRemoteStream.ShouldBeFalse(); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Dto_Containing_IRemoteStreamContent_Property_Should_Be_False() |
|||
{ |
|||
// ABP design constraint: IRemoteStreamContent only works as the DIRECT endpoint return type.
|
|||
// When nested inside a DTO, the server JSON-serializes the DTO and the stream metadata only —
|
|||
// the binary payload is lost. So the proxy should NOT use blob mode for this case.
|
|||
var model = ReturnValueApiDescriptionModel.Create(typeof(DtoWithStream)); |
|||
model.IsRemoteStream.ShouldBeFalse(); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Dto_Inheriting_From_Type_With_Stream_Should_Be_False() |
|||
{ |
|||
var model = ReturnValueApiDescriptionModel.Create(typeof(DtoInheritingStream)); |
|||
model.IsRemoteStream.ShouldBeFalse(); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Byte_Array_Should_Be_False() |
|||
{ |
|||
// byte[] is serialized as base64 JSON by ABP, not as binary stream.
|
|||
var model = ReturnValueApiDescriptionModel.Create(typeof(byte[])); |
|||
model.IsRemoteStream.ShouldBeFalse(); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Dictionary_Should_Be_False() |
|||
{ |
|||
var model = ReturnValueApiDescriptionModel.Create(typeof(Dictionary<string, int>)); |
|||
model.IsRemoteStream.ShouldBeFalse(); |
|||
} |
|||
|
|||
private class MyCustomStreamContent : IRemoteStreamContent |
|||
{ |
|||
public string? FileName => null; |
|||
public string? ContentType => null; |
|||
public long? ContentLength => null; |
|||
public Stream GetStream() => Stream.Null; |
|||
public void Dispose() { } |
|||
} |
|||
|
|||
private class PlainDto |
|||
{ |
|||
public int Id { get; set; } |
|||
public string Name { get; set; } = string.Empty; |
|||
} |
|||
|
|||
private class DtoWithStream |
|||
{ |
|||
public string FileName { get; set; } = string.Empty; |
|||
public IRemoteStreamContent? File { get; set; } |
|||
} |
|||
|
|||
private class DtoInheritingStream : PlainDto |
|||
{ |
|||
public IRemoteStreamContent? Stream { get; set; } |
|||
} |
|||
} |
|||
|
|||
public class ReturnValueApiDescriptionModel_BackwardsCompat_Tests |
|||
{ |
|||
[Fact] |
|||
public void Deserializing_Json_Without_ContentTypes_Field_Should_Leave_It_Null() |
|||
{ |
|||
// Old backend JSON (no contentTypes field) read by new client.
|
|||
var json = """
|
|||
{ |
|||
"type": "System.String", |
|||
"typeSimple": "string" |
|||
} |
|||
""";
|
|||
|
|||
var model = System.Text.Json.JsonSerializer.Deserialize<ReturnValueApiDescriptionModel>( |
|||
json, |
|||
new System.Text.Json.JsonSerializerOptions |
|||
{ |
|||
PropertyNameCaseInsensitive = true |
|||
}); |
|||
|
|||
model.ShouldNotBeNull(); |
|||
model!.Type.ShouldBe("System.String"); |
|||
model.TypeSimple.ShouldBe("string"); |
|||
model.ContentTypes.ShouldBeNull(); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Deserializing_Json_With_ContentTypes_Field_Should_Populate_It() |
|||
{ |
|||
var json = """
|
|||
{ |
|||
"type": "System.String", |
|||
"typeSimple": "string", |
|||
"contentTypes": ["application/json", "text/plain"] |
|||
} |
|||
""";
|
|||
|
|||
var model = System.Text.Json.JsonSerializer.Deserialize<ReturnValueApiDescriptionModel>( |
|||
json, |
|||
new System.Text.Json.JsonSerializerOptions |
|||
{ |
|||
PropertyNameCaseInsensitive = true |
|||
}); |
|||
|
|||
model!.ContentTypes.ShouldNotBeNull(); |
|||
model.ContentTypes!.ShouldBe(new[] { "application/json", "text/plain" }); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Serializing_With_Null_ContentTypes_Should_Emit_Null_Or_Omit() |
|||
{ |
|||
var model = ReturnValueApiDescriptionModel.Create(typeof(string)); |
|||
var json = System.Text.Json.JsonSerializer.Serialize(model); |
|||
|
|||
// Either "contentTypes":null or omitted — both are acceptable for old clients.
|
|||
var deserialized = System.Text.Json.JsonSerializer.Deserialize<ReturnValueApiDescriptionModel>( |
|||
json, |
|||
new System.Text.Json.JsonSerializerOptions { PropertyNameCaseInsensitive = true }); |
|||
deserialized!.ContentTypes.ShouldBeNull(); |
|||
} |
|||
} |
|||
|
|||
public class ActionApiDescriptionModel_Tests |
|||
{ |
|||
[Fact] |
|||
public void Create_Should_Propagate_ReturnValueContentTypes() |
|||
{ |
|||
var method = typeof(ActionApiDescriptionModel_Tests).GetMethod(nameof(SampleMethod))!; |
|||
var model = ActionApiDescriptionModel.Create( |
|||
uniqueName: "SampleMethod", |
|||
method: method, |
|||
url: "api/test/sample", |
|||
httpMethod: "GET", |
|||
supportedVersions: new[] { "1.0" }, |
|||
allowAnonymous: true, |
|||
authorizeDatas: null, |
|||
implementFrom: null, |
|||
returnValueContentTypes: new[] { "application/octet-stream" }); |
|||
|
|||
model.ReturnValue.ContentTypes.ShouldNotBeNull(); |
|||
model.ReturnValue.ContentTypes!.ShouldBe(new[] { "application/octet-stream" }); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Create_Without_ReturnValueContentTypes_Should_Leave_Null() |
|||
{ |
|||
var method = typeof(ActionApiDescriptionModel_Tests).GetMethod(nameof(SampleMethod))!; |
|||
var model = ActionApiDescriptionModel.Create( |
|||
uniqueName: "SampleMethod", |
|||
method: method, |
|||
url: "api/test/sample", |
|||
httpMethod: "GET", |
|||
supportedVersions: new[] { "1.0" }); |
|||
|
|||
model.ReturnValue.ContentTypes.ShouldBeNull(); |
|||
} |
|||
|
|||
public string SampleMethod() => string.Empty; |
|||
} |
|||
Loading…
Reference in new issue