From fefa2da21849514098d213f658bd8014942e863c Mon Sep 17 00:00:00 2001 From: Pavel Gross Date: Thu, 4 Jul 2024 13:29:29 +0300 Subject: [PATCH] [#16] Unit tests Signed-off-by: Pavel Gross --- src/FrostFS.SDK.ClientV2/Client.cs | 30 +- .../Interfaces/IFrostFSClient.cs | 4 +- .../Mappers/Object/Object.cs | 4 +- .../Mappers/Object/ObjectHeaderMapper.cs | 4 +- .../Mappers/Session/Session.cs | 2 - .../Services/ContainerServiceProvider.cs | 2 +- .../Services/NetmapServiceProvider.cs | 4 - .../Services/ObjectServiceProvider.cs | 40 +-- .../Services/ObjectTools.cs | 3 +- .../Tools/ClientEnvironment.cs | 1 - src/FrostFS.SDK.ClientV2/Tools/Object.cs | 12 +- .../Tools/RequestConstructor.cs | 1 + .../Tools/RequestSigner.cs | 10 +- src/FrostFS.SDK.ClientV2/Tools/Verifier.cs | 1 + src/FrostFS.SDK.ModelsV2/CallStatistics.cs | 2 +- .../{ => Containers}/Container.cs | 1 - .../{ => Containers}/ContainerId.cs | 0 src/FrostFS.SDK.ModelsV2/Context.cs | 8 +- .../Object/{Object.cs => FrostFsObject.cs} | 10 +- .../Object/ObjectHeader.cs | 24 +- src/FrostFS.SDK.ModelsV2/Object/ObjectId.cs | 12 +- .../Interfaces/IRequest.cs | 4 +- .../container/Extension.Message.cs | 1 + .../netmap/Extension.Message.cs | 3 +- .../object/Extension.Message.cs | 1 + .../session/Extension.Message.cs | 3 +- src/FrostFS.SDK.Tests/ContainerTest.cs | 150 +++++----- .../FrostFS.SDK.Tests.csproj | 10 + .../Mocks/AsyncStreamReaderMock.cs | 60 ++++ .../Mocks/ClientStreamWriter.cs | 29 ++ .../ContainerServiceBase.cs | 1 + ...ContainerMock copy.cs => ContainerStub.cs} | 1 - .../DeleteContainerMock.cs | 54 ---- .../ContainerServiceMocks/GetContainerMock.cs | 111 +++++++- .../ContainerServiceMocks/PutContainerMock.cs | 88 ------ .../ContainerServiceMocks/RequestData.cs | 12 + src/FrostFS.SDK.Tests/Mocks/NetmapMock.cs | 14 - src/FrostFS.SDK.Tests/Mocks/NetworkMocker.cs | 78 ++++++ src/FrostFS.SDK.Tests/Mocks/ObjectMock.cs | 257 +++++++++++++----- src/FrostFS.SDK.Tests/Mocks/SessionMock.cs | 2 +- src/FrostFS.SDK.Tests/ObjectTest.cs | 243 ++++++++++++++--- .../{ClientTestLive.cs => SmokeTests.cs} | 64 +++-- src/FrostFS.SDK.Tests/TestData/cat.jpg | Bin 0 -> 6740 bytes 43 files changed, 884 insertions(+), 477 deletions(-) rename src/FrostFS.SDK.ModelsV2/{ => Containers}/Container.cs (99%) rename src/FrostFS.SDK.ModelsV2/{ => Containers}/ContainerId.cs (100%) rename src/FrostFS.SDK.ModelsV2/Object/{Object.cs => FrostFsObject.cs} (82%) create mode 100644 src/FrostFS.SDK.Tests/Mocks/AsyncStreamReaderMock.cs create mode 100644 src/FrostFS.SDK.Tests/Mocks/ClientStreamWriter.cs rename src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/{GetContainerMock copy.cs => ContainerStub.cs} (99%) delete mode 100644 src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/DeleteContainerMock.cs delete mode 100644 src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/PutContainerMock.cs create mode 100644 src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/RequestData.cs delete mode 100644 src/FrostFS.SDK.Tests/Mocks/NetmapMock.cs create mode 100644 src/FrostFS.SDK.Tests/Mocks/NetworkMocker.cs rename src/FrostFS.SDK.Tests/{ClientTestLive.cs => SmokeTests.cs} (85%) create mode 100644 src/FrostFS.SDK.Tests/TestData/cat.jpg diff --git a/src/FrostFS.SDK.ClientV2/Client.cs b/src/FrostFS.SDK.ClientV2/Client.cs index ca1acea..6e9cbd0 100644 --- a/src/FrostFS.SDK.ClientV2/Client.cs +++ b/src/FrostFS.SDK.ClientV2/Client.cs @@ -13,7 +13,6 @@ using Microsoft.Extensions.Options; using System; using System.Collections.Generic; using System.Net.Http; -using System.Security.Cryptography; using System.Threading.Tasks; using Version = FrostFS.SDK.ModelsV2.Version; @@ -168,7 +167,7 @@ public class Client : IFrostFSClient return service.GetObjectHeadAsync(containerId, objectId, ctx!); } - public Task GetObjectAsync(ContainerId containerId, ObjectId objectId, Context? ctx = default) + public Task GetObjectAsync(ContainerId containerId, ObjectId objectId, Context? ctx = default) { var service = GetObjectService(ref ctx); return service.GetObjectAsync(containerId, objectId, ctx!); @@ -180,7 +179,7 @@ public class Client : IFrostFSClient return service.PutObjectAsync(putObjectParameters, ctx!); } - public Task PutSingleObjectAsync(ModelsV2.Object obj, Context? ctx = default) + public Task PutSingleObjectAsync(FrostFsObject obj, Context? ctx = default) { var service = GetObjectService(ref ctx); return service.PutSingleObjectAsync(obj, ctx!); @@ -312,26 +311,21 @@ public class Client : IFrostFSClient private static GrpcChannel InitGrpcChannel(string host, GrpcChannelOptions? channelOptions) { - Uri uri; try { - uri = new Uri(host); + var uri = new Uri(host); + + if (channelOptions != null) + return GrpcChannel.ForAddress(uri, channelOptions); + + return GrpcChannel.ForAddress(uri, new GrpcChannelOptions + { + HttpHandler = new HttpClientHandler() + }); } catch (UriFormatException e) { - var msg = $"Host '{host}' has invalid format. Error: {e.Message}"; - throw new ArgumentException(msg); + throw new ArgumentException($"Host '{host}' has invalid format. Error: {e.Message}"); } - - if (channelOptions != null) - { - return GrpcChannel.ForAddress(uri, channelOptions); - } - - - return GrpcChannel.ForAddress(uri, new GrpcChannelOptions - { - HttpHandler = new HttpClientHandler() - }); } } diff --git a/src/FrostFS.SDK.ClientV2/Interfaces/IFrostFSClient.cs b/src/FrostFS.SDK.ClientV2/Interfaces/IFrostFSClient.cs index df5bcce..bed4136 100644 --- a/src/FrostFS.SDK.ClientV2/Interfaces/IFrostFSClient.cs +++ b/src/FrostFS.SDK.ClientV2/Interfaces/IFrostFSClient.cs @@ -34,11 +34,11 @@ public interface IFrostFSClient : IDisposable #region Object Task GetObjectHeadAsync(ContainerId containerId, ObjectId objectId, Context? context = default); - Task GetObjectAsync(ContainerId containerId, ObjectId objectId, Context? context = default); + Task GetObjectAsync(ContainerId containerId, ObjectId objectId, Context? context = default); Task PutObjectAsync(PutObjectParameters putObjectParameters, Context? context = default); - Task PutSingleObjectAsync(ModelsV2.Object obj, Context? context = default); + Task PutSingleObjectAsync(FrostFsObject obj, Context? context = default); Task DeleteObjectAsync(ContainerId containerId, ObjectId objectId, Context? context = default); diff --git a/src/FrostFS.SDK.ClientV2/Mappers/Object/Object.cs b/src/FrostFS.SDK.ClientV2/Mappers/Object/Object.cs index be61b13..2804b65 100644 --- a/src/FrostFS.SDK.ClientV2/Mappers/Object/Object.cs +++ b/src/FrostFS.SDK.ClientV2/Mappers/Object/Object.cs @@ -4,9 +4,9 @@ namespace FrostFS.SDK.ClientV2.Mappers.GRPC; public static class ObjectMapper { - public static ModelsV2.Object ToModel(this Object.Object obj) + public static FrostFsObject ToModel(this Object.Object obj) { - return new ModelsV2.Object( + return new FrostFsObject( ObjectId.FromHash(obj.ObjectId.Value.ToByteArray()), obj.Header.ToModel(), obj.Payload.ToByteArray()); diff --git a/src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectHeaderMapper.cs b/src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectHeaderMapper.cs index 6ded9d2..2292811 100644 --- a/src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectHeaderMapper.cs +++ b/src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectHeaderMapper.cs @@ -44,7 +44,7 @@ public static class ObjectHeaderMapper SplitId = split.SplitId != null ? ByteString.CopyFrom(split.SplitId.ToBinary()) : null }; - if (split.Children != null && split.Children.Any()) + if (split.Children != null && split.Children.Count != 0) head.Split.Children.AddRange(split.Children.Select(id => id.ToGrpcMessage())); } @@ -81,7 +81,7 @@ public static class ObjectHeaderMapper Previous = header.Split.Previous?.ToModel() }; - if (header.Split.Children.Any()) + if (header.Split.Children.Count != 0) model.Split.Children.AddRange(header.Split.Children.Select(x => x.ToModel())); } diff --git a/src/FrostFS.SDK.ClientV2/Mappers/Session/Session.cs b/src/FrostFS.SDK.ClientV2/Mappers/Session/Session.cs index 4b85759..e565891 100644 --- a/src/FrostFS.SDK.ClientV2/Mappers/Session/Session.cs +++ b/src/FrostFS.SDK.ClientV2/Mappers/Session/Session.cs @@ -1,5 +1,3 @@ - -using System; using Google.Protobuf; namespace FrostFS.SDK.ClientV2; diff --git a/src/FrostFS.SDK.ClientV2/Services/ContainerServiceProvider.cs b/src/FrostFS.SDK.ClientV2/Services/ContainerServiceProvider.cs index b2aadad..6a3036b 100644 --- a/src/FrostFS.SDK.ClientV2/Services/ContainerServiceProvider.cs +++ b/src/FrostFS.SDK.ClientV2/Services/ContainerServiceProvider.cs @@ -1,11 +1,11 @@ using System.Threading.Tasks; using FrostFS.SDK.ClientV2.Mappers.GRPC.Netmap; -using FrostFS.SDK.ModelsV2; using FrostFS.Container; using FrostFS.SDK.ClientV2.Mappers.GRPC; using FrostFS.SDK.Cryptography; using System.Collections.Generic; +using FrostFS.SDK.ModelsV2; namespace FrostFS.SDK.ClientV2; diff --git a/src/FrostFS.SDK.ClientV2/Services/NetmapServiceProvider.cs b/src/FrostFS.SDK.ClientV2/Services/NetmapServiceProvider.cs index 1060a20..441d13b 100644 --- a/src/FrostFS.SDK.ClientV2/Services/NetmapServiceProvider.cs +++ b/src/FrostFS.SDK.ClientV2/Services/NetmapServiceProvider.cs @@ -51,10 +51,6 @@ internal class NetmapServiceProvider : ContextAccessor var response = await netmapServiceClient.LocalNodeInfoAsync(request, null, ctx.Deadline, ctx.CancellationToken); - //var response = await Context.InvokeAsyncUnaryWithMetrics(() => - // netmapServiceClient.LocalNodeInfoAsync(request, null, ctx.Deadline, ctx.CancellationToken), - // nameof(netmapServiceClient.LocalNodeInfoAsync)); - Verifier.CheckResponse(response); return response.Body.ToModel(); diff --git a/src/FrostFS.SDK.ClientV2/Services/ObjectServiceProvider.cs b/src/FrostFS.SDK.ClientV2/Services/ObjectServiceProvider.cs index 2439c6e..1937e70 100644 --- a/src/FrostFS.SDK.ClientV2/Services/ObjectServiceProvider.cs +++ b/src/FrostFS.SDK.ClientV2/Services/ObjectServiceProvider.cs @@ -43,7 +43,7 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C return response.Body.Header.Header.ToModel(); } - internal async Task GetObjectAsync(ContainerId cid, ObjectId oid, Context ctx) + internal async Task GetObjectAsync(ContainerId cid, ObjectId oid, Context ctx) { var sessionToken = await GetOrCreateSession(ctx); @@ -70,9 +70,7 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C request.Sign(Context.Key); - var obj = await GetObject(request, ctx); - - return obj; + return await GetObject(request, ctx); } internal Task PutObjectAsync(PutObjectParameters parameters, Context ctx) @@ -89,7 +87,7 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C return PutStreamObject(parameters, ctx); } - internal async Task PutSingleObjectAsync(ModelsV2.Object modelObject, Context ctx) + internal async Task PutSingleObjectAsync(FrostFsObject modelObject, Context ctx) { var sessionToken = await GetOrCreateSession(ctx); @@ -178,7 +176,8 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C ObjectId? objectId; List sentObjectIds = []; - ModelsV2.Object? currentObject; + + FrostFsObject? currentObject; var networkSettings = await Context.Client.GetNetworkSettingsAsync(ctx); @@ -199,7 +198,7 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C largeObject.AppendBlock(buffer, bytesCount); - currentObject = new ModelsV2.Object(header.ContainerId, bytesCount < partSize ? buffer.Take(bytesCount).ToArray() : buffer) + currentObject = new FrostFsObject(header.ContainerId, bytesCount < partSize ? buffer.Take(bytesCount).ToArray() : buffer) .SetSplit(split); if (largeObject.PayloadLength == fullLength) @@ -231,6 +230,7 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C } currentObject.AddAttributes(parameters.Header!.Attributes); + return await PutSingleObjectAsync(currentObject, ctx); } @@ -247,7 +247,7 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C var oid = new ObjectID { Value = hdr.Sha256() }; - var request = new PutRequest + var initRequest = new PutRequest { Body = new PutRequest.Types.Body { @@ -258,8 +258,8 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C } }; - request.AddMetaHeader(); - request.AddObjectSessionToken( + initRequest.AddMetaHeader(); + initRequest.AddObjectSessionToken( sessionToken, hdr.ContainerId, oid, @@ -267,9 +267,10 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C Context.Key ); - request.Sign(Context.Key); + initRequest.Sign(Context.Key); - using var stream = await PutObjectInit(request, ctx); + using var stream = await PutObjectInit(initRequest, ctx); + var buffer = new byte[Constants.ObjectChunkSize]; while (true) @@ -279,14 +280,17 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C if (bufferLength == 0) break; - request.Body = new PutRequest.Types.Body + var chunkRequest = new PutRequest(initRequest) { - Chunk = ByteString.CopyFrom(buffer[..bufferLength]), + Body = new PutRequest.Types.Body + { + Chunk = ByteString.CopyFrom(buffer[..bufferLength]), + }, + VerifyHeader = null }; - request.VerifyHeader = null; - request.Sign(Context.Key); - await stream.Write(request); + chunkRequest.Sign(Context.Key); + await stream.Write(chunkRequest); } var response = await stream.Close(); @@ -295,7 +299,7 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C return ObjectId.FromHash(response.Body.ObjectId.Value.ToByteArray()); } - private async Task GetObject(GetRequest request, Context ctx) + private async Task GetObject(GetRequest request, Context ctx) { var reader = GetObjectInit(request, ctx); diff --git a/src/FrostFS.SDK.ClientV2/Services/ObjectTools.cs b/src/FrostFS.SDK.ClientV2/Services/ObjectTools.cs index 95ba626..35f68ae 100644 --- a/src/FrostFS.SDK.ClientV2/Services/ObjectTools.cs +++ b/src/FrostFS.SDK.ClientV2/Services/ObjectTools.cs @@ -19,7 +19,7 @@ internal class ObjectTools(ClientEnvironment ctx) : ContextAccessor (ctx) return new ObjectID { Value = grpcHeader.Sha256() }.ToModel(); } - internal Object.Object CreateObject(ModelsV2.Object @object) + internal Object.Object CreateObject(FrostFsObject @object) { var grpcHeader = @object.Header.ToGrpcMessage(); @@ -100,5 +100,4 @@ internal class ObjectTools(ClientEnvironment ctx) : ContextAccessor (ctx) Sum = ByteString.CopyFrom(data.Sha256()) }; } - } \ No newline at end of file diff --git a/src/FrostFS.SDK.ClientV2/Tools/ClientEnvironment.cs b/src/FrostFS.SDK.ClientV2/Tools/ClientEnvironment.cs index b37e7fc..dae8ff2 100644 --- a/src/FrostFS.SDK.ClientV2/Tools/ClientEnvironment.cs +++ b/src/FrostFS.SDK.ClientV2/Tools/ClientEnvironment.cs @@ -1,4 +1,3 @@ -using FrostFS.SDK.ClientV2.Interfaces; using FrostFS.SDK.ModelsV2; using Grpc.Net.Client; using System; diff --git a/src/FrostFS.SDK.ClientV2/Tools/Object.cs b/src/FrostFS.SDK.ClientV2/Tools/Object.cs index e7e59f1..0a0290e 100644 --- a/src/FrostFS.SDK.ClientV2/Tools/Object.cs +++ b/src/FrostFS.SDK.ClientV2/Tools/Object.cs @@ -7,31 +7,31 @@ namespace FrostFS.SDK.ClientV2.Extensions; public static class ObjectExtensions { - public static ModelsV2.Object SetPayloadLength(this ModelsV2.Object obj, ulong length) + public static FrostFsObject SetPayloadLength(this FrostFsObject obj, ulong length) { obj.Header.PayloadLength = length; return obj; } - public static ModelsV2.Object AddAttribute(this ModelsV2.Object obj, string key, string value) + public static FrostFsObject AddAttribute(this FrostFsObject obj, string key, string value) { obj.AddAttribute(new ObjectAttribute(key, value)); return obj; } - public static ModelsV2.Object AddAttribute(this ModelsV2.Object obj, ObjectAttribute attribute) + public static FrostFsObject AddAttribute(this FrostFsObject obj, ObjectAttribute attribute) { obj.Header.Attributes.Add(attribute); return obj; } - public static ModelsV2.Object AddAttributes(this ModelsV2.Object obj, IEnumerable attributes) + public static FrostFsObject AddAttributes(this FrostFsObject obj, IEnumerable attributes) { obj.Header.Attributes.AddRange(attributes); return obj; } - public static ModelsV2.Object SetSplit(this ModelsV2.Object obj, Split split) + public static FrostFsObject SetSplit(this FrostFsObject obj, Split split) { obj.Header.Split = split; return obj; @@ -43,7 +43,7 @@ public static class ObjectExtensions return linkObject; } - public static ModelsV2.Object CalculateObjectId(this ModelsV2.Object obj) + public static FrostFsObject CalculateObjectId(this FrostFsObject obj) { if (obj.Payload == null) throw new MissingFieldException("Payload cannot be null"); diff --git a/src/FrostFS.SDK.ClientV2/Tools/RequestConstructor.cs b/src/FrostFS.SDK.ClientV2/Tools/RequestConstructor.cs index 6b1d4cf..6e2a5dc 100644 --- a/src/FrostFS.SDK.ClientV2/Tools/RequestConstructor.cs +++ b/src/FrostFS.SDK.ClientV2/Tools/RequestConstructor.cs @@ -2,6 +2,7 @@ using System.Security.Cryptography; using FrostFS.Refs; using FrostFS.SDK.ClientV2.Mappers.GRPC; using FrostFS.SDK.ModelsV2; +using FrostFS.SDK.ProtosV2.Interfaces; using FrostFS.Session; namespace FrostFS.SDK.ClientV2; diff --git a/src/FrostFS.SDK.ClientV2/Tools/RequestSigner.cs b/src/FrostFS.SDK.ClientV2/Tools/RequestSigner.cs index 6016ddf..d9fa633 100644 --- a/src/FrostFS.SDK.ClientV2/Tools/RequestSigner.cs +++ b/src/FrostFS.SDK.ClientV2/Tools/RequestSigner.cs @@ -12,6 +12,7 @@ using Org.BouncyCastle.Math; using FrostFS.Refs; using FrostFS.SDK.Cryptography; using FrostFS.Session; +using FrostFS.SDK.ProtosV2.Interfaces; namespace FrostFS.SDK.ClientV2; @@ -67,20 +68,21 @@ public static class RequestSigner { var hash = new byte[65]; hash[0] = 0x04; - key - .SignHash(SHA512.Create().ComputeHash(data)) - .CopyTo(hash, 1); + + key.SignHash(SHA512.Create().ComputeHash(data)).CopyTo(hash, 1); + return hash; } public static Signature SignMessagePart(this ECDsa key, IMessage? data) { - var data2Sign = data is null ? Array.Empty() : data.ToByteArray(); + var data2Sign = data is null ? [] : data.ToByteArray(); var sig = new Signature { Key = ByteString.CopyFrom(key.PublicKey()), Sign = ByteString.CopyFrom(key.SignData(data2Sign)), }; + return sig; } diff --git a/src/FrostFS.SDK.ClientV2/Tools/Verifier.cs b/src/FrostFS.SDK.ClientV2/Tools/Verifier.cs index 5811ff1..6e65c02 100644 --- a/src/FrostFS.SDK.ClientV2/Tools/Verifier.cs +++ b/src/FrostFS.SDK.ClientV2/Tools/Verifier.cs @@ -13,6 +13,7 @@ using FrostFS.Refs; using FrostFS.SDK.ClientV2.Mappers.GRPC; using FrostFS.SDK.Cryptography; using FrostFS.Session; +using FrostFS.SDK.ProtosV2.Interfaces; namespace FrostFS.SDK.ClientV2; diff --git a/src/FrostFS.SDK.ModelsV2/CallStatistics.cs b/src/FrostFS.SDK.ModelsV2/CallStatistics.cs index e6424b6..a6cbcbf 100644 --- a/src/FrostFS.SDK.ModelsV2/CallStatistics.cs +++ b/src/FrostFS.SDK.ModelsV2/CallStatistics.cs @@ -2,6 +2,6 @@ namespace FrostFS.SDK.ModelsV2; public class CallStatistics { - public string MethodName { get; set; } + public string? MethodName { get; set; } public long ElapsedMicroSeconds { get; set; } } \ No newline at end of file diff --git a/src/FrostFS.SDK.ModelsV2/Container.cs b/src/FrostFS.SDK.ModelsV2/Containers/Container.cs similarity index 99% rename from src/FrostFS.SDK.ModelsV2/Container.cs rename to src/FrostFS.SDK.ModelsV2/Containers/Container.cs index 4533325..02543b3 100644 --- a/src/FrostFS.SDK.ModelsV2/Container.cs +++ b/src/FrostFS.SDK.ModelsV2/Containers/Container.cs @@ -1,5 +1,4 @@ using System; - using FrostFS.SDK.ModelsV2.Enums; using FrostFS.SDK.ModelsV2.Netmap; diff --git a/src/FrostFS.SDK.ModelsV2/ContainerId.cs b/src/FrostFS.SDK.ModelsV2/Containers/ContainerId.cs similarity index 100% rename from src/FrostFS.SDK.ModelsV2/ContainerId.cs rename to src/FrostFS.SDK.ModelsV2/Containers/ContainerId.cs diff --git a/src/FrostFS.SDK.ModelsV2/Context.cs b/src/FrostFS.SDK.ModelsV2/Context.cs index e486464..b1038ec 100644 --- a/src/FrostFS.SDK.ModelsV2/Context.cs +++ b/src/FrostFS.SDK.ModelsV2/Context.cs @@ -8,7 +8,7 @@ namespace FrostFS.SDK.ClientV2; public class Context() { - private List interceptors; + private List? interceptors; public CancellationToken CancellationToken { get; set; } = default; public TimeSpan Timeout { get; set; } = default; @@ -16,11 +16,11 @@ public class Context() public DateTime? Deadline => Timeout.Ticks > 0 ? DateTime.UtcNow.Add(Timeout) : null; - public Action Callback { get; set; } + public Action? Callback { get; set; } public List Interceptors { - get { return interceptors ??= []; } - set { interceptors = value; } + get { return this.interceptors ??= []; } + set { this.interceptors = value; } } } diff --git a/src/FrostFS.SDK.ModelsV2/Object/Object.cs b/src/FrostFS.SDK.ModelsV2/Object/FrostFsObject.cs similarity index 82% rename from src/FrostFS.SDK.ModelsV2/Object/Object.cs rename to src/FrostFS.SDK.ModelsV2/Object/FrostFsObject.cs index e129601..4cbfaae 100644 --- a/src/FrostFS.SDK.ModelsV2/Object/Object.cs +++ b/src/FrostFS.SDK.ModelsV2/Object/FrostFsObject.cs @@ -4,16 +4,16 @@ using FrostFS.SDK.ModelsV2.Enums; namespace FrostFS.SDK.ModelsV2; -public class Object +public class FrostFsObject { - public Object(ObjectId objectId, ObjectHeader header, byte[] payload) + public FrostFsObject(ObjectId objectId, ObjectHeader header, byte[] payload) { ObjectId = objectId; Payload = payload; Header = header; } - public Object(ContainerId container, byte[] payload, ObjectType objectType = ObjectType.Regular) + public FrostFsObject(ContainerId container, byte[] payload, ObjectType objectType = ObjectType.Regular) { Payload = payload; Header = new ObjectHeader(containerId: container, type: objectType, attributes: []); @@ -41,7 +41,7 @@ public class Object } } -public class LargeObject(ContainerId container) : Object(container, []) +public class LargeObject(ContainerId container) : FrostFsObject(container, []) { private readonly SHA256 payloadHash = SHA256.Create(); @@ -64,7 +64,7 @@ public class LargeObject(ContainerId container) : Object(container, []) } } -public class LinkObject : Object +public class LinkObject : FrostFsObject { public LinkObject(ContainerId containerId, SplitId splitId, LargeObject largeObject) : base (containerId, []) { diff --git a/src/FrostFS.SDK.ModelsV2/Object/ObjectHeader.cs b/src/FrostFS.SDK.ModelsV2/Object/ObjectHeader.cs index 83bee87..715f903 100644 --- a/src/FrostFS.SDK.ModelsV2/Object/ObjectHeader.cs +++ b/src/FrostFS.SDK.ModelsV2/Object/ObjectHeader.cs @@ -1,35 +1,27 @@ using System.Collections.Generic; - using FrostFS.SDK.ModelsV2.Enums; namespace FrostFS.SDK.ModelsV2; -public class ObjectHeader +public class ObjectHeader( + ContainerId containerId, + ObjectType type = ObjectType.Regular, + params ObjectAttribute[] attributes + ) { public OwnerId? OwnerId { get; set; } - public List Attributes { get; set; } + public List Attributes { get; set; } = [.. attributes]; - public ContainerId ContainerId { get; set; } + public ContainerId ContainerId { get; set; } = containerId; public ulong PayloadLength { get; set; } public byte[]? PayloadCheckSum { get; set; } - public ObjectType ObjectType { get; set; } + public ObjectType ObjectType { get; set; } = type; public Version? Version { get; set; } public Split? Split { get; set; } - - public ObjectHeader( - ContainerId containerId, - ObjectType type = ObjectType.Regular, - params ObjectAttribute[] attributes - ) - { - Attributes = [.. attributes]; - ContainerId = containerId; - ObjectType = type; - } } diff --git a/src/FrostFS.SDK.ModelsV2/Object/ObjectId.cs b/src/FrostFS.SDK.ModelsV2/Object/ObjectId.cs index 2bcf3ab..74e6bfb 100644 --- a/src/FrostFS.SDK.ModelsV2/Object/ObjectId.cs +++ b/src/FrostFS.SDK.ModelsV2/Object/ObjectId.cs @@ -4,21 +4,15 @@ using FrostFS.SDK.Cryptography; namespace FrostFS.SDK.ModelsV2; -public class ObjectId +public class ObjectId(string id) { - public string Value { get; } - - public ObjectId(string id) - { - Value = id; - } + public string Value { get; } = id; public static ObjectId FromHash(byte[] hash) { if (hash.Length != Constants.Sha256HashLength) - { throw new FormatException("ObjectID must be a sha256 hash."); - } + return new ObjectId(Base58.Encode(hash)); } diff --git a/src/FrostFS.SDK.ProtosV2/Interfaces/IRequest.cs b/src/FrostFS.SDK.ProtosV2/Interfaces/IRequest.cs index 7efad42..42129c3 100644 --- a/src/FrostFS.SDK.ProtosV2/Interfaces/IRequest.cs +++ b/src/FrostFS.SDK.ProtosV2/Interfaces/IRequest.cs @@ -1,4 +1,6 @@ -namespace FrostFS.Session; +using FrostFS.Session; + +namespace FrostFS.SDK.ProtosV2.Interfaces; public interface IRequest : IVerifiableMessage { diff --git a/src/FrostFS.SDK.ProtosV2/container/Extension.Message.cs b/src/FrostFS.SDK.ProtosV2/container/Extension.Message.cs index d8bd601..5d48399 100644 --- a/src/FrostFS.SDK.ProtosV2/container/Extension.Message.cs +++ b/src/FrostFS.SDK.ProtosV2/container/Extension.Message.cs @@ -1,6 +1,7 @@ using Google.Protobuf; using FrostFS.Session; +using FrostFS.SDK.ProtosV2.Interfaces; namespace FrostFS.Container; diff --git a/src/FrostFS.SDK.ProtosV2/netmap/Extension.Message.cs b/src/FrostFS.SDK.ProtosV2/netmap/Extension.Message.cs index ba2d11e..2c1c8b6 100644 --- a/src/FrostFS.SDK.ProtosV2/netmap/Extension.Message.cs +++ b/src/FrostFS.SDK.ProtosV2/netmap/Extension.Message.cs @@ -1,4 +1,5 @@ -using FrostFS.Session; +using FrostFS.SDK.ProtosV2.Interfaces; +using FrostFS.Session; using Google.Protobuf; namespace FrostFS.Netmap; diff --git a/src/FrostFS.SDK.ProtosV2/object/Extension.Message.cs b/src/FrostFS.SDK.ProtosV2/object/Extension.Message.cs index de933fb..dbfdd79 100644 --- a/src/FrostFS.SDK.ProtosV2/object/Extension.Message.cs +++ b/src/FrostFS.SDK.ProtosV2/object/Extension.Message.cs @@ -1,4 +1,5 @@ using System.Diagnostics; +using FrostFS.SDK.ProtosV2.Interfaces; using FrostFS.Session; using Google.Protobuf; diff --git a/src/FrostFS.SDK.ProtosV2/session/Extension.Message.cs b/src/FrostFS.SDK.ProtosV2/session/Extension.Message.cs index 937af24..decf002 100644 --- a/src/FrostFS.SDK.ProtosV2/session/Extension.Message.cs +++ b/src/FrostFS.SDK.ProtosV2/session/Extension.Message.cs @@ -1,4 +1,5 @@ -using Google.Protobuf; +using FrostFS.SDK.ProtosV2.Interfaces; +using Google.Protobuf; namespace FrostFS.Session; diff --git a/src/FrostFS.SDK.Tests/ContainerTest.cs b/src/FrostFS.SDK.Tests/ContainerTest.cs index b915304..d41edb8 100644 --- a/src/FrostFS.SDK.Tests/ContainerTest.cs +++ b/src/FrostFS.SDK.Tests/ContainerTest.cs @@ -1,118 +1,112 @@ using FrostFS.SDK.ClientV2; -using FrostFS.SDK.Cryptography; using FrostFS.SDK.ModelsV2; -using FrostFS.SDK.ModelsV2.Enums; -using FrostFS.SDK.ModelsV2.Netmap; +using FrostFS.SDK.ClientV2.Mappers.GRPC.Netmap; using FrostFS.SDK.ClientV2.Mappers.GRPC; +using FrostFS.SDK.Cryptography; using Microsoft.Extensions.Options; +using FrostFS.SDK.ModelsV2.Netmap; +using FrostFS.SDK.ModelsV2.Enums; +using Google.Protobuf; +using FrostFS.SDK.ClientV2.Interfaces; namespace FrostFS.SDK.Tests; -public class ContainerTest -{ - private readonly string key = "KwHDAJ66o8FoLBjVbjP2sWBmgBMGjt7Vv4boA7xQrBoAYBE397Aq"; - [Fact] - public async void CreateContainerTest() +public abstract class ContainerTestsBase +{ + protected readonly string key = "KwHDAJ66o8FoLBjVbjP2sWBmgBMGjt7Vv4boA7xQrBoAYBE397Aq"; + + protected IOptions Settings { get; set; } + protected ContainerMocker Mocker { get; set; } + + protected ContainerTestsBase() { - var factory = new PutContainerMockFactory(this.key) + Settings = Options.Create(new ClientSettings + { + Key = key, + Host = "http://localhost:8080" + }); + + Mocker = new ContainerMocker(this.key) { PlacementPolicy = new PlacementPolicy(true, new Replica(1)), Version = new ModelsV2.Version(2, 13), ContainerGuid = Guid.NewGuid() }; + } - var settings = Options.Create(new ClientSettings - { - Key = key, - Host = "http://localhost:8080" - }); + protected IFrostFSClient GetClient() + { + return ClientV2.Client.GetTestInstance( + Settings, + null, + new NetworkMocker(this.key).GetMock().Object, + new SessionMocker(this.key).GetMock().Object, + Mocker.GetMock().Object, + new ObjectMocker(this.key).GetMock().Object); + } +} - var fsClient = Client.GetTestInstance( - settings, - null, - new NetmapMockFactory(this.key).GetMock().Object, - new SessionMockFactory(this.key).GetMock().Object, - factory.GetMock().Object, - new ObjectMockFactory(this.key).GetMock().Object); - - var result = await fsClient.CreateContainerAsync(new ModelsV2.Container(BasicAcl.PublicRW, factory.PlacementPolicy)); +public class ContainerTest : ContainerTestsBase +{ + [Fact] + public async void CreateContainerTest() + { + var result = await GetClient().CreateContainerAsync(new ModelsV2.Container(BasicAcl.PublicRW, Mocker.PlacementPolicy)); Assert.NotNull(result); Assert.NotNull(result.Value); - Assert.True(Base58.Encode(factory.ContainerGuid.ToBytes()) == result.Value); + Assert.True(Base58.Encode(Mocker.ContainerGuid.ToBytes()) == result.Value); } [Fact] public async void GetContainerTest() { - var factory = new GetContainerMockFactory(this.key) - { - PlacementPolicy = new PlacementPolicy(true, new Replica(1)), - Version = new ModelsV2.Version(2, 13), - Acl = BasicAcl.PublicRO, - ContainerGuid = Guid.NewGuid(), - }; + var cid = new ContainerId(Base58.Encode(Mocker.ContainerGuid.ToBytes())); - var settings = Options.Create(new ClientSettings - { - Key = key, - Host = "http://localhost:8080" - }); + Mocker.Acl = BasicAcl.PublicRO; - var fsClient = Client.GetTestInstance( - settings, - null, - new NetmapMockFactory(this.key).GetMock().Object, - new SessionMockFactory(this.key).GetMock().Object, - factory.GetMock().Object, - new ObjectMockFactory(this.key).GetMock().Object); - - var cid = new ContainerId(Base58.Encode(factory.ContainerGuid.ToBytes())); - - var context = new Context(); - - var result = await fsClient.GetContainerAsync(cid, context); + var result = await GetClient().GetContainerAsync(cid); Assert.NotNull(result); - Assert.Equal(factory.Acl, result.BasicAcl); - Assert.Equal(factory.ContainerGuid, result.Nonce); - Assert.Equal(0, factory.PlacementPolicy.CompareTo(result.PlacementPolicy)); - Assert.Equal(factory.Version.ToString(), result.Version!.ToString()); + Assert.Equal(Mocker.Acl, result.BasicAcl); + Assert.Equal(Mocker.ContainerGuid, result.Nonce); + Assert.Equal(0, Mocker.PlacementPolicy.CompareTo(result.PlacementPolicy)); + Assert.Equal(Mocker.Version.ToString(), result.Version!.ToString()); + } + + [Fact] + public async void GetContainerListTest() + { + Mocker.ContainerIds.Add([0xaa]); + Mocker.ContainerIds.Add([0xbb]); + Mocker.ContainerIds.Add([0xcc]); + + var result = GetClient().ListContainersAsync(); + + Assert.NotNull(result); + + int i = 0; + await foreach (var cid in result) + { + var val = Base58.Encode(ByteString.CopyFrom(Mocker.ContainerIds[i++]).ToByteArray()); + Assert.Equal(val, cid.Value); + } + + Assert.Equal(3, i); } [Fact] public async void DeleteContainerAsyncTest() { - var factory = new DeleteContainerMockFactory(this.key) - { - Version = new ModelsV2.Version(2, 13), - Acl = BasicAcl.PublicRW, - ContainerGuid = Guid.NewGuid(), - }; + var cid = new ContainerId(Base58.Encode(Mocker.ContainerGuid.ToBytes())); - var settings = Options.Create(new ClientSettings - { - Key = key, - Host = "http://localhost:8080" - }); + await GetClient().DeleteContainerAsync(cid); - var fsClient = Client.GetTestInstance( - settings, - null, - new NetmapMockFactory(this.key).GetMock().Object, - new SessionMockFactory(this.key).GetMock().Object, - factory.GetMock().Object, - new ObjectMockFactory(this.key).GetMock().Object); + Assert.Single(Mocker.Requests); - var cid = new ContainerId(Base58.Encode(factory.ContainerGuid.ToBytes())); - - await fsClient.DeleteContainerAsync(cid); - - Assert.Single(factory.Requests); - - var request = factory.Requests.First(); + var request = Mocker.Requests.First(); Assert.Equal(cid.ToGrpcMessage(), request.Request.Body.ContainerId); } diff --git a/src/FrostFS.SDK.Tests/FrostFS.SDK.Tests.csproj b/src/FrostFS.SDK.Tests/FrostFS.SDK.Tests.csproj index 931a072..2b74f0f 100644 --- a/src/FrostFS.SDK.Tests/FrostFS.SDK.Tests.csproj +++ b/src/FrostFS.SDK.Tests/FrostFS.SDK.Tests.csproj @@ -9,6 +9,10 @@ true + + + + @@ -25,4 +29,10 @@ + + + PreserveNewest + + + diff --git a/src/FrostFS.SDK.Tests/Mocks/AsyncStreamReaderMock.cs b/src/FrostFS.SDK.Tests/Mocks/AsyncStreamReaderMock.cs new file mode 100644 index 0000000..45b17bc --- /dev/null +++ b/src/FrostFS.SDK.Tests/Mocks/AsyncStreamReaderMock.cs @@ -0,0 +1,60 @@ +using FrostFS.Object; +using FrostFS.Session; +using Google.Protobuf; +using Grpc.Core; +using FrostFS.SDK.ClientV2; +using FrostFS.SDK.ModelsV2; +using FrostFS.SDK.ClientV2.Mappers.GRPC.Netmap; +using FrostFS.SDK.ClientV2.Mappers.GRPC; +using FrostFS.SDK.Cryptography; +using System.Security.Cryptography; + +namespace FrostFS.SDK.Tests; + +public class AsyncStreamReaderMock(string key, ObjectHeader objectHeader) : ServiceBase(key), IAsyncStreamReader +{ + public GetResponse Current + { + get + { + var header = new Header + { + ContainerId = objectHeader.ContainerId.ToGrpcMessage(), + PayloadLength = objectHeader.PayloadLength, + Version = objectHeader.Version!.ToGrpcMessage(), + OwnerId = objectHeader.OwnerId!.ToGrpcMessage() + }; + + foreach (var attr in objectHeader.Attributes) + header.Attributes.Add(attr.ToGrpcMessage()); + + var response = new GetResponse + { + Body = new GetResponse.Types.Body + { + Init = new GetResponse.Types.Body.Types.Init + { + Header = header, + ObjectId = new Refs.ObjectID { Value = ByteString.CopyFrom(SHA256.HashData(Array.Empty())) }, + Signature = new Refs.Signature + { + Key = ByteString.CopyFrom(Key.PublicKey()), + Sign = ByteString.CopyFrom(Key.SignData(header.ToByteArray())), + } + } + }, + MetaHeader = new ResponseMetaHeader() + }; + + response.VerifyHeader = GetResponseVerificationHeader(response); + + return response; + } + } + + public Task MoveNext(CancellationToken cancellationToken) + { + return Task.FromResult(true); + } +} + diff --git a/src/FrostFS.SDK.Tests/Mocks/ClientStreamWriter.cs b/src/FrostFS.SDK.Tests/Mocks/ClientStreamWriter.cs new file mode 100644 index 0000000..fb1fe2d --- /dev/null +++ b/src/FrostFS.SDK.Tests/Mocks/ClientStreamWriter.cs @@ -0,0 +1,29 @@ +using Grpc.Core; +using FrostFS.SDK.ProtosV2.Interfaces; + +namespace FrostFS.SDK.Tests; + +public class ClientStreamWriter : IClientStreamWriter +{ + public List Messages { get; set; } = []; + public bool CompletedTask { get; private set; } + + public WriteOptions? WriteOptions + { + get => throw new NotImplementedException(); + set => throw new NotImplementedException(); + } + + public Task CompleteAsync() + { + CompletedTask = true; + return Task.CompletedTask; + } + + public Task WriteAsync(IRequest message) + { + Messages.Add(message); + return Task.CompletedTask; + } +} + diff --git a/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/ContainerServiceBase.cs b/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/ContainerServiceBase.cs index 0ea346d..530294a 100644 --- a/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/ContainerServiceBase.cs +++ b/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/ContainerServiceBase.cs @@ -17,6 +17,7 @@ namespace FrostFS.SDK.Tests; public abstract class ServiceBase(string key) { + public string StringKey { get; private set; } = key; public ECDsa Key { get; private set; } = key.LoadWif(); public ModelsV2.Version Version { get; set; } = DefaultVersion; public BasicAcl Acl { get; set; } = DefaultAcl; diff --git a/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/GetContainerMock copy.cs b/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/ContainerStub.cs similarity index 99% rename from src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/GetContainerMock copy.cs rename to src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/ContainerStub.cs index 3a17e88..c0b69a3 100644 --- a/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/GetContainerMock copy.cs +++ b/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/ContainerStub.cs @@ -1,7 +1,6 @@ using FrostFS.Container; using Moq; - namespace FrostFS.SDK.Tests; public class ContainerStub(string key) : ContainerServiceBase(key) diff --git a/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/DeleteContainerMock.cs b/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/DeleteContainerMock.cs deleted file mode 100644 index ba03978..0000000 --- a/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/DeleteContainerMock.cs +++ /dev/null @@ -1,54 +0,0 @@ -using FrostFS.Container; -using FrostFS.Session; -using Grpc.Core; -using Moq; - -namespace FrostFS.SDK.Tests; - -public class DeleteContainerMockFactory(string key) : ContainerServiceBase(key) -{ - public override Mock GetMock() - { - var mock = new Mock(); - - var v = mock.Setup(x => x.DeleteAsync( - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny())) - .Returns((DeleteRequest r, Metadata m, DateTime? dt, CancellationToken ct) => - { - Requests.Add(new RequestData(r, m, dt, ct)); - - var response = new DeleteResponse - { - Body = new DeleteResponse.Types.Body(), - MetaHeader = new ResponseMetaHeader() - }; - - var metadata = new Metadata(); - - response.VerifyHeader = GetResponseVerificationHeader(response); - - return new AsyncUnaryCall( - Task.FromResult(response), - Task.FromResult(metadata), - () => new Grpc.Core.Status(StatusCode.OK, string.Empty), - () => metadata, - () => { }); - }); - - return mock; - } - - public List> Requests = []; -} - -public class RequestData(T request, Metadata m, DateTime? dt, CancellationToken ct) -{ - public T Request => request; - public Metadata Metadata => m; - public DateTime? deadline => dt; - public CancellationToken CancellationToken => ct; -} - \ No newline at end of file diff --git a/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/GetContainerMock.cs b/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/GetContainerMock.cs index 8b2a496..9a72f09 100644 --- a/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/GetContainerMock.cs +++ b/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/GetContainerMock.cs @@ -8,10 +8,13 @@ using FrostFS.SDK.ClientV2; using FrostFS.SDK.ModelsV2.Netmap; using FrostFS.SDK.ClientV2.Mappers.GRPC.Netmap; using FrostFS.SDK.ClientV2.Mappers.GRPC; +using FrostFS.Session; +using FrostFS.Refs; +using System.Collections.Generic; namespace FrostFS.SDK.Tests; -public class GetContainerMockFactory(string key) : ContainerServiceBase(key) +public class ContainerMocker(string key) : ContainerServiceBase(key) { public override Mock GetMock() { @@ -19,7 +22,46 @@ public class GetContainerMockFactory(string key) : ContainerServiceBase(key) var grpcVersion = Version.ToGrpcMessage(); - var response = new GetResponse + PutResponse putResponse = new() + { + Body = new PutResponse.Types.Body + { + ContainerId = new ContainerID + { + Value = ByteString.CopyFrom(ContainerGuid.ToBytes()) + } + }, + MetaHeader = new ResponseMetaHeader + { + Version = Version is null ? DefaultVersion.ToGrpcMessage() : Version.ToGrpcMessage(), + Epoch = 100, + Ttl = 1 + } + }; + + putResponse.VerifyHeader = GetResponseVerificationHeader(putResponse); + + var metadata = new Metadata(); + var putContainerResponse = new AsyncUnaryCall( + Task.FromResult(putResponse), + Task.FromResult(metadata), + () => new Grpc.Core.Status(StatusCode.OK, string.Empty), + () => metadata, + () => { }); + + mock.Setup(x => x.PutAsync( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())) + .Returns((PutRequest r, Metadata m, DateTime? dt, CancellationToken ct) => + { + Verifier.CheckRequest(r); + + return putContainerResponse; + }); + + var getResponse = new GetResponse { Body = new GetResponse.Types.Body { @@ -34,7 +76,7 @@ public class GetContainerMockFactory(string key) : ContainerServiceBase(key) MetaHeader = ResponseMetaHeader }; - response.VerifyHeader = GetResponseVerificationHeader(response); + getResponse.VerifyHeader = GetResponseVerificationHeader(getResponse); mock.Setup(x => x.GetAsync( It.IsAny(), @@ -46,13 +88,74 @@ public class GetContainerMockFactory(string key) : ContainerServiceBase(key) Verifier.CheckRequest(r); return new AsyncUnaryCall( - Task.FromResult(response), + Task.FromResult(getResponse), Task.FromResult(ResponseMetaData), () => new Grpc.Core.Status(StatusCode.OK, string.Empty), () => ResponseMetaData, () => { }); }); + var listResponse = new ListResponse + { + Body = new ListResponse.Types.Body(), + MetaHeader = ResponseMetaHeader + }; + + foreach (var item in ContainerIds) + { + listResponse.Body.ContainerIds.Add(new ContainerID { Value = ByteString.CopyFrom(item) }); + } + + listResponse.VerifyHeader = GetResponseVerificationHeader(listResponse); + + mock.Setup(x => x.ListAsync( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())) + .Returns((ListRequest r, Metadata m, DateTime? dt, CancellationToken ct) => + { + Verifier.CheckRequest(r); + + return new AsyncUnaryCall( + Task.FromResult(listResponse), + Task.FromResult(ResponseMetaData), + () => new Grpc.Core.Status(StatusCode.OK, string.Empty), + () => ResponseMetaData, + () => { }); + }); + + var v = mock.Setup(x => x.DeleteAsync( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())) + .Returns((DeleteRequest r, Metadata m, DateTime? dt, CancellationToken ct) => + { + Requests.Add(new RequestData(r, m, dt, ct)); + + var response = new DeleteResponse + { + Body = new DeleteResponse.Types.Body(), + MetaHeader = new ResponseMetaHeader() + }; + + var metadata = new Metadata(); + + response.VerifyHeader = GetResponseVerificationHeader(response); + + return new AsyncUnaryCall( + Task.FromResult(response), + Task.FromResult(metadata), + () => new Grpc.Core.Status(StatusCode.OK, string.Empty), + () => metadata, + () => { }); + }); + return mock; } + + public List ContainerIds { get; set; } = []; + + public List> Requests { get; set; } = []; } diff --git a/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/PutContainerMock.cs b/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/PutContainerMock.cs deleted file mode 100644 index c6abeab..0000000 --- a/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/PutContainerMock.cs +++ /dev/null @@ -1,88 +0,0 @@ -using FrostFS.Container; -using FrostFS.Session; -using Google.Protobuf; -using Grpc.Core; -using Moq; -using FrostFS.SDK.ClientV2; -using FrostFS.SDK.ClientV2.Mappers.GRPC.Netmap; -using FrostFS.SDK.ClientV2.Mappers.GRPC; -using FrostFS.SDK.Cryptography; -using FrostFS.Refs; - -namespace FrostFS.SDK.Tests; - -public class PutContainerMockFactory(string key) : ContainerServiceBase(key) -{ - public override Mock GetMock() - { - var mock = new Mock(); - - PutResponse response = new() - { - Body = new PutResponse.Types.Body - { - ContainerId = new ContainerID - { - Value = ByteString.CopyFrom(ContainerGuid.ToBytes()) - } - }, - MetaHeader = new ResponseMetaHeader - { - Version = Version is null ? DefaultVersion.ToGrpcMessage() : Version.ToGrpcMessage(), - Epoch = 100, - Ttl = 1 - } - }; - - response.VerifyHeader = PutResponseVerificationHeader(response); - - var metadata = new Metadata(); - var putContainerResponse = new AsyncUnaryCall( - Task.FromResult(response), - Task.FromResult(metadata), - () => new Grpc.Core.Status(StatusCode.OK, string.Empty), - () => metadata, - () => { }); - - mock.Setup(x => x.PutAsync( - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny())) - .Returns((PutRequest r, Metadata m, DateTime? dt, CancellationToken ct) => - { - Verifier.CheckRequest(r); - - return putContainerResponse; - }); - - return mock; - } - - private ResponseVerificationHeader PutResponseVerificationHeader(PutResponse response) - { - var verifyHeader = new ResponseVerificationHeader - { - MetaSignature = new Signature - { - Key = ByteString.CopyFrom(Key.PublicKey()), - Scheme = SignatureScheme.EcdsaRfc6979Sha256, - Sign = ByteString.CopyFrom(Key.SignData(response.MetaHeader.ToByteArray())) - }, - BodySignature = new Signature - { - Key = ByteString.CopyFrom(Key.PublicKey()), - Scheme = SignatureScheme.EcdsaRfc6979Sha256, - Sign = ByteString.CopyFrom(Key.SignData(response.GetBody().ToByteArray())) - }, - OriginSignature = new Signature - { - Key = ByteString.CopyFrom(Key.PublicKey()), - Scheme = SignatureScheme.EcdsaRfc6979Sha256, - Sign = ByteString.CopyFrom(Key.SignData([])) - } - }; - - return verifyHeader; - } -} \ No newline at end of file diff --git a/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/RequestData.cs b/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/RequestData.cs new file mode 100644 index 0000000..8af698b --- /dev/null +++ b/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/RequestData.cs @@ -0,0 +1,12 @@ +using Grpc.Core; + +namespace FrostFS.SDK.Tests; + +public class RequestData(T request, Metadata m, DateTime? dt, CancellationToken ct) +{ + public T Request => request; + public Metadata Metadata => m; + public DateTime? Deadline => dt; + public CancellationToken CancellationToken => ct; +} + \ No newline at end of file diff --git a/src/FrostFS.SDK.Tests/Mocks/NetmapMock.cs b/src/FrostFS.SDK.Tests/Mocks/NetmapMock.cs deleted file mode 100644 index 07264ff..0000000 --- a/src/FrostFS.SDK.Tests/Mocks/NetmapMock.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Moq; -using FrostFS.Netmap; - -namespace FrostFS.SDK.Tests; - -public class NetmapMockFactory(string key) : ServiceBase(key) -{ - public Mock GetMock() - { - var mock = new Mock(); - - return mock; - } -} diff --git a/src/FrostFS.SDK.Tests/Mocks/NetworkMocker.cs b/src/FrostFS.SDK.Tests/Mocks/NetworkMocker.cs new file mode 100644 index 0000000..6cef45f --- /dev/null +++ b/src/FrostFS.SDK.Tests/Mocks/NetworkMocker.cs @@ -0,0 +1,78 @@ +using Moq; +using FrostFS.Netmap; +using Grpc.Core; +using FrostFS.SDK.ClientV2; +using Google.Protobuf; +using NuGet.Frameworks; + +namespace FrostFS.SDK.Tests; + +public class NetworkMocker(string key) : ServiceBase(key) +{ + private static readonly string[] parameterKeys = [ + "ContainerFee", + "EpochDuration", + "IRCandidateFee", + "MaxECDataCount", + "MaxECParityCount", + "MaxObjectSize", + "WithdrawalFee", + "HomomorphicHashingDisabled", + "MaintenanceModeAllowed" ]; + + public Dictionary? Parameters { get; set; } + + public Mock GetMock() + { + var mock = new Mock(); + + var networkInfoResponse = new NetworkInfoResponse(); + + var networkConfig = new NetworkConfig(); + + foreach (var key in parameterKeys) + { + networkConfig.Parameters.Add(new NetworkConfig.Types.Parameter + { + Key = ByteString.CopyFromUtf8(key), + Value = (Parameters != null && Parameters.TryGetValue(key, out byte[]? value)) ? ByteString.CopyFrom(value) : ByteString.CopyFrom(0) + }); + } + + var response = new NetworkInfoResponse + { + Body = new NetworkInfoResponse.Types.Body + { + NetworkInfo = new NetworkInfo + { + CurrentEpoch = 99, + MagicNumber = 13, + MsPerBlock = 999, + NetworkConfig = networkConfig + } + }, + MetaHeader = ResponseMetaHeader + }; + + response.VerifyHeader = GetResponseVerificationHeader(response); + + mock.Setup(x => x.NetworkInfoAsync( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())) + .Returns((NetworkInfoRequest r, Metadata m, DateTime? dt, CancellationToken ct) => + { + Verifier.CheckRequest(r); + + return new AsyncUnaryCall( + Task.FromResult(response), + Task.FromResult(ResponseMetaData), + () => new Grpc.Core.Status(StatusCode.OK, string.Empty), + () => ResponseMetaData, + () => { }); + }); + + return mock; + } +} diff --git a/src/FrostFS.SDK.Tests/Mocks/ObjectMock.cs b/src/FrostFS.SDK.Tests/Mocks/ObjectMock.cs index 1d6c9e1..db4df1e 100644 --- a/src/FrostFS.SDK.Tests/Mocks/ObjectMock.cs +++ b/src/FrostFS.SDK.Tests/Mocks/ObjectMock.cs @@ -1,101 +1,208 @@ -using Moq; -using FrostFS.Object; -using Grpc.Core; -using FrostFS.SDK.ClientV2; -using FrostFS.Session; using Google.Protobuf; +using Grpc.Core; +using Moq; +using FrostFS.SDK.ClientV2; +using FrostFS.SDK.ModelsV2; +using FrostFS.Object; +using FrostFS.SDK.ClientV2.Mappers.GRPC; using System.Security.Cryptography; using FrostFS.SDK.Cryptography; -using FrostFS.SDK.ClientV2.Mappers.GRPC; -using FrostFS.SDK.ModelsV2; namespace FrostFS.SDK.Tests; -public class ObjectMockFactory(string key) : ObjectServiceBase(key) +public class ObjectMocker(string key) : ObjectServiceBase(key) { public override Mock GetMock() { var mock = new Mock(); - GetResponse response = new() + if (ObjectHeader != null) { - Body = new GetResponse.Types.Body - { - }, - MetaHeader = ResponseMetaHeader - }; - - response.VerifyHeader = GetResponseVerificationHeader(response); - - mock.Setup(x => x.Get( - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny())) - .Returns((GetRequest r, Metadata m, DateTime? dt, CancellationToken ct) => + mock.Setup(x => x.Get( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())) + .Returns((GetRequest r, Metadata m, DateTime? dt, CancellationToken ct) => { Verifier.CheckRequest(r); - return new AsyncServerStreamingCall( - new AsyncStreamReaderMock(key, ObjectHeader), + return new AsyncServerStreamingCall( + new AsyncStreamReaderMock(StringKey, ObjectHeader), Task.FromResult(ResponseMetaData), () => new Grpc.Core.Status(StatusCode.OK, string.Empty), () => ResponseMetaData, () => { }); }); + HeadResponse ??= new Header + { + CreationEpoch = 99, + ContainerId = ObjectHeader.ContainerId.ToGrpcMessage(), + ObjectType = ObjectType.Regular, + OwnerId = ObjectHeader.OwnerId!.ToGrpcMessage(), + PayloadLength = 1, + PayloadHash = new Refs.Checksum { Type = Refs.ChecksumType.Sha256, Sum = ByteString.CopyFrom(SHA256.HashData([0xff])) }, + Version = ObjectHeader.Version!.ToGrpcMessage() + }; + + HeadResponse headResponse = new() + { + Body = new HeadResponse.Types.Body + { + Header = new HeaderWithSignature + { + Header = HeadResponse + } + }, + MetaHeader = ResponseMetaHeader + }; + + headResponse.Body.Header.Header.Attributes.Add(new Header.Types.Attribute { Key = "k", Value = "v" }); + + headResponse.Body.Header.Signature = new Refs.Signature + { + Key = ByteString.CopyFrom(Key.PublicKey()), + Sign = ByteString.CopyFrom(Key.SignData(headResponse.Body.Header.ToByteArray())), + }; + + headResponse.VerifyHeader = GetResponseVerificationHeader(headResponse); + + mock.Setup(x => x.HeadAsync( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())) + .Returns((HeadRequest r, Metadata m, DateTime? dt, CancellationToken ct) => + { + Verifier.CheckRequest(r); + + HeadRequests.Add(r); + + return new AsyncUnaryCall( + Task.FromResult(headResponse), + Task.FromResult(ResponseMetaData), + () => new Grpc.Core.Status(StatusCode.OK, string.Empty), + () => ResponseMetaData, + () => { }); + }); + + } + + if (ResultObjectId != null) + { + PutResponse putResponse = new() + { + Body = new PutResponse.Types.Body + { + ObjectId = new Refs.ObjectID + { + Value = ByteString.CopyFrom(ResultObjectId) + } + }, + MetaHeader = ResponseMetaHeader, + }; + + putResponse.VerifyHeader = GetResponseVerificationHeader(putResponse); + + mock.Setup(x => x.Put( + It.IsAny(), + It.IsAny(), + It.IsAny())) + .Returns((Metadata m, DateTime? dt, CancellationToken ct) => + { + return new AsyncClientStreamingCall( + ClientStreamWriter!, + Task.FromResult(putResponse), + Task.FromResult(ResponseMetaData), + () => new Grpc.Core.Status(StatusCode.OK, string.Empty), + () => ResponseMetaData, + () => { }); + }); + } + + PutSingleResponse putSingleResponse = new() + { + Body = new PutSingleResponse.Types.Body(), + MetaHeader = ResponseMetaHeader, + }; + + putSingleResponse.VerifyHeader = GetResponseVerificationHeader(putSingleResponse); + + mock.Setup(x => x.PutSingleAsync( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())) + .Returns((PutSingleRequest r, Metadata m, DateTime? dt, CancellationToken ct) => + { + Verifier.CheckRequest(r); + + PutSingleRequests.Add(r); + + return new AsyncUnaryCall( + Task.FromResult(putSingleResponse), + Task.FromResult(ResponseMetaData), + () => new Grpc.Core.Status(StatusCode.OK, string.Empty), + () => ResponseMetaData, + () => { }); + }); + + if (ObjectId != null) + { + DeleteResponse deleteResponse = new() + { + Body = new DeleteResponse.Types.Body + { + Tombstone = new Refs.Address + { + ContainerId = ObjectHeader!.ContainerId.ToGrpcMessage(), + ObjectId = ObjectId.ToGrpcMessage() + } + }, + MetaHeader = ResponseMetaHeader + }; + + deleteResponse.VerifyHeader = GetResponseVerificationHeader(deleteResponse); + + mock.Setup(x => x.DeleteAsync( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())) + .Returns((DeleteRequest r, Metadata m, DateTime? dt, CancellationToken ct) => + { + Verifier.CheckRequest(r); + + DeleteRequests.Add(r); + + return new AsyncUnaryCall( + Task.FromResult(deleteResponse), + Task.FromResult(ResponseMetaData), + () => new Grpc.Core.Status(StatusCode.OK, string.Empty), + () => ResponseMetaData, + () => { }); + }); + } + + return mock; } - public ObjectHeader ObjectHeader { get; set; } -} - -public class AsyncStreamReaderMock(string key, ObjectHeader objectHeader) : ServiceBase(key), IAsyncStreamReader -{ - public GetResponse Current - { - get - { - var ecdsaKey = key.LoadWif(); - - var header = new Header - { - ContainerId = objectHeader.ContainerId.ToGrpcMessage(), - PayloadLength = objectHeader.PayloadLength, - Version = objectHeader.Version!.ToGrpcMessage(), - OwnerId = objectHeader.OwnerId!.ToGrpcMessage() - }; - - foreach (var attr in objectHeader.Attributes) - header.Attributes.Add(attr.ToGrpcMessage()); - - var response = new GetResponse - { - Body = new GetResponse.Types.Body - { - Init = new GetResponse.Types.Body.Types.Init - { - Header = header, - ObjectId = new Refs.ObjectID { Value = ByteString.CopyFrom(SHA256.HashData(Array.Empty())) }, - Signature = new Refs.Signature - { - Key = ByteString.CopyFrom(ecdsaKey.PublicKey()), - Sign = ByteString.CopyFrom(ecdsaKey.SignData(header.ToByteArray())), - } - } - }, - MetaHeader = new ResponseMetaHeader() - }; - - response.VerifyHeader = GetResponseVerificationHeader(response); - - return response; - } - } - - public Task MoveNext(CancellationToken cancellationToken) - { - return Task.FromResult(true); - } + public ObjectId? ObjectId { get; set; } + + public ObjectHeader? ObjectHeader { get; set; } + + public Header? HeadResponse { get; set; } + + public byte[]? ResultObjectId { get; set; } + + public ClientStreamWriter? ClientStreamWriter { get; private set; } = new (); + + public List PutSingleRequests { get; private set; } = []; + + public List DeleteRequests { get; private set; } = []; + + public List HeadRequests { get; private set; } = []; } diff --git a/src/FrostFS.SDK.Tests/Mocks/SessionMock.cs b/src/FrostFS.SDK.Tests/Mocks/SessionMock.cs index 83beaf8..094090d 100644 --- a/src/FrostFS.SDK.Tests/Mocks/SessionMock.cs +++ b/src/FrostFS.SDK.Tests/Mocks/SessionMock.cs @@ -6,7 +6,7 @@ using Google.Protobuf; namespace FrostFS.SDK.Tests; -public class SessionMockFactory(string key) : ServiceBase(key) +public class SessionMocker(string key) : ServiceBase(key) { public byte[]? SessionId { get; set; } diff --git a/src/FrostFS.SDK.Tests/ObjectTest.cs b/src/FrostFS.SDK.Tests/ObjectTest.cs index 53c2a9a..399dc07 100644 --- a/src/FrostFS.SDK.Tests/ObjectTest.cs +++ b/src/FrostFS.SDK.Tests/ObjectTest.cs @@ -1,66 +1,237 @@ +using FrostFS.Refs; using FrostFS.SDK.ClientV2; +using FrostFS.SDK.ClientV2.Interfaces; using FrostFS.SDK.ClientV2.Mappers.GRPC; using FrostFS.SDK.Cryptography; using FrostFS.SDK.ModelsV2; +using FrostFS.SDK.ModelsV2.Enums; using FrostFS.SDK.ModelsV2.Netmap; +using Google.Protobuf; using Microsoft.Extensions.Options; - using System.Security.Cryptography; +using System.Text; namespace FrostFS.SDK.Tests; -public class ObjectTest +public abstract class ObjectTestsBase { - private readonly string key = "KwHDAJ66o8FoLBjVbjP2sWBmgBMGjt7Vv4boA7xQrBoAYBE397Aq"; + protected static readonly string key = "KwHDAJ66o8FoLBjVbjP2sWBmgBMGjt7Vv4boA7xQrBoAYBE397Aq"; - [Fact] - public async void GetObjectTest() + protected IOptions Settings { get; set; } + protected ContainerId ContainerId { get; set; } + + protected NetworkMocker NetworkMocker { get; set; } = new NetworkMocker(key); + protected SessionMocker SessionMocker { get; set; } = new SessionMocker(key); + protected ContainerMocker ContainerMocker { get; set; } = new ContainerMocker(key); + protected ObjectMocker Mocker { get; set; } + + protected ObjectTestsBase() { var ecdsaKey = key.LoadWif(); - ContainerId cntId = new("xyz"); - - ObjectHeader header = new(cntId, ModelsV2.Enums.ObjectType.Regular, [new ObjectAttribute("k", "v")]) - { - PayloadLength = 1, - Version = new ModelsV2.Version(2, 13), - OwnerId = OwnerId.FromKey(ecdsaKey) - }; - - var objectMockFactory = new ObjectMockFactory(this.key) - { - PlacementPolicy = new PlacementPolicy(true, new Replica(1)), - Version = new ModelsV2.Version(2, 13), - ContainerGuid = Guid.NewGuid(), - ObjectHeader = header - }; - var settings = Options.Create(new ClientSettings + Settings = Options.Create(new ClientSettings { Key = key, Host = "http://localhost:8080" }); - var fsClient = Client.GetTestInstance( - settings, + Mocker = new ObjectMocker(key) + { + PlacementPolicy = new PlacementPolicy(true, new Replica(1)), + Version = new ModelsV2.Version(2, 13), + ContainerGuid = Guid.NewGuid() + }; + + ContainerId = new ContainerId(Base58.Encode(Mocker.ContainerGuid.ToBytes())); + + Mocker.ObjectHeader = new(ContainerId, ModelsV2.Enums.ObjectType.Regular, [new ObjectAttribute("k", "v")]) + { + PayloadLength = 1, + Version = new ModelsV2.Version(2, 13), + OwnerId = OwnerId.FromKey(ecdsaKey) + }; + } + + protected IFrostFSClient GetClient() + { + return Client.GetTestInstance( + Settings, null, - new NetmapMockFactory(this.key).GetMock().Object, - new SessionMockFactory(this.key).GetMock().Object, - new ContainerStub(this.key).GetMock().Object, - objectMockFactory.GetMock().Object); + NetworkMocker.GetMock().Object, + SessionMocker.GetMock().Object, + ContainerMocker.GetMock().Object, + Mocker.GetMock().Object); + } +} - var objectId = fsClient.CalculateObjectId(header); +public class ObjectTest : ObjectTestsBase +{ + [Fact] + public async void GetObjectTest() + { + var client = GetClient(); + var objectId = client.CalculateObjectId(Mocker.ObjectHeader!); - var containerId = new ContainerId(Base58.Encode(objectMockFactory.ContainerGuid.ToBytes())); + var context = new Context + { + Timeout = TimeSpan.FromSeconds(2) + }; - var result = await fsClient.GetObjectAsync(containerId, objectId); + var result = await client.GetObjectAsync(ContainerId, objectId, context); Assert.NotNull(result); - Assert.Equal(header.ContainerId.Value, result.Header.ContainerId.Value); - Assert.Equal(header.OwnerId.ToGrpcMessage().ToString(), result.Header.OwnerId!.Value); - Assert.Equal(header.PayloadLength, result.Header.PayloadLength); + Assert.Equal(Mocker.ObjectHeader!.ContainerId.Value, result.Header.ContainerId.Value); + Assert.Equal(Mocker.ObjectHeader!.OwnerId!.ToGrpcMessage().ToString(), result.Header.OwnerId!.Value); + Assert.Equal(Mocker.ObjectHeader.PayloadLength, result.Header.PayloadLength); Assert.Single(result.Header.Attributes); - Assert.Equal(header.Attributes[0].Key, result.Header.Attributes[0].Key); - Assert.Equal(header.Attributes[0].Value,result.Header.Attributes[0].Value); + Assert.Equal(Mocker.ObjectHeader.Attributes[0].Key, result.Header.Attributes[0].Key); + Assert.Equal(Mocker.ObjectHeader.Attributes[0].Value,result.Header.Attributes[0].Value); + } + + [Fact] + public async void PutObjectTest() + { + Mocker.ResultObjectId = SHA256.HashData([]); + + Random rnd = new(); + var bytes = new byte[1024]; + rnd.NextBytes(bytes); + + var param = new PutObjectParameters + { + Header = Mocker.ObjectHeader, + Payload = new MemoryStream(bytes), + ClientCut = false + }; + + var result = await GetClient().PutObjectAsync(param); + + var sentMessages = Mocker.ClientStreamWriter!.Messages; + + var body1 = sentMessages.ElementAt(0).GetBody() as Object.PutRequest.Types.Body; + var body2 = sentMessages.ElementAt(1).GetBody() as Object.PutRequest.Types.Body; + + Assert.NotNull(result); + Assert.Equal(Mocker.ResultObjectId, result.ToHash()); + + Assert.True(Mocker.ClientStreamWriter.CompletedTask); + + Assert.Equal(0, body1!.Chunk.Length); + Assert.Equal(Object.PutRequest.Types.Body.ObjectPartOneofCase.Init, body1!.ObjectPartCase); + + Assert.Equal(1024, body2!.Chunk.Length); + Assert.Equal(Object.PutRequest.Types.Body.ObjectPartOneofCase.Chunk, body2!.ObjectPartCase); + } + + [Fact] + public async void ClientCutTest() + { + NetworkMocker.Parameters = new Dictionary() { { "MaxObjectSize", [0x0, 0xa] } }; + + var blockSize = 2560; + byte[] bytes = File.ReadAllBytes(@".\..\..\..\TestData\cat.jpg"); + var fileLength = bytes.Length; + + var param = new PutObjectParameters + { + Header = Mocker.ObjectHeader, + Payload = new MemoryStream(bytes), + ClientCut = true + }; + + var result = await GetClient().PutObjectAsync(param); + + var sentMessages = Mocker.PutSingleRequests.ToArray(); + + Assert.Equal(4, sentMessages.Length); + + var object_0 = sentMessages[0].Body.Object; + var object_1 = sentMessages[1].Body.Object; + var object_2 = sentMessages[2].Body.Object; + var object_3 = sentMessages[3].Body.Object; + + Assert.NotNull(object_0.Header.Split.SplitId); + Assert.Null(object_0.Header.Split.Previous); + Assert.Equal(blockSize, (int)object_0.Header.PayloadLength); + Assert.Equal(bytes[..blockSize], object_0.Payload); + Assert.True(object_0.Header.Attributes.Count == 0); + + Assert.Equal(object_0.Header.Split.SplitId, object_1.Header.Split.SplitId); + Assert.Equal(object_0.ObjectId, object_1.Header.Split.Previous); + Assert.Equal(blockSize, (int)object_1.Header.PayloadLength); + Assert.Equal(bytes[blockSize..(blockSize * 2)], object_1.Payload); + Assert.True(object_1.Header.Attributes.Count == 0); + + // last part + Assert.NotNull(object_2.Header.Split.Parent); + Assert.NotNull(object_2.Header.Split.ParentHeader); + Assert.NotNull(object_2.Header.Split.ParentSignature); + Assert.Equal(object_1.Header.Split.SplitId, object_2.Header.Split.SplitId); + Assert.Equal(object_1.ObjectId, object_2.Header.Split.Previous); + Assert.Equal(fileLength%blockSize, (int)object_2.Header.PayloadLength); + Assert.Equal(bytes[((fileLength/blockSize) * blockSize)..fileLength], object_2.Payload); + Assert.True(object_2.Header.Attributes.Count == 0); + + // link object + Assert.Equal(object_2.Header.Split.Parent, object_3.Header.Split.Parent); + Assert.Equal(object_2.Header.Split.ParentHeader, object_3.Header.Split.ParentHeader); + Assert.Equal(object_2.Header.Split.SplitId, object_3.Header.Split.SplitId); + Assert.Equal(0, (int)object_3.Header.PayloadLength); + Assert.Contains(object_0.ObjectId, object_3.Header.Split.Children); + Assert.Contains(object_2.ObjectId, object_3.Header.Split.Children); + Assert.Contains(object_2.ObjectId, object_3.Header.Split.Children); + Assert.True(object_2.Header.Attributes.Count == 0); + + Assert.Single(object_3.Header.Split.ParentHeader.Attributes); + Assert.Equal("k", object_3.Header.Split.ParentHeader.Attributes[0].Key); + Assert.Equal("v", object_3.Header.Split.ParentHeader.Attributes[0].Value); + + var modelObjId = ObjectId.FromHash(object_3.Header.Split.Parent.Value.ToByteArray()); + + Assert.Equal(result.Value, modelObjId.ToString()); + } + + [Fact] + public async void DeleteObject() + { + Mocker.ObjectId = new ObjectID { Value = ByteString.CopyFrom(SHA256.HashData(Encoding.UTF8.GetBytes("test"))) }.ToModel(); + + await GetClient().DeleteObjectAsync(ContainerId, Mocker.ObjectId); + + var request = Mocker.DeleteRequests.FirstOrDefault(); + Assert.NotNull(request); + Assert.Equal(ContainerId.ToGrpcMessage(), request.Body.Address.ContainerId); + Assert.Equal(Mocker.ObjectId.ToGrpcMessage(), request.Body.Address.ObjectId); + } + + [Fact] + public async void GetHeaderTest() + { + Mocker.ObjectId = new ObjectID { Value = ByteString.CopyFrom(SHA256.HashData(Encoding.UTF8.GetBytes("test"))) }.ToModel(); + + var response = await GetClient().GetObjectHeadAsync(ContainerId, Mocker.ObjectId); + + var request = Mocker.HeadRequests.FirstOrDefault(); + Assert.NotNull(request); + Assert.Equal(ContainerId.ToGrpcMessage(), request.Body.Address.ContainerId); + Assert.Equal(Mocker.ObjectId.ToGrpcMessage(), request.Body.Address.ObjectId); + + Assert.NotNull(response); + Assert.Equal(ContainerId.Value, response.ContainerId.Value); + + Assert.Equal(Mocker.ObjectHeader!.OwnerId!.ToGrpcMessage().ToString(), response.OwnerId!.Value); + Assert.Equal(Mocker.ObjectHeader!.Version!.ToString(), response.Version!.ToString()); + + Assert.Equal(Mocker.HeadResponse!.PayloadLength, response.PayloadLength); + + Assert.Equal(ObjectType.Regular, response.ObjectType); + + Assert.Single(response.Attributes); + + Assert.Equal(Mocker.HeadResponse.Attributes[0].Key, response.Attributes.First().Key); + Assert.Equal(Mocker.HeadResponse.Attributes[0].Value, response.Attributes.First().Value); + + Assert.Null(response.Split); } } diff --git a/src/FrostFS.SDK.Tests/ClientTestLive.cs b/src/FrostFS.SDK.Tests/SmokeTests.cs similarity index 85% rename from src/FrostFS.SDK.Tests/ClientTestLive.cs rename to src/FrostFS.SDK.Tests/SmokeTests.cs index bfd50ca..6a33209 100644 --- a/src/FrostFS.SDK.Tests/ClientTestLive.cs +++ b/src/FrostFS.SDK.Tests/SmokeTests.cs @@ -5,14 +5,13 @@ using FrostFS.SDK.ModelsV2; using FrostFS.SDK.ModelsV2.Enums; using FrostFS.SDK.ModelsV2.Netmap; using Grpc.Core; -using Grpc.Net.Client; using Microsoft.Extensions.Options; using Grpc.Core.Interceptors; using System.Diagnostics; -namespace FrostFS.SDK.Tests; +namespace FrostFS.SDK.SmokeTests; -public class ClientTestLive +public class SmokeTests { private readonly string key = "KwHDAJ66o8FoLBjVbjP2sWBmgBMGjt7Vv4boA7xQrBoAYBE397Aq"; private readonly string url = "http://172.29.238.97:8080"; @@ -46,11 +45,24 @@ public class ClientTestLive Assert.Equal(2, result.Version.Major); Assert.Equal(13, result.Version.Minor); Assert.Equal(NodeState.Online, result.State); - Assert.True(result.PublicKey.Length > 0); + Assert.Equal(33, result.PublicKey.Length); Assert.Single(result.Addresses); Assert.Equal(9, result.Attributes.Count); } + [Fact] + public async void NodeInfo_Statictics_Test() + { + var ctx = new Context + { + Callback = (cs) => Console.WriteLine($"{cs.MethodName} took {cs.ElapsedMicroSeconds} microseconds") + }; + + using var fsClient = Client.GetInstance(GetOptions(this.key, this.url)); + + var result = await fsClient.GetNodeInfoAsync(); + } + [Fact] public async void SimpleScenarioTest() { @@ -76,9 +88,7 @@ public class ClientTestLive Assert.NotNull(container); - Random rnd = new(); - var bytes = new byte[6 * 1024 * 1024 + 100]; - rnd.NextBytes(bytes); + var bytes = GetRandomBytes(6 * 1024 * 1024 + 100); var param = new PutObjectParameters { @@ -141,25 +151,21 @@ public class ClientTestLive await Cleanup(fsClient); - var containerId = await fsClient.CreateContainerAsync( - new ModelsV2.Container(BasicAcl.PublicRW, new PlacementPolicy(true, new Replica(1)))); + var cnt = new ModelsV2.Container(BasicAcl.PublicRW, new PlacementPolicy(true, new Replica(1))); - var context = new Context - { - Timeout = TimeSpan.FromSeconds(10) + var containerId = await fsClient.CreateContainerAsync(cnt); + + var context = new Context + { + Timeout = TimeSpan.FromSeconds(10), + Interceptors = new([new MetricsInterceptor()]) }; - var metrics = new MetricsInterceptor(); - - context.Interceptors.Add(metrics); - var container = await GetContainer(fsClient, containerId, context); Assert.NotNull(container); - Random rnd = new(); - var bytes = new byte[6 * 1024 * 1024 + 100]; - rnd.NextBytes(bytes); + byte[] bytes = GetRandomBytes(150 * 1024 * 1024); var param = new PutObjectParameters { @@ -200,6 +206,8 @@ public class ClientTestLive ms.Write(chunk); } + Assert.Equal(MD5.HashData(bytes), MD5.HashData(downloadedBytes)); + await Cleanup(fsClient); await Task.Delay(2000); @@ -210,15 +218,21 @@ public class ClientTestLive } } + private static byte[] GetRandomBytes(int size) + { + Random rnd = new(); + var bytes = new byte[size]; + rnd.NextBytes(bytes); + return bytes; + } + private static IOptions GetOptions(string key, string url) { - var settings = new ClientSettings + return Options.Create(new ClientSettings { Key = key, Host = url - }; - - return Options.Create(settings); + }); } static async Task Cleanup(IFrostFSClient fsClient) @@ -243,7 +257,7 @@ public class ClientTestLive if (DateTime.UtcNow >= ctx.Deadline) throw new TimeoutException(); } - catch (Grpc.Core.RpcException) + catch (RpcException) { throw; } @@ -268,7 +282,7 @@ public class MetricsInterceptor() : Interceptor call.Dispose); } - private async Task HandleUnaryResponse(AsyncUnaryCall call) + private static async Task HandleUnaryResponse(AsyncUnaryCall call) { var watch = new Stopwatch(); watch.Start(); diff --git a/src/FrostFS.SDK.Tests/TestData/cat.jpg b/src/FrostFS.SDK.Tests/TestData/cat.jpg new file mode 100644 index 0000000000000000000000000000000000000000..03236d57c0bd3698dab8ba686ec867c0782de91a GIT binary patch literal 6740 zcmb7HbyQT}x1S*g1Y~Fd=^8qQ?hfgc6cA+q=?+mwx?voUmS!j=9T*U45W%5wK$MVf z5K-#k@B4l))_VWEbMIYypB+ZJo|di_00;yCfHx1|dL9rBAR!{A zqNbvyrlO{$qot?6dFdHwY5x*hhMSL;o|f@09~%QB_gz|Ab}@Eth=8!LFaxWEjD(;x zpOCNskd%~^f}Dbpl9Ew~m5p2I|J|-X0Kg!?J0LYakOP1R2I7N(*L?tH002PzH`4!Z zc=!Z_M8rVQjn;?~0K~(`Cn6@HAS3`1-q-*EH)=2;yBHCbs)o699BOL*&>vpXG)W_=rtZweEpbSz5m4HDbOU$?2*3ya@8NC+ARxR^v4U^p zczFL)4a5WEi&3$wnotv5;{as%H+nEW7@!Qm{4yWmJAEQ^J3EeiO<-YygP0QE)p~-9zE*@>10GxD(f^|JaF>hH)YE5Pg-M~G z&bk_%{>zPDzSi&z68Xev!MX!cjTce~KMSuN`$wXwI8zxx(`o(2bQ zej^kbO*&5~8w^TV`!{x|m>gbQetta(3_AIBIkHRo?^N8A#^mouA=mmZ`%|f6Og83k zVK9biJ0Fj4;(j$0xjpIMbinPbn(e_M+dT%0XO_6XZf}*5q}FZm8NGj;qs+*=sc{b* z)=faEBi0pAIa;pt^GfYQ?vW_B_xnwwJiVve7ctM-C^qV4GUMSOKs!Acf!_e$*wqNr z>m-GP*xBGD(*=CaeI7&zy{#D&uT+CSlBy4KGAH_*fvcB-TP&)2eth?=xQi$%?udBP zgM6bB$NW~TLK>|oO*uY>3EGOSs6Crx9lvN4RTbW}A}67u-KA%e_YDwcgWs=x;g=a| zVyW7ur(r`7SmsUxfbvI1*wyU!^V50oz)bE1W2=Lp%{r;2!g#-AF0xWEK?3Fwt#`Jq z2)%+7`l=BGvqdUh&gkg#VxCQcw{`JIsL@0`Dsg=w_;+7X3iU||o1!Be#P&aCiS8P(_wsjh?t*zx5R{TUgbS4M zS|Haz)ZiRfP?#l+T~Jk!i~c%(H&&%DWvfbUV`Vc=#ET71`sF57&q@C#@6*4M$M4+s z>+Y35?~2AHU;OR+Y;X{n_gCIyH>1vH>2F~~KReFLt~?>X-W<{07XFC5kMxtTw!m-% z@aKQxr*$7fgcxo1&){79YcnT2IpkBGSk^>}WVl#j%dEJw-Nl_(#$UDDnwI_C91d~G zCG>0LI<|#-I9m50*{vCIj`Xb3!h5BaN#xqxj(8!4K1_s0x};&IUIZctQi;<{!caa~ zF{$MPWmR_9Sevs+NYhZ8${j)fGIg{;74xN1E+{STbSbxihjO0XtDw`zH&B@}=RwifUX-1k*c3Mmu zkO|XtqS3oNTY)G1Idgf*ydnUpC0|K`tx1F7m4ZkZl+i7r{_D`1&C5AEqt_noD0-=P z0If>xZGHYHZIdH7WPb4w^8|8KT<7uqm2NkaAuQq~V_oYVhZB`qp- zYP|idpUgY!Ujy<6uK_d~#ML+Df=GG~@t;MYGeMxxa`N9@>qyORLQ8J_I*d%Sh0;(* zsQHY=J>cyORhMwI9V#D|6<@2QnPIzowk`yJVoA?2?1wC3G|4UDr!)&vi1m+R7jIlm zT6ja}@)|}jZ0KbWmU4C=f=2{HbMqm&X=HudU+17gP1U}Wga~r{;U&#iI^HZ^q(~{2 zN&vRa`Y=-@-v(o??)`KCR&j#gR;rO}HSB7>LnwkSL=kyE(_aHr?^bd zWdE8pp>27uY49-so9nwBnoq4`_mn4xFeo`2)7;{eoz& z(&F3VpV8_?WWUdEuGDj|C~NriN41_w)5vkvRKnDPMT$9KCusG^(4tO=R~^)|T~(AA z6rCcZBkk~Po5D1AZb$gT(L{TQ;WmDQ?w~ZCJM5drESF=3-FFy(`TevCTwf-eDHZlT zrE6IVSeabXIcugXDOdjiSn&t-yUp&{o*5I@uxjGFdP`H69B0s*G_S^|?P`3GM&MwG zu&t7W>94KbiZ7q)a-2}aP*-6+qM*G&h2?kUE6b|G zd2`XC@`-b`PgrX(fAD!nQR+2dJP{t2KtQSTuy9lPpB*&EMb^f<-Dqv1DC`=*{^&Q= z_Vlua)5pofh(uJT(b7?kk_BdY`W|<|VLmF)JTX!r!_~63KEiWbo%PUqkDih@5LJq<$+Zo^7HQ;=d!C%GSZr&}_leajB-lkKR9PZyaF~gjnOG~i80?iw z)n{p`QWYNLs)k@9chQ~i7X7szhU6X#jO&x62S`_lYl&)ZEW0fBe8j&v>-m<9D^Yvw z_6A`PB5Z4ErA=RJhkavU_+659m&|5_^MUh0Gn#H@cG7wAC zQ+HEL4wxGZN$jG{S{xl3s<2L%5!KV8AgDG;#$DSuHq@7H&ur0BwvXI$+`LBK z<*_HlI(Vo?Nmfo!R%lmzb52-+@_4{wh|tU$qgcPBbZPM6iTC`RC^C*>S>MKi{pPYM`xt6^y!5{iDElxsL4Y~oMp7H$wRJkmTDU5F&t_orFt z)um4_DXq^JpC>D(qL(ny*Bm=*o|J~wU4GR5&m|a-YTNp@P%z!HdJ%k;tkF9)#0FPQ z4gdEhXO%JVv--b!Mwy144&0W^sNnwUEd8S7AC?gnH*CG(4XcmqppP&%ZeTyCE^HNI zufTr1IX_!IyQ?5`>GK+>_t=6-(x+WJI~H1b&>P5e05Dp5%OAai>H=!iS^)%hv55oU z64jjTFvQ`$9P#WS33kiEr$2Q1U~V4*LMOl6al~r*5+}Y7;NcGV)!lbrMWIEf`%MfB z^^)fII8rOkKzOyRN#-Qj|DK9JPC`A3KoR2>qDY_5Uuh@d%{jKH4!?WEdqQnr*zFM~ z)L$6k&n-1L6bk=-_Ph3>VIT=AP!wZ1QEM4ftj4ZZH&xRHNqtSdu`K3!D`mxikj9n^ zz*8xMNEH7uqV$xT}EFeI4mKbEyH_j7g^xeb1hTh{F$S66GD zmofv@N>5;P8Ik-G$av*9G9hD}p8d=@t|teBtkItL45HHk%lLIxYlXH}q-mb!H%@?s{cK(MzfW5Y zJNfA90;+Jnf=%3yJ5E(KUP6vJr+$nrP`p*p=wKvm5QaDgV-(R-5qVmrV=UES>rne#S>0n3 zI}XFdtp&p)>I6EuwgtA3VXf$0HfevAb;>d(8Mg!_Vc#uGxp)OaA}SkA1)aJE;2d3l zJV+vsaF(-CQ!EA#>Z=Z%_8#Tueh?#RZ@vb6bq$b|P3QD)pDSqFFGHiM)S1gq-q*-f`_I%~$b70cK7YedOpa{D<~=8q9Y* zNJc{w7^eR)5{}kGO}(OjkwP0^f4c^R(KgPe#|~rzd`^o=OMXkyQ!#I?#26Vu)>E?Q zNrLhl9<$86^5&F&SQw^)$x5gz7P(EiuI%&)5LlR=H>4TP%x|x^FxmZv8-gwOBz8^8 zKizp^2~fTb2&fs@b1(UW(GWM52YC_SCtRuh$;IOm01CXIQFI8<-Ig4<_+9$0gFa<9 zUM-nQkm&5wh4FIp-X}%(CleUKbIGO3OC@Cug}_W2e=ZJbkQZz8&uy2W2G2MaGx&2<`dplq`yRU29_s45QfMhul+-sWgyWTI@0$edBP6VTXgl`zdxFkV~m*e!#Z=G0WOy!wITv-V`9xl$i)X*ych z@$ZPM(5!yIsr_e;MWQ2)AN2&p22cMi-*Ep~zgo8Uc=*`q_+!x{_0C1Q`K;7Zr?asw z3s4El8eMCB5o%*+3INnMNl7@o(j4wC_FEq;{L1hn5zU+(`-iuQ?qOar*CPsKO!l_5 z<7*54851fpPV>!l5{R3v%u8V|qQxKxK4IN&s{*wHS)c7?gVzgu^RWe9D{`&8^kl(d z7JY4W0u3|(xAy(M9a+A-oL62n{!OV9DvREVpz(e!|9FqU-?>D*b5&;uwdNNThnHT( z&{(C-Bx@|@vs!0Og(ZEsGtc^8k{)| ziB__;Sy^^KF2VL$RzxgxVkwIDggoKXNZ&m*b{Vu>o-oG39Qw%)YZpuw0&mQ*1V5#C zudHIg!&UsUL#ZgUeyBb~l0dp_`<`x(C`kbG+RTw;LA7V=Q^KCr87MUzg-rZb{G)F1 zVq)k6G?^TB+pCZDvCMZ5dnKH?PMo%RQgdZWiaxVjb&ZjT@Z2|hZf+L|5omq!#>6Wd zf1fm>wwB3zH8qyM2ylWcNp5B)JGZsf;Dsp#Hun^mqwDOx4WsEnx zvHl)?2DWX(JUD;yLAmyVXX+gvj&Z%W{wB^1tDx$MOeLg4UcU9ZXp=ySYkH~=>Llz7s?TpX@*9Uhj zWq!+o2iitAPdLa5lSMx%RP4p1ERP&kp7h&9pav2`$l?iSA+S@ynRv%t3h%9H&ognoWP=9Ot{npm<=~P~PhHBl@HuI-Bj<}4H?1AT}66)?VDbv-M zA(sW!BOLKEG0N(o{L;DN>=4xxx=PZ->mU&NdZ2n8w%TWfzxb8xF6|hbA0ag>R|(Ph z7zMNgn{z!buZ-ife$U z+u4sZ+vqwbGeedlxrA%Ln3>VLSCtvHGONZicZ22AXA)iZ#lGYQ>}i0nXU zVd-!zQ53(2dRi<0l0Z7b(YTf2BJ#Nz`pYfxEy^=hoOdWuQ5>z3EbB}2*Lub?+2{6s z8rGn#lQPk?@dKy8u+J#p=yj3v4qGDfipxhi(^-V<^}2;*?ewb0tPw(?JhwLA zT2-S$Wh-0P@uXzHGx-brZM})p(wSr4hdyp6X_-Gv5GfX}!_-&xx0CnTKTb>=s}dvt zkzG8jOzp_P>h3IxQl~>q6@Pc+)KUR-TSY11ZP=b4a42lsndd_c77sX-2#$97%D^`A zYs&HDx&0^~;rSMkG@}Y$npquik_L8C&OSJk!B-C={(8a@pD6j6-M7K8nV{E>UWZf` zIrEZWTSYE_?h=gV2+n#+w?{&@a*|CFJfR{-AS~N0!L_ZywXL5gA4$QCOVrqn=}0{BRJLhr{f;(ZGlF8-^cTty#L{>30~7kAw?l5SyLT*no9yMx%|_k(wD3~q;)_S_(WIGsL+ zzxc~7{o{Cp2^o%By72%al-+OQWh?AfMRd7`c2f95t_-KNUEbjeU2h;A%`#u~Tp3RHW~$$iDM}+p^^D%0E7fi8+MO)Ql18=!y9omc Uhz(q