#include "client/network/update/card_spoiler/spoiler_file.h" #include "support/scoped_file_size_limit.h" #include #include #include #include #include namespace { class SpoilerFileTest : public ::testing::Test { protected: QTemporaryDir directory; QString path; QString error; const QByteArray original = ""; void SetUp() override { ASSERT_TRUE(directory.isValid()); path = directory.filePath("spoiler.xml"); QFile file(path); ASSERT_TRUE(file.open(QIODevice::WriteOnly)); ASSERT_EQ(file.write(original), original.size()); } QByteArray read() { QFile file(path); EXPECT_TRUE(file.open(QIODevice::ReadOnly)); return file.readAll(); } SpoilerFile::SaveResult save(const QByteArray &data) { return SpoilerFile::save(path, data, [] { return true; }, error); } }; TEST_F(SpoilerFileTest, InvalidDocumentsPreserveExistingFile) { for (const QByteArray &invalid : {QByteArray(), QByteArray(""), QByteArray(""), QByteArray(""), QByteArray(""), QByteArray(""), QByteArray("")}) { EXPECT_EQ(save(invalid), SpoilerFile::SaveResult::Failed); EXPECT_FALSE(error.isEmpty()); EXPECT_EQ(read(), original); } } TEST_F(SpoilerFileTest, SupportedVersionsAreAccepted) { EXPECT_TRUE(SpoilerFile::validate(original).isEmpty()); EXPECT_TRUE( SpoilerFile::validate("").isEmpty()); } TEST_F(SpoilerFileTest, IdenticalFileIsUnchanged) { EXPECT_EQ(save(original), SpoilerFile::SaveResult::Unchanged); EXPECT_TRUE(error.isEmpty()); EXPECT_EQ(read(), original); } TEST_F(SpoilerFileTest, CompleteFileHashDetectsChangesAfterLargeCommonPrefix) { QByteArray changed = ""; ASSERT_EQ(save(changed), SpoilerFile::SaveResult::Written); changed.replace("", ""); EXPECT_EQ(save(changed), SpoilerFile::SaveResult::Written); EXPECT_EQ(read(), changed); EXPECT_TRUE(error.isEmpty()); } TEST_F(SpoilerFileTest, CancellationBeforeWritePreservesExistingFile) { EXPECT_EQ(SpoilerFile::save(path, original, [] { return false; }, error), SpoilerFile::SaveResult::Failed); EXPECT_FALSE(error.isEmpty()); EXPECT_EQ(read(), original); } TEST_F(SpoilerFileTest, CancellationBeforeCommitPreservesExistingFile) { int checks = 0; EXPECT_EQ(SpoilerFile::save( path, original + "\n", [&checks] { return ++checks == 1; }, error), SpoilerFile::SaveResult::Failed); EXPECT_EQ(checks, 2); EXPECT_FALSE(error.isEmpty()); EXPECT_EQ(read(), original); EXPECT_EQ(QDir(directory.path()).entryList(QDir::Files), QStringList{"spoiler.xml"}); } TEST_F(SpoilerFileTest, MissingParentIsCreated) { path = directory.filePath("nested/spoiler.xml"); EXPECT_EQ(save(original), SpoilerFile::SaveResult::Written); EXPECT_EQ(read(), original); } TEST_F(SpoilerFileTest, FileInParentPathReportsFailureWithoutModifyingIt) { EXPECT_EQ(SpoilerFile::save( QDir(path).filePath("spoiler.xml"), original, [] { return true; }, error), SpoilerFile::SaveResult::Failed); EXPECT_FALSE(error.isEmpty()); EXPECT_EQ(read(), original); } #ifdef Q_OS_UNIX TEST_F(SpoilerFileTest, ShortWriteLeavesExistingFileIntact) { const QByteArray changed = ""; // A real short write exercises QSaveFile's buffered flush failure without depending // on disk capacity. The cap is scoped rather than forked, because a death-test child // under RLIMIT_FSIZE is killed by SIGSEGV on macOS. test_support::ScopedFileSizeLimit fileSizeCap(128); ASSERT_TRUE(fileSizeCap.isValid()); EXPECT_EQ(save(changed), SpoilerFile::SaveResult::Failed); EXPECT_FALSE(error.isEmpty()); EXPECT_EQ(read(), original); EXPECT_EQ(QDir(directory.path()).entryList(QDir::Files), QStringList{"spoiler.xml"}); } #endif } // namespace