unit tests
This commit is contained in:
parent
a74d4e4d08
commit
42e2beaf7c
35 changed files with 605 additions and 144 deletions
|
@ -294,6 +294,16 @@ public class FrostFSClient : IFrostFSClient
|
|||
return service.GetRangeAsync(args);
|
||||
}
|
||||
|
||||
public Task<IEnumerable<ReadOnlyMemory<byte>>> GetRangeHashAsync(PrmRangeHashGet args)
|
||||
{
|
||||
if (args is null)
|
||||
throw new ArgumentNullException(nameof(args));
|
||||
|
||||
var service = GetObjectService(args);
|
||||
return service.GetRangeHashAsync(args);
|
||||
}
|
||||
|
||||
|
||||
public Task<FrostFsObjectId> PutObjectAsync(PrmObjectPut args)
|
||||
{
|
||||
if (args is null)
|
||||
|
@ -311,8 +321,8 @@ public class FrostFSClient : IFrostFSClient
|
|||
var service = GetObjectService(args);
|
||||
return service.PutSingleObjectAsync(args);
|
||||
}
|
||||
|
||||
public Task PatchObjectAsync(PrmObjectPatch args)
|
||||
|
||||
public Task<FrostFsObjectId> PatchObjectAsync(PrmObjectPatch args)
|
||||
{
|
||||
if (args is null)
|
||||
{
|
||||
|
|
|
@ -44,11 +44,13 @@ public interface IFrostFSClient : IDisposable
|
|||
|
||||
Task<RangeReader> GetRangeAsync(PrmRangeGet args);
|
||||
|
||||
Task<IEnumerable<ReadOnlyMemory<byte>>> GetRangeHashAsync(PrmRangeHashGet args);
|
||||
|
||||
Task<FrostFsObjectId> PutObjectAsync(PrmObjectPut args);
|
||||
|
||||
Task<FrostFsObjectId> PutSingleObjectAsync(PrmSingleObjectPut args);
|
||||
|
||||
Task PatchObjectAsync(PrmObjectPatch args);
|
||||
Task<FrostFsObjectId> PatchObjectAsync(PrmObjectPatch args);
|
||||
|
||||
Task DeleteObjectAsync(PrmObjectDelete args);
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
using System;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
using FrostFS.Refs;
|
||||
using FrostFS.SDK.Cryptography;
|
||||
|
|
|
@ -10,7 +10,7 @@ public class FrostFsAddress
|
|||
private ObjectID? objectId;
|
||||
private ContainerID? containerId;
|
||||
|
||||
public FrostFsAddress(FrostFsObjectId frostFsObjectId, FrostFsContainerId frostFsContainerId)
|
||||
public FrostFsAddress(FrostFsContainerId frostFsContainerId, FrostFsObjectId frostFsObjectId)
|
||||
{
|
||||
FrostFsObjectId = frostFsObjectId ?? throw new System.ArgumentNullException(nameof(frostFsObjectId));
|
||||
FrostFsContainerId = frostFsContainerId ?? throw new System.ArgumentNullException(nameof(frostFsContainerId));
|
||||
|
@ -24,25 +24,25 @@ public class FrostFsAddress
|
|||
|
||||
public FrostFsObjectId FrostFsObjectId
|
||||
{
|
||||
get => frostFsObjectId ??= objectId!.ToModel();
|
||||
set => frostFsObjectId = value;
|
||||
get => frostFsObjectId ??= objectId!.ToModel();
|
||||
set => frostFsObjectId = value;
|
||||
}
|
||||
|
||||
public FrostFsContainerId FrostFsContainerId
|
||||
{
|
||||
public FrostFsContainerId FrostFsContainerId
|
||||
{
|
||||
get => frostFsContainerId ??= containerId!.ToModel();
|
||||
set => frostFsContainerId = value;
|
||||
set => frostFsContainerId = value;
|
||||
}
|
||||
|
||||
public ObjectID ObjectId
|
||||
|
||||
public ObjectID ObjectId
|
||||
{
|
||||
get => objectId ??= frostFsObjectId!.ToMessage();
|
||||
set => objectId = value;
|
||||
set => objectId = value;
|
||||
}
|
||||
|
||||
public ContainerID ContainerId
|
||||
{
|
||||
get => containerId ??= frostFsContainerId!.ToMessage();
|
||||
set => containerId = value;
|
||||
set => containerId = value;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
namespace FrostFS.SDK;
|
||||
|
||||
public struct FrostFsRange : System.IEquatable<FrostFsRange>
|
||||
public readonly struct FrostFsRange(ulong offset, ulong length) : System.IEquatable<FrostFsRange>
|
||||
{
|
||||
public ulong Offset {get; set;}
|
||||
public ulong Offset { get; } = offset;
|
||||
|
||||
public ulong Length { get; set; }
|
||||
public ulong Length { get; } = length;
|
||||
|
||||
public override readonly bool Equals(object obj) => this == (FrostFsRange)obj;
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace FrostFS.SDK.ClientV2;
|
||||
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
namespace FrostFS.SDK.ClientV2;
|
||||
|
||||
public sealed class PrmRangeGet(
|
||||
FrostFsContainerId containerId,
|
||||
FrostFsContainerId containerId,
|
||||
FrostFsObjectId objectId,
|
||||
FrostFsRange range,
|
||||
bool raw = false,
|
||||
CallContext? ctx = null) : PrmBase(ctx), ISessionToken
|
||||
{
|
||||
public FrostFsContainerId ContainerId { get; } = containerId;
|
||||
|
@ -12,6 +13,8 @@ public sealed class PrmRangeGet(
|
|||
|
||||
public FrostFsRange Range { get; } = range;
|
||||
|
||||
public bool Raw { get; } = raw;
|
||||
|
||||
/// <inheritdoc />
|
||||
public FrostFsSessionToken? SessionToken { get; set; }
|
||||
}
|
||||
|
|
20
src/FrostFS.SDK.ClientV2/Parameters/PrmRangeHashGet.cs
Normal file
20
src/FrostFS.SDK.ClientV2/Parameters/PrmRangeHashGet.cs
Normal file
|
@ -0,0 +1,20 @@
|
|||
namespace FrostFS.SDK.ClientV2;
|
||||
|
||||
public sealed class PrmRangeHashGet(
|
||||
FrostFsContainerId containerId,
|
||||
FrostFsObjectId objectId,
|
||||
FrostFsRange[] ranges,
|
||||
byte[] salt,
|
||||
CallContext? ctx = null) : PrmBase(ctx), ISessionToken
|
||||
{
|
||||
public FrostFsContainerId ContainerId { get; } = containerId;
|
||||
|
||||
public FrostFsObjectId ObjectId { get; } = objectId;
|
||||
|
||||
public FrostFsRange[] Ranges { get; } = ranges;
|
||||
|
||||
public byte[] Salt { get; } = salt;
|
||||
|
||||
/// <inheritdoc />
|
||||
public FrostFsSessionToken? SessionToken { get; set; }
|
||||
}
|
|
@ -104,7 +104,7 @@ public class ClientWrapper : ClientStatusMonitor
|
|||
//#pragma warning disable CA2000 // Dispose objects before losing scope: will be disposed manually
|
||||
FrostFSClient client = new(WrapperPrm, sessionCache);
|
||||
//#pragma warning restore CA2000
|
||||
|
||||
|
||||
//TODO: set additioanl params
|
||||
var error = await client.Dial(ctx).ConfigureAwait(false);
|
||||
if (!string.IsNullOrEmpty(error))
|
|
@ -2,7 +2,6 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
@ -711,7 +710,7 @@ public partial class Pool : IFrostFSClient
|
|||
return await client.Client!.PutSingleObjectAsync(args).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task PatchAsync(PrmObjectPatch args)
|
||||
public async Task<FrostFsObjectId> PatchObjectAsync(PrmObjectPatch args)
|
||||
{
|
||||
if (args is null)
|
||||
{
|
||||
|
@ -722,7 +721,49 @@ public partial class Pool : IFrostFSClient
|
|||
|
||||
args.Context.PoolErrorHandler = client.HandleError;
|
||||
|
||||
await client.Client!.PatchObjectAsync(args).ConfigureAwait(false);
|
||||
return await client.Client!.PatchObjectAsync(args).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task<RangeReader> GetRangeAsync(PrmRangeGet args)
|
||||
{
|
||||
if (args is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(args));
|
||||
}
|
||||
|
||||
var client = Connection();
|
||||
|
||||
args.Context.PoolErrorHandler = client.HandleError;
|
||||
|
||||
return await client.Client!.GetRangeAsync(args).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ReadOnlyMemory<byte>>> GetRangeHashAsync(PrmRangeHashGet args)
|
||||
{
|
||||
if (args is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(args));
|
||||
}
|
||||
|
||||
var client = Connection();
|
||||
|
||||
args.Context.PoolErrorHandler = client.HandleError;
|
||||
|
||||
return await client.Client!.GetRangeHashAsync(args).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task<FrostFsObjectId> PatchAsync(PrmObjectPatch args)
|
||||
{
|
||||
if (args is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(args));
|
||||
}
|
||||
|
||||
var client = Connection();
|
||||
|
||||
args.Context.PoolErrorHandler = client.HandleError;
|
||||
|
||||
return await client.Client!.PatchObjectAsync(args).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task DeleteObjectAsync(PrmObjectDelete args)
|
||||
|
@ -787,14 +828,4 @@ public partial class Pool : IFrostFSClient
|
|||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task PatchObjectAsync(PrmObjectPatch args)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<RangeReader> GetRangeAsync(PrmRangeGet args)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
|
@ -130,7 +130,8 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl
|
|||
{
|
||||
Offset = args.Range.Offset,
|
||||
Length = args.Range.Length
|
||||
}
|
||||
},
|
||||
Raw = args.Raw
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -149,6 +150,59 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl
|
|||
return new RangeReader(call);
|
||||
}
|
||||
|
||||
internal async Task<IEnumerable<ReadOnlyMemory<byte>>> GetRangeHashAsync(PrmRangeHashGet args)
|
||||
{
|
||||
var ctx = args.Context!;
|
||||
|
||||
ctx.Key ??= ClientContext.Key?.ECDsaKey;
|
||||
|
||||
if (ctx.Key == null)
|
||||
throw new ArgumentNullException(nameof(args), "Key is null");
|
||||
|
||||
var request = new GetRangeHashRequest
|
||||
{
|
||||
Body = new GetRangeHashRequest.Types.Body
|
||||
{
|
||||
Address = new Address
|
||||
{
|
||||
ContainerId = args.ContainerId.ToMessage(),
|
||||
ObjectId = args.ObjectId.ToMessage()
|
||||
},
|
||||
Type = ChecksumType.Sha256,
|
||||
Salt = ByteString.CopyFrom(args.Salt) // TODO: create a type with calculated cashed ByteString inside
|
||||
}
|
||||
};
|
||||
|
||||
foreach (var range in args.Ranges)
|
||||
{
|
||||
request.Body.Ranges.Add(new Object.Range
|
||||
{
|
||||
Length = range.Length,
|
||||
Offset = range.Offset
|
||||
});
|
||||
}
|
||||
|
||||
var sessionToken = await GetOrCreateSession(args, ctx).ConfigureAwait(false);
|
||||
|
||||
sessionToken.CreateObjectTokenContext(
|
||||
request.Body.Address,
|
||||
ObjectSessionContext.Types.Verb.Rangehash,
|
||||
ctx.Key);
|
||||
|
||||
request.AddMetaHeader(args.XHeaders, sessionToken);
|
||||
|
||||
request.Sign(ctx.Key);
|
||||
|
||||
var response = await client.GetRangeHashAsync(request, null, ctx.Deadline, ctx.CancellationToken);
|
||||
|
||||
Verifier.CheckResponse(response);
|
||||
|
||||
var hashCollection = response.Body.HashList.ToArray().Select(h => h.Memory);
|
||||
|
||||
return hashCollection;
|
||||
}
|
||||
|
||||
|
||||
internal async Task DeleteObjectAsync(PrmObjectDelete args)
|
||||
{
|
||||
var ctx = args.Context!;
|
||||
|
@ -281,19 +335,19 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl
|
|||
return FrostFsObjectId.FromHash(grpcObject.ObjectId.Value.ToByteArray());
|
||||
}
|
||||
|
||||
internal async Task PatchObjectAsync(PrmObjectPatch args)
|
||||
internal async Task<FrostFsObjectId> PatchObjectAsync(PrmObjectPatch args)
|
||||
{
|
||||
var ctx = args.Context!;
|
||||
if (ctx.Key == null)
|
||||
throw new ArgumentNullException(nameof(args), "Key is null");
|
||||
|
||||
|
||||
var chunkSize = args.MaxPayloadPatchChunkLength;
|
||||
Stream payload = args.Payload ?? throw new ArgumentNullException(nameof(args), "Stream parameter is null");
|
||||
|
||||
var call = client.Patch(null, ctx.Deadline, ctx.CancellationToken);
|
||||
|
||||
byte[]? chunkBuffer = null;
|
||||
try
|
||||
try
|
||||
{
|
||||
// common
|
||||
chunkBuffer = ClientContext.GetArrayPool(Constants.ObjectChunkSize).Rent(chunkSize);
|
||||
|
@ -312,7 +366,7 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl
|
|||
ctx.Key
|
||||
);
|
||||
|
||||
var request = new PatchRequest
|
||||
var request = new PatchRequest()
|
||||
{
|
||||
Body = new()
|
||||
{
|
||||
|
@ -322,8 +376,9 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl
|
|||
};
|
||||
|
||||
bool isFirstChunk = true;
|
||||
ulong currentPos = args.Range.Offset;
|
||||
|
||||
while (true)
|
||||
while (true)
|
||||
{
|
||||
var bytesCount = await payload.ReadAsync(chunkBuffer, 0, chunkSize, ctx.CancellationToken).ConfigureAwait(false);
|
||||
|
||||
|
@ -332,7 +387,7 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl
|
|||
break;
|
||||
}
|
||||
|
||||
if (isFirstChunk)
|
||||
if (isFirstChunk && args.NewAttributes != null && args.NewAttributes.Length > 0)
|
||||
{
|
||||
foreach (var attr in args.NewAttributes)
|
||||
{
|
||||
|
@ -340,14 +395,18 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl
|
|||
}
|
||||
}
|
||||
|
||||
request.Sign(ctx.Key);
|
||||
|
||||
request.Body.Patch = new PatchRequest.Types.Body.Types.Patch
|
||||
{
|
||||
Chunk = ByteString.CopyFrom(chunkBuffer, 0, bytesCount),
|
||||
SourceRange = new Object.Range { Offset = 0, Length = (ulong)bytesCount }
|
||||
SourceRange = new Object.Range { Offset = currentPos, Length = (ulong)bytesCount }
|
||||
};
|
||||
|
||||
currentPos += (ulong)bytesCount;
|
||||
|
||||
request.AddMetaHeader(args.XHeaders, sessionToken);
|
||||
|
||||
request.Sign(ctx.Key);
|
||||
|
||||
await call.RequestStream.WriteAsync(request).ConfigureAwait(false);
|
||||
|
||||
isFirstChunk = false;
|
||||
|
@ -362,6 +421,12 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl
|
|||
ArrayPool<byte>.Shared.Return(chunkBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
var response = await call.ResponseAsync.ConfigureAwait(false);
|
||||
|
||||
Verifier.CheckResponse(response);
|
||||
|
||||
return response.Body.ObjectId.ToModel();
|
||||
}
|
||||
|
||||
private async Task<FrostFsObjectId> PutClientCutObject(PrmObjectPut args)
|
||||
|
@ -534,7 +599,7 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private async Task<ObjectStreamer<PutRequest, PutResponse>> GetUploadStream(PrmObjectPut args, CallContext ctx)
|
||||
{
|
||||
var header = args.Header!;
|
||||
|
@ -553,7 +618,7 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl
|
|||
}
|
||||
|
||||
var oid = new ObjectID { Value = grpcHeader.Sha256() };
|
||||
|
||||
|
||||
var initRequest = new PutRequest
|
||||
{
|
||||
Body = new PutRequest.Types.Body
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using FrostFS.Object;
|
||||
|
||||
using Grpc.Core;
|
||||
|
||||
namespace FrostFS.SDK.ClientV2;
|
||||
|
|
43
src/FrostFS.SDK.Tests/Mocks/AsyncStreamRangeReaderMock.cs
Normal file
43
src/FrostFS.SDK.Tests/Mocks/AsyncStreamRangeReaderMock.cs
Normal file
|
@ -0,0 +1,43 @@
|
|||
using System.Security.Cryptography;
|
||||
|
||||
using FrostFS.Object;
|
||||
using FrostFS.SDK.ClientV2;
|
||||
using FrostFS.SDK.ClientV2.Mappers.GRPC;
|
||||
using FrostFS.SDK.Cryptography;
|
||||
using FrostFS.Session;
|
||||
|
||||
using Google.Protobuf;
|
||||
|
||||
using Grpc.Core;
|
||||
|
||||
namespace FrostFS.SDK.Tests;
|
||||
|
||||
public class AsyncStreamRangeReaderMock(string key, byte[] response) : ServiceBase(key), IAsyncStreamReader<GetRangeResponse>
|
||||
{
|
||||
private readonly byte[] _response = response;
|
||||
|
||||
public GetRangeResponse Current
|
||||
{
|
||||
get
|
||||
{
|
||||
var response = new GetRangeResponse
|
||||
{
|
||||
Body = new GetRangeResponse.Types.Body
|
||||
{
|
||||
Chunk = ByteString.CopyFrom(_response)
|
||||
},
|
||||
MetaHeader = new ResponseMetaHeader()
|
||||
};
|
||||
|
||||
response.VerifyHeader = GetResponseVerificationHeader(response);
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
public Task<bool> MoveNext(CancellationToken cancellationToken)
|
||||
{
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ using FrostFS.Object;
|
|||
using FrostFS.SDK.ClientV2;
|
||||
using FrostFS.SDK.ClientV2.Mappers.GRPC;
|
||||
using FrostFS.SDK.Cryptography;
|
||||
using FrostFS.Session;
|
||||
|
||||
using Google.Protobuf;
|
||||
|
||||
|
@ -16,6 +17,32 @@ namespace FrostFS.SDK.Tests;
|
|||
|
||||
public class ObjectMocker(string key) : ObjectServiceBase(key)
|
||||
{
|
||||
public FrostFsObjectId? ObjectId { get; set; }
|
||||
|
||||
public FrostFsObjectHeader? ObjectHeader { get; set; }
|
||||
|
||||
public Header? HeadResponse { get; set; }
|
||||
|
||||
public Collection<byte[]>? ResultObjectIds { get; } = [];
|
||||
|
||||
public ClientStreamWriter? ClientStreamWriter { get; } = new();
|
||||
|
||||
public PatchStreamWriter? PatchStreamWriter { get; } = new();
|
||||
|
||||
public Collection<PutSingleRequest> PutSingleRequests { get; } = [];
|
||||
|
||||
public Collection<DeleteRequest> DeleteRequests { get; } = [];
|
||||
|
||||
public Collection<HeadRequest> HeadRequests { get; } = [];
|
||||
|
||||
public byte[] RangeResponse { get; set; } = [];
|
||||
|
||||
public GetRangeRequest? GetRangeRequest { get; set; }
|
||||
|
||||
public GetRangeHashRequest? GetRangeHashRequest { get; set; }
|
||||
|
||||
public Collection<ByteString> RangeHashResponses { get; } = [];
|
||||
|
||||
public override Mock<ObjectService.ObjectServiceClient> GetMock()
|
||||
{
|
||||
var mock = new Mock<ObjectService.ObjectServiceClient>();
|
||||
|
@ -189,23 +216,88 @@ public class ObjectMocker(string key) : ObjectServiceBase(key)
|
|||
});
|
||||
}
|
||||
|
||||
mock.Setup(x => x.GetRange(
|
||||
It.IsAny<GetRangeRequest>(),
|
||||
It.IsAny<Metadata>(),
|
||||
It.IsAny<DateTime?>(),
|
||||
It.IsAny<CancellationToken>()))
|
||||
.Returns((GetRangeRequest r, Metadata m, DateTime? dt, CancellationToken ct) =>
|
||||
{
|
||||
Verifier.CheckRequest(r);
|
||||
|
||||
GetRangeRequest = r;
|
||||
|
||||
return new AsyncServerStreamingCall<GetRangeResponse>(
|
||||
new AsyncStreamRangeReaderMock(StringKey, RangeResponse),
|
||||
Task.FromResult(ResponseMetaData),
|
||||
() => new Grpc.Core.Status(StatusCode.OK, string.Empty),
|
||||
() => ResponseMetaData,
|
||||
() => { });
|
||||
});
|
||||
|
||||
mock.Setup(x => x.GetRangeHashAsync(
|
||||
It.IsAny<GetRangeHashRequest>(),
|
||||
It.IsAny<Metadata>(),
|
||||
It.IsAny<DateTime?>(),
|
||||
It.IsAny<CancellationToken>()))
|
||||
.Returns((GetRangeHashRequest r, Metadata m, DateTime? dt, CancellationToken ct) =>
|
||||
{
|
||||
Verifier.CheckRequest(r);
|
||||
|
||||
GetRangeHashRequest = r;
|
||||
|
||||
var response = new GetRangeHashResponse
|
||||
{
|
||||
Body = new GetRangeHashResponse.Types.Body(),
|
||||
MetaHeader = ResponseMetaHeader
|
||||
};
|
||||
|
||||
if (RangeHashResponses != null)
|
||||
{
|
||||
foreach (var hash in RangeHashResponses)
|
||||
{
|
||||
response.Body.HashList.Add(hash);
|
||||
}
|
||||
}
|
||||
|
||||
response.VerifyHeader = GetResponseVerificationHeader(response);
|
||||
|
||||
return new AsyncUnaryCall<GetRangeHashResponse>(
|
||||
Task.FromResult(response),
|
||||
Task.FromResult(ResponseMetaData),
|
||||
() => new Grpc.Core.Status(StatusCode.OK, string.Empty),
|
||||
() => ResponseMetaData,
|
||||
() => { });
|
||||
});
|
||||
|
||||
|
||||
mock.Setup(x => x.Patch(
|
||||
It.IsAny<Metadata>(),
|
||||
It.IsAny<DateTime?>(),
|
||||
It.IsAny<CancellationToken>()))
|
||||
.Returns((Metadata m, DateTime? dt, CancellationToken ct) =>
|
||||
{
|
||||
var patchResponse = new PatchResponse
|
||||
{
|
||||
Body = new PatchResponse.Types.Body
|
||||
{
|
||||
ObjectId = new Refs.ObjectID { Value = ByteString.CopyFrom(SHA256.HashData([1,2,3])) },
|
||||
},
|
||||
MetaHeader = ResponseMetaHeader
|
||||
};
|
||||
|
||||
patchResponse.VerifyHeader = GetResponseVerificationHeader(patchResponse);
|
||||
|
||||
return new AsyncClientStreamingCall<PatchRequest, PatchResponse>(
|
||||
PatchStreamWriter!,
|
||||
Task.FromResult(patchResponse),
|
||||
Task.FromResult(ResponseMetaData),
|
||||
() => new Grpc.Core.Status(StatusCode.OK, string.Empty),
|
||||
() => ResponseMetaData,
|
||||
() => { });
|
||||
});
|
||||
|
||||
return mock;
|
||||
}
|
||||
|
||||
public FrostFsObjectId? ObjectId { get; set; }
|
||||
|
||||
public FrostFsObjectHeader? ObjectHeader { get; set; }
|
||||
|
||||
public Header? HeadResponse { get; set; }
|
||||
|
||||
public Collection<byte[]>? ResultObjectIds { get; } = [];
|
||||
|
||||
public ClientStreamWriter? ClientStreamWriter { get; private set; } = new();
|
||||
|
||||
public Collection<PutSingleRequest> PutSingleRequests { get; private set; } = [];
|
||||
|
||||
public Collection<DeleteRequest> DeleteRequests { get; private set; } = [];
|
||||
|
||||
public Collection<HeadRequest> HeadRequests { get; private set; } = [];
|
||||
}
|
||||
|
||||
|
|
36
src/FrostFS.SDK.Tests/Mocks/PatchStreamWriter.cs
Normal file
36
src/FrostFS.SDK.Tests/Mocks/PatchStreamWriter.cs
Normal file
|
@ -0,0 +1,36 @@
|
|||
using System.Collections.ObjectModel;
|
||||
|
||||
using FrostFS.SDK.ProtosV2.Interfaces;
|
||||
|
||||
using Grpc.Core;
|
||||
|
||||
namespace FrostFS.SDK.Tests;
|
||||
|
||||
public class PatchStreamWriter : IClientStreamWriter<IRequest>
|
||||
{
|
||||
private WriteOptions? _options;
|
||||
|
||||
public Collection<IRequest> Messages { get; } = [];
|
||||
|
||||
public bool CompletedTask { get; private set; }
|
||||
|
||||
public WriteOptions? WriteOptions
|
||||
{
|
||||
get => _options;
|
||||
set => _options = value;
|
||||
}
|
||||
|
||||
public Task CompleteAsync()
|
||||
{
|
||||
CompletedTask = true;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task WriteAsync(IRequest message)
|
||||
{
|
||||
Object.PatchRequest pr = new((Object.PatchRequest)message);
|
||||
Messages.Add(pr);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
|
@ -10,6 +10,8 @@ using FrostFS.SDK.Cryptography;
|
|||
|
||||
using Google.Protobuf;
|
||||
|
||||
using static FrostFS.Object.ECInfo.Types;
|
||||
|
||||
namespace FrostFS.SDK.Tests;
|
||||
|
||||
[SuppressMessage("Reliability", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "Default Value is correct for tests")]
|
||||
|
@ -223,4 +225,123 @@ public class ObjectTest : ObjectTestsBase
|
|||
|
||||
Assert.Null(response.Split);
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public async void GetRangeTest()
|
||||
{
|
||||
Mocker.ResultObjectIds!.Add(SHA256.HashData([]));
|
||||
|
||||
Random rnd = new();
|
||||
var bytes = new byte[1024];
|
||||
rnd.NextBytes(bytes);
|
||||
|
||||
Mocker.RangeResponse = bytes;
|
||||
|
||||
Mocker.ObjectId = new ObjectID { Value = ByteString.CopyFrom(SHA256.HashData(Encoding.UTF8.GetBytes("test"))) }.ToModel();
|
||||
|
||||
var param = new PrmRangeGet(ContainerId, Mocker.ObjectId, new FrostFsRange(100, (ulong)Mocker.RangeResponse.Length));
|
||||
|
||||
var result = await GetClient().GetRangeAsync(param);
|
||||
|
||||
Assert.NotNull(Mocker.GetRangeRequest);
|
||||
|
||||
Assert.Equal(param.Range.Offset, Mocker.GetRangeRequest.Body.Range.Offset);
|
||||
Assert.Equal(param.Range.Length, Mocker.GetRangeRequest.Body.Range.Length);
|
||||
|
||||
Assert.NotNull(result);
|
||||
|
||||
var chunk = await result.ReadChunk();
|
||||
|
||||
var chunkBytes = chunk.Value.Span.ToArray();
|
||||
|
||||
Assert.Equal(chunkBytes.Length, Mocker.RangeResponse.Length);
|
||||
|
||||
Assert.Equal(SHA256.HashData(bytes), SHA256.HashData(Mocker.RangeResponse));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async void GetRangeHashTest()
|
||||
{
|
||||
Mocker.ResultObjectIds!.Add(SHA256.HashData([]));
|
||||
|
||||
Random rnd = new();
|
||||
var bytes = new byte[1024];
|
||||
rnd.NextBytes(bytes);
|
||||
|
||||
var salt = new byte[32];
|
||||
rnd.NextBytes(salt);
|
||||
|
||||
var hash = new byte[32];
|
||||
rnd.NextBytes(hash);
|
||||
|
||||
Mocker.RangeResponse = bytes;
|
||||
var len = (ulong)bytes.Length;
|
||||
|
||||
Mocker.RangeHashResponses.Add(ByteString.CopyFrom(hash));
|
||||
|
||||
Mocker.ObjectId = new ObjectID { Value = ByteString.CopyFrom(SHA256.HashData(Encoding.UTF8.GetBytes("test"))) }.ToModel();
|
||||
|
||||
var param = new PrmRangeHashGet(ContainerId, Mocker.ObjectId, [new FrostFsRange(100, len)], salt);
|
||||
|
||||
var result = await GetClient().GetRangeHashAsync(param);
|
||||
|
||||
Assert.NotNull(Mocker.GetRangeHashRequest);
|
||||
|
||||
Assert.Equal(param.Ranges[0].Offset, Mocker.GetRangeHashRequest.Body.Ranges[0].Offset);
|
||||
Assert.Equal(param.Ranges[0].Length, Mocker.GetRangeHashRequest.Body.Ranges[0].Length);
|
||||
|
||||
Assert.NotNull(result);
|
||||
Assert.Single(result);
|
||||
|
||||
Assert.Equal(SHA256.HashData(hash), SHA256.HashData(result.First().ToArray()));
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public async void PatchTest()
|
||||
{
|
||||
Mocker.ObjectId = new ObjectID { Value = ByteString.CopyFrom(SHA256.HashData(Encoding.UTF8.GetBytes("test"))) }.ToModel();
|
||||
|
||||
var address = new FrostFsAddress(ContainerId, Mocker.ObjectId);
|
||||
|
||||
Mocker.ResultObjectIds!.Add(SHA256.HashData([]));
|
||||
|
||||
Random rnd = new();
|
||||
var patch = new byte[32];
|
||||
rnd.NextBytes(patch);
|
||||
|
||||
var range = new FrostFsRange(8, (ulong)patch.Length);
|
||||
|
||||
var param = new PrmObjectPatch(address)
|
||||
{
|
||||
Payload = new MemoryStream(patch),
|
||||
MaxPayloadPatchChunkLength = 32,
|
||||
Range = range
|
||||
};
|
||||
|
||||
var result = await GetClient().PatchObjectAsync(param);
|
||||
|
||||
Assert.NotNull(result);
|
||||
|
||||
Assert.NotNull(result.Value);
|
||||
|
||||
Assert.NotNull(Mocker.PatchStreamWriter);
|
||||
Assert.Single(Mocker.PatchStreamWriter.Messages);
|
||||
|
||||
var sentMessages = Mocker.PatchStreamWriter!.Messages;
|
||||
|
||||
var body = sentMessages.First().GetBody() as Object.PatchRequest.Types.Body;
|
||||
|
||||
Assert.NotNull(body);
|
||||
|
||||
Assert.True(Mocker.PatchStreamWriter.CompletedTask);
|
||||
|
||||
Assert.Equal(address.ContainerId, body.Address.ContainerId);
|
||||
Assert.Equal(address.ObjectId, body.Address.ObjectId);
|
||||
|
||||
Assert.Equal(32, body.Patch.Chunk.Length);
|
||||
|
||||
Assert.Equal(SHA256.HashData(patch), SHA256.HashData(body.Patch.Chunk.ToArray()));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
using FrostFS.Refs;
|
||||
using FrostFS.SDK.ClientV2;
|
||||
using FrostFS.SDK.ClientV2.Interfaces;
|
||||
using FrostFS.SDK.Cryptography;
|
||||
|
@ -249,8 +248,6 @@ public class SmokeClientTests : SmokeTestsBase
|
|||
[InlineData(6 * 1024 * 1024 + 100)]
|
||||
public async void SimpleScenarioTest(int objectSize)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var client = FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.keyString, this.url));
|
||||
|
||||
await Cleanup(client);
|
||||
|
@ -327,89 +324,86 @@ public class SmokeClientTests : SmokeTestsBase
|
|||
await foreach (var _ in client.ListContainersAsync())
|
||||
{
|
||||
Assert.Fail("Containers exist");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async void PatchTest()
|
||||
{
|
||||
try
|
||||
using var client = FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.keyString, this.url));
|
||||
|
||||
await Cleanup(client);
|
||||
|
||||
var createContainerParam = new PrmContainerCreate(
|
||||
new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1)), [new("testKey", "testValue")]));
|
||||
|
||||
var createdContainer = await client.CreateContainerAsync(createContainerParam);
|
||||
|
||||
var container = await client.GetContainerAsync(new PrmContainerGet(createdContainer));
|
||||
Assert.NotNull(container);
|
||||
|
||||
var bytes = new byte[1024];
|
||||
for (int i = 0; i < 1024; i++)
|
||||
{
|
||||
using var client = FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.keyString, this.url));
|
||||
|
||||
await Cleanup(client);
|
||||
|
||||
var createContainerParam = new PrmContainerCreate(
|
||||
new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1)), [new("testKey", "testValue")]));
|
||||
|
||||
var createdContainer = await client.CreateContainerAsync(createContainerParam);
|
||||
|
||||
var container = await client.GetContainerAsync(new PrmContainerGet(createdContainer));
|
||||
Assert.NotNull(container);
|
||||
|
||||
var bytes = new byte[4 * 1024];
|
||||
for (int i = 0; i < 4 * 1024; i++)
|
||||
{
|
||||
bytes[i] = (byte)31;
|
||||
}
|
||||
|
||||
var param = new PrmObjectPut
|
||||
{
|
||||
Header = new FrostFsObjectHeader(
|
||||
containerId: createdContainer,
|
||||
type: FrostFsObjectType.Regular,
|
||||
[new FrostFsAttributePair("fileName", "test")]),
|
||||
Payload = new MemoryStream(bytes),
|
||||
ClientCut = false
|
||||
};
|
||||
|
||||
var objectId = await client.PutObjectAsync(param);
|
||||
|
||||
var patch = new byte[1024];
|
||||
for (int i = 0; i < 1024; i++)
|
||||
{
|
||||
bytes[i] = (byte)32;
|
||||
}
|
||||
|
||||
var patchParams = new PrmObjectPatch(new FrostFsAddress(objectId, createdContainer))
|
||||
{
|
||||
Payload = new MemoryStream(patch),
|
||||
MaxPayloadPatchChunkLength = 1024,
|
||||
NewAttributes = [new FrostFsAttributePair("testKey", "testValue")],
|
||||
Range = new FrostFsRange() { Length = 1024, Offset = 10 },
|
||||
ReplaceAttributes = false
|
||||
};
|
||||
|
||||
await client.PatchObjectAsync(patchParams);
|
||||
|
||||
var @object = await client.GetObjectAsync(new PrmObjectGet(createdContainer, objectId));
|
||||
|
||||
var downloadedBytes = new byte[@object.Header.PayloadLength];
|
||||
MemoryStream ms = new(downloadedBytes);
|
||||
|
||||
ReadOnlyMemory<byte>? chunk = null;
|
||||
while ((chunk = await @object.ObjectReader!.ReadChunk()) != null)
|
||||
{
|
||||
ms.Write(chunk.Value.Span);
|
||||
}
|
||||
|
||||
Assert.Equal(SHA256.HashData(bytes), SHA256.HashData(downloadedBytes));
|
||||
|
||||
await Cleanup(client);
|
||||
|
||||
await foreach (var _ in client.ListContainersAsync())
|
||||
{
|
||||
Assert.Fail("Containers exist");
|
||||
}
|
||||
bytes[i] = (byte)31;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
var param = new PrmObjectPut
|
||||
{
|
||||
Header = new FrostFsObjectHeader(
|
||||
containerId: createdContainer,
|
||||
type: FrostFsObjectType.Regular,
|
||||
[new FrostFsAttributePair("fileName", "test")]),
|
||||
Payload = new MemoryStream(bytes),
|
||||
ClientCut = false
|
||||
};
|
||||
|
||||
var objectId = await client.PutObjectAsync(param);
|
||||
|
||||
var patch = new byte[16];
|
||||
for (int i = 0; i < 16; i++)
|
||||
{
|
||||
patch[i] = (byte)32;
|
||||
}
|
||||
|
||||
var range = new FrostFsRange(8, (ulong)patch.Length);
|
||||
|
||||
var patchParams = new PrmObjectPatch(new FrostFsAddress(createdContainer, objectId))
|
||||
{
|
||||
Payload = new MemoryStream(patch),
|
||||
MaxPayloadPatchChunkLength = 32,
|
||||
Range = range
|
||||
};
|
||||
|
||||
var newIbjId = await client.PatchObjectAsync(patchParams);
|
||||
|
||||
var @object = await client.GetObjectAsync(new PrmObjectGet(createdContainer, newIbjId));
|
||||
|
||||
var downloadedBytes = new byte[@object.Header.PayloadLength];
|
||||
MemoryStream ms = new(downloadedBytes);
|
||||
|
||||
ReadOnlyMemory<byte>? chunk = null;
|
||||
while ((chunk = await @object.ObjectReader!.ReadChunk()) != null)
|
||||
{
|
||||
ms.Write(chunk.Value.Span);
|
||||
}
|
||||
|
||||
for(int i = 0; i < (int)range.Offset; i++)
|
||||
Assert.Equal(downloadedBytes[i], bytes[i]);
|
||||
|
||||
var rangeEnd = range.Offset + range.Length;
|
||||
|
||||
for (int i = (int)range.Offset; i < (int)rangeEnd; i++)
|
||||
Assert.Equal(downloadedBytes[i], patch[i - (int)range.Offset]);
|
||||
|
||||
for (int i = (int)rangeEnd; i < bytes.Length; i++)
|
||||
Assert.Equal(downloadedBytes[i], bytes[i]);
|
||||
|
||||
await Cleanup(client);
|
||||
|
||||
await foreach (var _ in client.ListContainersAsync())
|
||||
{
|
||||
Assert.Fail("Containers exist");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -438,15 +432,14 @@ public class SmokeClientTests : SmokeTestsBase
|
|||
{
|
||||
Header = new FrostFsObjectHeader(
|
||||
containerId: createdContainer,
|
||||
type: FrostFsObjectType.Regular,
|
||||
[new FrostFsAttributePair("fileName", "test")]),
|
||||
type: FrostFsObjectType.Regular),
|
||||
Payload = new MemoryStream(bytes),
|
||||
ClientCut = false
|
||||
};
|
||||
|
||||
var objectId = await client.PutObjectAsync(param);
|
||||
|
||||
var rangeParam = new PrmRangeGet(createdContainer, objectId, new FrostFsRange { Offset = 100, Length = 64 });
|
||||
var rangeParam = new PrmRangeGet(createdContainer, objectId, new FrostFsRange(100, 64));
|
||||
|
||||
var rangeReader = await client.GetRangeAsync(rangeParam);
|
||||
|
||||
|
@ -469,6 +462,55 @@ public class SmokeClientTests : SmokeTestsBase
|
|||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async void RangeHashTest()
|
||||
{
|
||||
using var client = FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.keyString, this.url));
|
||||
|
||||
await Cleanup(client);
|
||||
|
||||
var createContainerParam = new PrmContainerCreate(
|
||||
new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1)), [new("testKey", "testValue")]));
|
||||
|
||||
var createdContainer = await client.CreateContainerAsync(createContainerParam);
|
||||
|
||||
var container = await client.GetContainerAsync(new PrmContainerGet(createdContainer));
|
||||
Assert.NotNull(container);
|
||||
|
||||
var bytes = new byte[256];
|
||||
for (int i = 0; i < 256; i++)
|
||||
{
|
||||
bytes[i] = (byte)i;
|
||||
}
|
||||
|
||||
var param = new PrmObjectPut
|
||||
{
|
||||
Header = new FrostFsObjectHeader(
|
||||
containerId: createdContainer,
|
||||
type: FrostFsObjectType.Regular),
|
||||
Payload = new MemoryStream(bytes),
|
||||
ClientCut = false
|
||||
};
|
||||
|
||||
var objectId = await client.PutObjectAsync(param);
|
||||
|
||||
var rangeParam = new PrmRangeHashGet(createdContainer, objectId, [ new FrostFsRange(100, 64)], bytes);
|
||||
|
||||
var hashes = await client.GetRangeHashAsync(rangeParam);
|
||||
|
||||
foreach (var hash in hashes)
|
||||
{
|
||||
var x = hash.Slice(0, 32).ToArray();
|
||||
}
|
||||
|
||||
await Cleanup(client);
|
||||
|
||||
await foreach (var _ in client.ListContainersAsync())
|
||||
{
|
||||
Assert.Fail("Containers exist");
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(1)]
|
||||
[InlineData(3 * 1024 * 1024)] // exactly one chunk size - 3MB
|
||||
|
|
Loading…
Reference in a new issue