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
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
Uhz(q