using System.Collections.ObjectModel; using System.Security.Cryptography; using FrostFS.Object; using FrostFS.SDK.Client; using FrostFS.SDK.Client.Mappers.GRPC; using Google.Protobuf; using Grpc.Core; using Moq; 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? ResultObjectIds { get; } = []; public ClientStreamWriter? ClientStreamWriter { get; } = new(); public PatchStreamWriter? PatchStreamWriter { get; } = new(); public Collection PutSingleRequests { get; } = []; public Collection DeleteRequests { get; } = []; public Collection HeadRequests { get; } = []; public byte[] RangeResponse { get; set; } = []; public GetRangeRequest? GetRangeRequest { get; set; } public GetRangeHashRequest? GetRangeHashRequest { get; set; } public Collection RangeHashResponses { get; } = []; public Action? Callback; public override Mock GetMock() { var mock = new Mock(); if (ObjectHeader != null) { 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(StringKey, ObjectHeader), Task.FromResult(ResponseMetaData), () => new Grpc.Core.Status(StatusCode.OK, string.Empty), () => ResponseMetaData, () => { }); }); HeadResponse ??= new Header { CreationEpoch = 99, ContainerId = ObjectHeader.ContainerId.ToMessage(), ObjectType = ObjectType.Regular, OwnerId = ObjectHeader.OwnerId!.ToMessage(), PayloadLength = 1, PayloadHash = new Refs.Checksum { Type = Refs.ChecksumType.Sha256, Sum = ByteString.CopyFrom(SHA256.HashData([0xff])) }, Version = ObjectHeader.Version!.ToMessage() }; 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 = Key.PublicKeyProto, Sign = Key.ECDsaKey.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 (ResultObjectIds != null && ResultObjectIds.Count > 0) { PutResponse putResponse = new() { Body = new PutResponse.Types.Body { ObjectId = new Refs.ObjectID { Value = ByteString.CopyFrom(ResultObjectIds.ElementAt(0)) } }, 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) => { Callback?.Invoke(); Verifier.CheckRequest(r); var req = r.Clone(); // Clone method does not clone the payload but keeps a reference req.Body.Object.Payload = ByteString.CopyFrom(r.Body.Object.Payload.ToByteArray()); PutSingleRequests.Add(req); 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.ToMessage(), ObjectId = ObjectId.ToMessage() } }, 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, () => { }); }); } mock.Setup(x => x.GetRange( It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns((GetRangeRequest r, Metadata m, DateTime? dt, CancellationToken ct) => { Verifier.CheckRequest(r); GetRangeRequest = r; return new AsyncServerStreamingCall( new AsyncStreamRangeReaderMock(StringKey, RangeResponse), Task.FromResult(ResponseMetaData), () => new Grpc.Core.Status(StatusCode.OK, string.Empty), () => ResponseMetaData, () => { }); }); mock.Setup(x => x.GetRangeHashAsync( It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .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( Task.FromResult(response), Task.FromResult(ResponseMetaData), () => new Grpc.Core.Status(StatusCode.OK, string.Empty), () => ResponseMetaData, () => { }); }); mock.Setup(x => x.Patch( It.IsAny(), It.IsAny(), It.IsAny())) .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( PatchStreamWriter!, Task.FromResult(patchResponse), Task.FromResult(ResponseMetaData), () => new Grpc.Core.Status(StatusCode.OK, string.Empty), () => ResponseMetaData, () => { }); }); return mock; } }