diff options
| author | Stefan Boberg <[email protected]> | 2025-09-29 13:15:16 +0200 |
|---|---|---|
| committer | GitHub Enterprise <[email protected]> | 2025-09-29 13:15:16 +0200 |
| commit | d4c6e547a7081b1562a69dc9839d24cb82681c5d (patch) | |
| tree | 3ffe43dcf09bb6d01c2fb860bb1f73882f44827d /src/zenserver-test/workspace-tests.cpp | |
| parent | gracefully handle missing chunks when exporting an oplog (#526) (diff) | |
| download | zen-d4c6e547a7081b1562a69dc9839d24cb82681c5d.tar.xz zen-d4c6e547a7081b1562a69dc9839d24cb82681c5d.zip | |
split zenserver-test monolith into multiple source files (#528)
Diffstat (limited to 'src/zenserver-test/workspace-tests.cpp')
| -rw-r--r-- | src/zenserver-test/workspace-tests.cpp | 541 |
1 files changed, 541 insertions, 0 deletions
diff --git a/src/zenserver-test/workspace-tests.cpp b/src/zenserver-test/workspace-tests.cpp new file mode 100644 index 000000000..f299b6dcf --- /dev/null +++ b/src/zenserver-test/workspace-tests.cpp @@ -0,0 +1,541 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#if ZEN_WITH_TESTS +# include "zenserver-test.h" +# include <zencore/testing.h> +# include <zencore/testutils.h> +# include <zencore/workthreadpool.h> +# include <zencore/compactbinarybuilder.h> +# include <zencore/compactbinarypackage.h> +# include <zencore/compress.h> +# include <zencore/fmtutils.h> +# include <zencore/filesystem.h> +# include <zencore/stream.h> +# include <zencore/string.h> +# include <zenutil/zenserverprocess.h> +# include <zenutil/chunkrequests.h> +# include <zenhttp/httpclient.h> + +namespace zen::tests { + +using namespace std::literals; + +std::vector<std::pair<std::filesystem::path, IoBuffer>> +GenerateFolderContent(const std::filesystem::path& RootPath) +{ + CreateDirectories(RootPath); + std::vector<std::pair<std::filesystem::path, IoBuffer>> Result; + Result.push_back(std::make_pair(RootPath / "root_blob_1.bin", CreateRandomBlob(4122))); + Result.push_back(std::make_pair(RootPath / "root_blob_2.bin", CreateRandomBlob(2122))); + + std::filesystem::path EmptyFolder(RootPath / "empty_folder"); + + std::filesystem::path FirstFolder(RootPath / "first_folder"); + CreateDirectories(FirstFolder); + Result.push_back(std::make_pair(FirstFolder / "first_folder_blob1.bin", CreateRandomBlob(22))); + Result.push_back(std::make_pair(FirstFolder / "first_folder_blob2.bin", CreateRandomBlob(122))); + + std::filesystem::path SecondFolder(RootPath / "second_folder"); + CreateDirectories(SecondFolder); + Result.push_back(std::make_pair(SecondFolder / "second_folder_blob1.bin", CreateRandomBlob(522))); + Result.push_back(std::make_pair(SecondFolder / "second_folder_blob2.bin", CreateRandomBlob(122))); + Result.push_back(std::make_pair(SecondFolder / "second_folder_blob3.bin", CreateRandomBlob(225))); + + std::filesystem::path SecondFolderChild(SecondFolder / "child_in_second"); + CreateDirectories(SecondFolderChild); + Result.push_back(std::make_pair(SecondFolderChild / "second_child_folder_blob1.bin", CreateRandomBlob(622))); + + for (const auto& It : Result) + { + WriteFile(It.first, It.second); + } + + return Result; +} + +std::vector<std::pair<std::filesystem::path, IoBuffer>> +GenerateFolderContent2(const std::filesystem::path& RootPath) +{ + std::vector<std::pair<std::filesystem::path, IoBuffer>> Result; + Result.push_back(std::make_pair(RootPath / "root_blob_3.bin", CreateRandomBlob(312))); + std::filesystem::path FirstFolder(RootPath / "first_folder"); + Result.push_back(std::make_pair(FirstFolder / "first_folder_blob3.bin", CreateRandomBlob(722))); + std::filesystem::path SecondFolder(RootPath / "second_folder"); + std::filesystem::path SecondFolderChild(SecondFolder / "child_in_second"); + Result.push_back(std::make_pair(SecondFolderChild / "second_child_folder_blob2.bin", CreateRandomBlob(962))); + Result.push_back(std::make_pair(SecondFolderChild / "second_child_folder_blob3.bin", CreateRandomBlob(561))); + + for (const auto& It : Result) + { + WriteFile(It.first, It.second); + } + + return Result; +} + +TEST_CASE("workspaces.create") +{ + using namespace std::literals; + + std::filesystem::path SystemRootPath = TestEnv.CreateNewTestDir(); + + std::filesystem::path TestDir = TestEnv.CreateNewTestDir(); + ZenServerInstance Instance(TestEnv); + Instance.SetTestDir(TestDir); + const uint16_t PortNumber = Instance.SpawnServerAndWaitUntilReady( + fmt::format("--workspaces-enabled --workspaces-allow-changes --system-dir {}", SystemRootPath)); + CHECK(PortNumber != 0); + + ScopedTemporaryDirectory TempDir; + std::filesystem::path Root1Path = TempDir.Path() / "root1"; + std::filesystem::path Root2Path = TempDir.Path() / "root2"; + DeleteDirectories(Root1Path); + DeleteDirectories(Root2Path); + + std::filesystem::path Share1Path = "shared_1"; + std::filesystem::path Share2Path = "shared_2"; + CreateDirectories(Root1Path / Share1Path); + CreateDirectories(Root1Path / Share2Path); + CreateDirectories(Root2Path / Share1Path); + CreateDirectories(Root2Path / Share2Path); + + Oid Root1Id = Oid::Zero; + Oid Root2Id = Oid::NewOid(); + + HttpClient Client(Instance.GetBaseUri()); + + CHECK(Client.Put(fmt::format("/ws/{}", Root1Id)).StatusCode == HttpResponseCode::BadRequest); + + if (HttpClient::Response Root1Response = + Client.Put(fmt::format("/ws/{}", Oid::Zero), HttpClient::KeyValueMap{{"root_path", Root1Path.string()}}); + Root1Response.StatusCode == HttpResponseCode::Created) + { + Root1Id = Oid::TryFromHexString(Root1Response.AsText()); + CHECK(Root1Id != Oid::Zero); + } + else + { + CHECK(false); + } + if (HttpClient::Response Root1Response = + Client.Put(fmt::format("/ws/{}", Oid::Zero), HttpClient::KeyValueMap{{"root_path", Root1Path.string()}}); + Root1Response.StatusCode == HttpResponseCode::OK) + { + CHECK(Root1Id == Oid::TryFromHexString(Root1Response.AsText())); + } + else + { + CHECK(false); + } + if (HttpClient::Response Root1Response = + Client.Put(fmt::format("/ws/{}", Root1Id), HttpClient::KeyValueMap{{"root_path", Root1Path.string()}}); + Root1Response.StatusCode == HttpResponseCode::OK) + { + CHECK(Root1Id == Oid::TryFromHexString(Root1Response.AsText())); + } + else + { + CHECK(false); + } + CHECK(Client.Put(fmt::format("/ws/{}", Root1Id), HttpClient::KeyValueMap{{"root_path", Root2Path.string()}}).StatusCode == + HttpResponseCode::Conflict); + + CHECK( + Client.Put(fmt::format("/ws/{}/{}", Root1Id, Oid::Zero), HttpClient::KeyValueMap{{"share_path", Share2Path.string()}}).StatusCode == + HttpResponseCode::Created); + + CHECK( + Client.Put(fmt::format("/ws/{}/{}", Root2Id, Oid::Zero), HttpClient::KeyValueMap{{"share_path", Share2Path.string()}}).StatusCode == + HttpResponseCode::NotFound); + + CHECK(Client.Put(fmt::format("/ws/{}", Root2Id), HttpClient::KeyValueMap{{"root_path", Root1Path.string()}}).StatusCode == + HttpResponseCode::Conflict); + + if (HttpClient::Response Root2Response = + Client.Put(fmt::format("/ws/{}", Root2Id), HttpClient::KeyValueMap{{"root_path", Root2Path.string()}}); + Root2Response.StatusCode == HttpResponseCode::Created) + { + CHECK(Root2Id == Oid::TryFromHexString(Root2Response.AsText())); + } + else + { + CHECK(false); + } + + CHECK(Client.Put(fmt::format("/ws/{}/{}", Root2Id, Oid::Zero)).StatusCode == HttpResponseCode::BadRequest); + + Oid Share2Id = Oid::Zero; + if (HttpClient::Response Share2Response = + Client.Put(fmt::format("/ws/{}/{}", Root2Id, Share2Id), HttpClient::KeyValueMap{{"share_path", Share2Path.string()}}); + Share2Response.StatusCode == HttpResponseCode::Created) + { + Share2Id = Oid::TryFromHexString(Share2Response.AsText()); + CHECK(Share2Id != Oid::Zero); + } + else + { + CHECK(false); + } + + CHECK( + Client.Put(fmt::format("/ws/{}/{}", Root2Id, Oid::Zero), HttpClient::KeyValueMap{{"share_path", Share2Path.string()}}).StatusCode == + HttpResponseCode::OK); + + CHECK( + Client.Put(fmt::format("/ws/{}/{}", Root2Id, Share2Id), HttpClient::KeyValueMap{{"share_path", Share2Path.string()}}).StatusCode == + HttpResponseCode::OK); + + CHECK( + Client.Put(fmt::format("/ws/{}/{}", Root2Id, Share2Id), HttpClient::KeyValueMap{{"share_path", Share1Path.string()}}).StatusCode == + HttpResponseCode::Conflict); + + CHECK(Client.Put(fmt::format("/ws/{}/{}", Root2Id, Oid::NewOid()), HttpClient::KeyValueMap{{"share_path", Share2Path.string()}}) + .StatusCode == HttpResponseCode::Conflict); + + CHECK(Client.Put(fmt::format("/ws/{}/{}", Root2Id, Oid::Zero), HttpClient::KeyValueMap{{"share_path", "idonotexist"}}).StatusCode != + HttpResponseCode::OK); + + while (true) + { + std::error_code Ec; + DeleteDirectories(Root2Path / Share2Path, Ec); + if (!Ec) + break; + } + + CHECK(Client.Get(fmt::format("/ws/{}/{}/files", Root2Id, Share2Id)).StatusCode == HttpResponseCode::NotFound); +} + +TEST_CASE("workspaces.restricted") +{ + using namespace std::literals; + + std::filesystem::path SystemRootPath = TestEnv.CreateNewTestDir(); + + std::filesystem::path TestDir = TestEnv.CreateNewTestDir(); + ZenServerInstance Instance(TestEnv); + Instance.SetTestDir(TestDir); + const uint16_t PortNumber = Instance.SpawnServerAndWaitUntilReady(fmt::format("--workspaces-enabled --system-dir {}", SystemRootPath)); + CHECK(PortNumber != 0); + + ScopedTemporaryDirectory TempDir; + std::filesystem::path Root1Path = TempDir.Path() / "root1"; + std::filesystem::path Root2Path = TempDir.Path() / "root2"; + DeleteDirectories(Root1Path); + DeleteDirectories(Root2Path); + + std::filesystem::path Share1Path = "shared_1"; + std::filesystem::path Share2Path = "shared_2"; + CreateDirectories(Root1Path / Share1Path); + CreateDirectories(Root1Path / Share2Path); + CreateDirectories(Root2Path / Share1Path); + CreateDirectories(Root2Path / Share2Path); + + Oid Root1Id = Oid::NewOid(); + Oid Root2Id = Oid::NewOid(); + Oid Share1Id = Oid::NewOid(); + Oid Share2Id = Oid::NewOid(); + + HttpClient Client(Instance.GetBaseUri()); + CHECK(Client.Put(fmt::format("/ws/{}", Oid::Zero), HttpClient::KeyValueMap{{"root_path", Root1Path.string()}}).StatusCode == + HttpResponseCode::Unauthorized); + + CHECK_EQ(Client.Get(fmt::format("/ws/{}", Root1Id)).StatusCode, HttpResponseCode::NotFound); + + std::string Config1; + { + CbObjectWriter Config; + Config.BeginArray("workspaces"); + Config.BeginObject(); + Config << "id"sv << Root1Id.ToString(); + Config << "root_path"sv << Root1Path.string(); + Config << "allow_share_creation_from_http"sv << false; + Config.EndObject(); + Config.EndArray(); + ExtendableStringBuilder<256> SB; + CompactBinaryToJson(Config.Save(), SB); + Config1 = SB.ToString(); + } + WriteFile(SystemRootPath / "workspaces" / "config.json", IoBuffer(IoBuffer::Wrap, Config1.data(), Config1.size())); + + CHECK(IsHttpSuccessCode(Client.Get("/ws/refresh").StatusCode)); + + CHECK_EQ(Client.Get(fmt::format("/ws/{}", Root1Id)).StatusCode, HttpResponseCode::OK); + + CHECK(Client.Get(fmt::format("/ws/{}/{}", Root1Id, Share1Id)).StatusCode == HttpResponseCode::NotFound); + CHECK( + Client.Put(fmt::format("/ws/{}/{}", Root1Id, Oid::Zero), HttpClient::KeyValueMap{{"share_path", Share1Path.string()}}).StatusCode == + HttpResponseCode::Unauthorized); + + std::string Config2; + { + CbObjectWriter Config; + Config.BeginArray("workspaces"); + Config.BeginObject(); + Config << "id"sv << Root1Id.ToString(); + Config << "root_path"sv << Root1Path.string(); + Config << "allow_share_creation_from_http"sv << false; + Config.EndObject(); + Config.BeginObject(); + Config << "id"sv << Root2Id.ToString(); + Config << "root_path"sv << Root2Path.string(); + Config << "allow_share_creation_from_http"sv << true; + Config.EndObject(); + Config.EndArray(); + ExtendableStringBuilder<256> SB; + CompactBinaryToJson(Config.Save(), SB); + Config2 = SB.ToString(); + } + WriteFile(SystemRootPath / "workspaces" / "config.json", IoBuffer(IoBuffer::Wrap, Config2.data(), Config2.size())); + + CHECK(IsHttpSuccessCode(Client.Get("/ws/refresh").StatusCode)); + + CHECK_EQ(Client.Get(fmt::format("/ws/{}", Root2Id)).StatusCode, HttpResponseCode::OK); + + CHECK(Client.Get(fmt::format("/ws/{}/{}", Root2Id, Share2Id)).StatusCode == HttpResponseCode::NotFound); + CHECK( + Client.Put(fmt::format("/ws/{}/{}", Root2Id, Share2Id), HttpClient::KeyValueMap{{"share_path", Share2Path.string()}}).StatusCode == + HttpResponseCode::Created); + CHECK(Client.Get(fmt::format("/ws/{}/{}", Root2Id, Share2Id)).StatusCode == HttpResponseCode::OK); + + CHECK(IsHttpSuccessCode(Client.Delete(fmt::format("/ws/{}/{}", Root2Id, Share2Id)).StatusCode)); +} + +TEST_CASE("workspaces.lifetimes") +{ + using namespace std::literals; + + std::filesystem::path SystemRootPath = TestEnv.CreateNewTestDir(); + + Oid WorkspaceId = Oid::NewOid(); + Oid ShareId = Oid::NewOid(); + + ScopedTemporaryDirectory TempDir; + std::filesystem::path RootPath = TempDir.Path(); + DeleteDirectories(RootPath); + std::filesystem::path SharePath = RootPath / "shared_folder"; + CreateDirectories(SharePath); + + { + std::filesystem::path TestDir = TestEnv.CreateNewTestDir(); + ZenServerInstance Instance(TestEnv); + Instance.SetTestDir(TestDir); + const uint16_t PortNumber = Instance.SpawnServerAndWaitUntilReady( + fmt::format("--workspaces-enabled --workspaces-allow-changes --system-dir {}", SystemRootPath)); + CHECK(PortNumber != 0); + + HttpClient Client(Instance.GetBaseUri()); + CHECK(Client.Put(fmt::format("/ws/{}", WorkspaceId), HttpClient::KeyValueMap{{"root_path", RootPath.string()}}).StatusCode == + HttpResponseCode::Created); + CHECK(Client.Get(fmt::format("/ws/{}", WorkspaceId)).AsObject()["id"sv].AsObjectId() == WorkspaceId); + CHECK(Client.Put(fmt::format("/ws/{}", WorkspaceId), HttpClient::KeyValueMap{{"root_path", RootPath.string()}}).StatusCode == + HttpResponseCode::OK); + + CHECK(Client.Put(fmt::format("/ws/{}/{}", WorkspaceId, ShareId), HttpClient::KeyValueMap{{"share_path", "shared_folder"}}) + .StatusCode == HttpResponseCode::Created); + CHECK(Client.Get(fmt::format("/ws/{}/{}", WorkspaceId, ShareId)).AsObject()["id"sv].AsObjectId() == ShareId); + CHECK(Client.Put(fmt::format("/ws/{}/{}", WorkspaceId, ShareId), HttpClient::KeyValueMap{{"share_path", "shared_folder"}}) + .StatusCode == HttpResponseCode::OK); + } + + // Restart + + { + std::filesystem::path TestDir = TestEnv.CreateNewTestDir(); + ZenServerInstance Instance(TestEnv); + Instance.SetTestDir(TestDir); + const uint16_t PortNumber = + Instance.SpawnServerAndWaitUntilReady(fmt::format("--workspaces-enabled --system-dir {}", SystemRootPath)); + CHECK(PortNumber != 0); + + HttpClient Client(Instance.GetBaseUri()); + CHECK(Client.Get(fmt::format("/ws/{}", WorkspaceId)).AsObject()["id"sv].AsObjectId() == WorkspaceId); + + CHECK(Client.Get(fmt::format("/ws/{}/{}", WorkspaceId, ShareId)).AsObject()["id"sv].AsObjectId() == ShareId); + } + + // Wipe system config + DeleteDirectories(SystemRootPath); + + // Restart + + { + std::filesystem::path TestDir = TestEnv.CreateNewTestDir(); + ZenServerInstance Instance(TestEnv); + Instance.SetTestDir(TestDir); + const uint16_t PortNumber = + Instance.SpawnServerAndWaitUntilReady(fmt::format("--workspaces-enabled --system-dir {}", SystemRootPath)); + CHECK(PortNumber != 0); + + HttpClient Client(Instance.GetBaseUri()); + CHECK(Client.Get(fmt::format("/ws/{}", WorkspaceId)).StatusCode == HttpResponseCode::NotFound); + CHECK(Client.Get(fmt::format("/ws/{}/{}", WorkspaceId, ShareId)).StatusCode == HttpResponseCode::NotFound); + } +} + +TEST_CASE("workspaces.share") +{ + std::filesystem::path SystemRootPath = TestEnv.CreateNewTestDir(); + + ZenServerInstance Instance(TestEnv); + + const uint16_t PortNumber = Instance.SpawnServerAndWaitUntilReady( + fmt::format("--workspaces-enabled --workspaces-allow-changes --system-dir {}", SystemRootPath)); + CHECK(PortNumber != 0); + + ScopedTemporaryDirectory TempDir; + std::filesystem::path RootPath = TempDir.Path(); + DeleteDirectories(RootPath); + std::filesystem::path SharePath = RootPath / "shared_folder"; + GenerateFolderContent(SharePath); + + HttpClient Client(Instance.GetBaseUri()); + + Oid WorkspaceId = Oid::NewOid(); + CHECK(Client.Put(fmt::format("/ws/{}", WorkspaceId), HttpClient::KeyValueMap{{"root_path", RootPath.string()}}).StatusCode == + HttpResponseCode::Created); + CHECK(Client.Get(fmt::format("/ws/{}", WorkspaceId)).AsObject()["id"sv].AsObjectId() == WorkspaceId); + + Oid ShareId = Oid::NewOid(); + CHECK(Client.Put(fmt::format("/ws/{}/{}", WorkspaceId, ShareId), HttpClient::KeyValueMap{{"share_path", "shared_folder"}}).StatusCode == + HttpResponseCode::Created); + CHECK(Client.Get(fmt::format("/ws/{}/{}", WorkspaceId, ShareId)).AsObject()["id"sv].AsObjectId() == ShareId); + + CHECK(Client.Get(fmt::format("/ws/{}/{}/files", WorkspaceId, ShareId)).AsObject()["files"sv].AsArrayView().Num() == 8); + GenerateFolderContent2(SharePath); + CHECK(Client.Get(fmt::format("/ws/{}/{}/files", WorkspaceId, ShareId)).AsObject()["files"sv].AsArrayView().Num() == 8); + HttpClient::Response FilesResponse = + Client.Get(fmt::format("/ws/{}/{}/files", WorkspaceId, ShareId), + {}, + HttpClient::KeyValueMap{{"refresh", ToString(true)}, {"fieldnames", "id,clientpath,size"}}); + CHECK(FilesResponse); + std::unordered_map<Oid, std::pair<std::filesystem::path, uint64_t>, Oid::Hasher> Files; + { + CbArrayView FilesArray = FilesResponse.AsObject()["files"sv].AsArrayView(); + CHECK(FilesArray.Num() == 12); + for (CbFieldView Field : FilesArray) + { + CbObjectView FileObject = Field.AsObjectView(); + Oid ChunkId = FileObject["id"sv].AsObjectId(); + CHECK(ChunkId != Oid::Zero); + uint64_t Size = FileObject["size"sv].AsUInt64(); + std::u8string_view Path = FileObject["clientpath"sv].AsU8String(); + std::filesystem::path AbsFilePath = SharePath / Path; + CHECK(IsFile(AbsFilePath)); + CHECK(FileSizeFromPath(AbsFilePath) == Size); + Files.insert_or_assign(ChunkId, std::make_pair(AbsFilePath, Size)); + } + } + + HttpClient::Response EntriesResponse = + Client.Get(fmt::format("/ws/{}/{}/entries", WorkspaceId, ShareId), {}, HttpClient::KeyValueMap{{"fieldfilter", "id,clientpath"}}); + CHECK(EntriesResponse); + { + CbArrayView EntriesArray = EntriesResponse.AsObject()["entries"sv].AsArrayView(); + CHECK(EntriesArray.Num() == 1); + for (CbFieldView EntryField : EntriesArray) + { + CbObjectView EntryObject = EntryField.AsObjectView(); + CbArrayView FilesArray = EntryObject["files"sv].AsArrayView(); + CHECK(FilesArray.Num() == 12); + for (CbFieldView FileField : FilesArray) + { + CbObjectView FileObject = FileField.AsObjectView(); + Oid ChunkId = FileObject["id"sv].AsObjectId(); + CHECK(ChunkId != Oid::Zero); + std::u8string_view Path = FileObject["clientpath"sv].AsU8String(); + std::filesystem::path AbsFilePath = SharePath / Path; + CHECK(IsFile(AbsFilePath)); + } + } + } + + HttpClient::Response FileManifestResponse = + Client.Get(fmt::format("/ws/{}/{}/entries", WorkspaceId, ShareId), + {}, + HttpClient::KeyValueMap{{"opkey", "file_manifest"}, {"fieldfilter", "id,clientpath"}}); + CHECK(FileManifestResponse); + { + CbArrayView EntriesArray = FileManifestResponse.AsObject()["entry"sv].AsObjectView()["files"sv].AsArrayView(); + CHECK(EntriesArray.Num() == 12); + for (CbFieldView Field : EntriesArray) + { + CbObjectView FileObject = Field.AsObjectView(); + Oid ChunkId = FileObject["id"sv].AsObjectId(); + CHECK(ChunkId != Oid::Zero); + std::u8string_view Path = FileObject["clientpath"sv].AsU8String(); + std::filesystem::path AbsFilePath = SharePath / Path; + CHECK(IsFile(AbsFilePath)); + } + } + + for (auto It : Files) + { + const Oid& ChunkId = It.first; + const std::filesystem::path& Path = It.second.first; + const uint64_t Size = It.second.second; + + CHECK(Client.Get(fmt::format("/ws/{}/{}/{}/info", WorkspaceId, ShareId, ChunkId)).AsObject()["size"sv].AsUInt64() == Size); + + { + IoBuffer Payload = Client.Get(fmt::format("/ws/{}/{}/{}", WorkspaceId, ShareId, ChunkId)).ResponsePayload; + CHECK(Payload); + CHECK(Payload.GetSize() == Size); + IoBuffer FileContent = IoBufferBuilder::MakeFromFile(Path); + CHECK(FileContent); + CHECK(FileContent.GetView().EqualBytes(Payload.GetView())); + } + + { + IoBuffer Payload = + Client + .Get(fmt::format("/ws/{}/{}/{}", WorkspaceId, ShareId, ChunkId), + {}, + HttpClient::KeyValueMap{{"offset", fmt::format("{}", Size / 4)}, {"size", fmt::format("{}", Size / 2)}}) + .ResponsePayload; + CHECK(Payload); + CHECK(Payload.GetSize() == Size / 2); + IoBuffer FileContent = IoBufferBuilder::MakeFromFile(Path, Size / 4, Size / 2); + CHECK(FileContent); + CHECK(FileContent.GetView().EqualBytes(Payload.GetView())); + } + } + + { + uint32_t CorrelationId = gsl::narrow<uint32_t>(Files.size()); + std::vector<RequestChunkEntry> BatchEntries; + for (auto It : Files) + { + const Oid& ChunkId = It.first; + const uint64_t Size = It.second.second; + + BatchEntries.push_back( + RequestChunkEntry{.ChunkId = ChunkId, .CorrelationId = --CorrelationId, .Offset = Size / 4, .RequestBytes = Size / 2}); + } + IoBuffer BatchResponse = + Client.Post(fmt::format("/ws/{}/{}/batch", WorkspaceId, ShareId), BuildChunkBatchRequest(BatchEntries)).ResponsePayload; + CHECK(BatchResponse); + std::vector<IoBuffer> BatchResult = ParseChunkBatchResponse(BatchResponse); + CHECK(BatchResult.size() == Files.size()); + for (const RequestChunkEntry& Request : BatchEntries) + { + IoBuffer Result = BatchResult[Request.CorrelationId]; + auto It = Files.find(Request.ChunkId); + const std::filesystem::path& Path = It->second.first; + CHECK(Result.GetSize() == Request.RequestBytes); + IoBuffer FileContent = IoBufferBuilder::MakeFromFile(Path, Request.Offset, Request.RequestBytes); + CHECK(FileContent); + CHECK(FileContent.GetView().EqualBytes(Result.GetView())); + } + } + + CHECK(Client.Delete(fmt::format("/ws/{}/{}", WorkspaceId, ShareId))); + CHECK(Client.Get(fmt::format("/ws/{}/{}", WorkspaceId, ShareId)).StatusCode == HttpResponseCode::NotFound); + CHECK(Client.Get(fmt::format("/ws/{}", WorkspaceId))); + + CHECK(Client.Delete(fmt::format("/ws/{}", WorkspaceId))); + CHECK(Client.Get(fmt::format("/ws/{}", WorkspaceId)).StatusCode == HttpResponseCode::NotFound); +} + +} // namespace zen::tests +#endif |