1
0
mirror of https://github.com/qTox/qTox.git synced 2024-03-22 14:00:36 +08:00
qTox/test/persistence/dbupgrade/dbTo11_test.cpp
Anthony Bilinski 9a8706a65f
fix(history): Heal duplicate peer entries with different case
Prior to 2f4e8dc3e8 we would take
the written ToxID and insert that straight into history without
any case check

Must be done prior to schema 11 since even though the UNIQUE constraint
on the peers table is fooled by the different case, the UNIQUE
constraint on the new chats and authors table which are stored as BLOBS
fail during upgrade when the two different case but equal ToxPks
collide.

Unfortunately it can't be done as its own upgrade since 11 was already
merged, and this is a prerequisite for 11 to pass for some users.

Execute prior to starting the split peer upgrade instead of as a larger
transaction for simplicity of the split upgrade, and since executing
this deduplication is idempotent.
2022-05-22 11:15:59 -07:00

342 lines
15 KiB
C++

/*
Copyright © 2022 by The qTox Project Contributors
This file is part of qTox, a Qt-based graphical interface for Tox.
qTox is libre software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
qTox is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with qTox. If not, see <http://www.gnu.org/licenses/>.
*/
#include "src/core/toxpk.h"
#include "src/persistence/db/rawdatabase.h"
#include "src/persistence/db/upgrades/dbto11.h"
#include "dbutility/dbutility.h"
#include <QByteArray>
#include <QTemporaryFile>
#include <QTest>
namespace
{
const auto selfPk = ToxPk{QByteArray(32, 0)};
const auto aPk = ToxPk{QByteArray(32, '=')};
const auto bPk = ToxPk{QByteArray(32, 2)};
const auto cPk = ToxPk{QByteArray(32, 3)};
const auto selfName = QStringLiteral("Self");
const auto aName = QStringLiteral("Alice");
const auto bName = QStringLiteral("Bob");
const auto selfAliasId = 1;
const auto aPeerId = 2;
const auto aPeerDuplicateId = 5;
const auto aChatId = 3;
const auto aAliasId = 2;
const auto aAliasDuplicateId = 4;
const auto bPeerId = 3;
const auto bChatId = 1;
const auto bAliasId = 3;
const auto cPeerId = 4;
const auto cChatId = 2;
void appendAddPeersQueries(QVector<RawDatabase::Query>& setupQueries)
{
setupQueries.append(RawDatabase::Query{QStringLiteral(
"INSERT INTO peers (id, public_key) VALUES (1, ?)"),
{selfPk.toString().toUtf8()}});
setupQueries.append(RawDatabase::Query{QStringLiteral(
"INSERT INTO peers (id, public_key) VALUES (2, ?)"),
{aPk.toString().toUtf8()}});
setupQueries.append(RawDatabase::Query{QStringLiteral(
"INSERT INTO peers (id, public_key) VALUES (3, ?)"),
{bPk.toString().toUtf8()}});
setupQueries.append(RawDatabase::Query{QStringLiteral(
"INSERT INTO peers (id, public_key) VALUES (4, ?)"),
{cPk.toString().toUtf8()}});
setupQueries.append(RawDatabase::Query{QStringLiteral(
"INSERT INTO peers (id, public_key) VALUES (5, ?)"),
{aPk.toString().toLower().toUtf8()}});
}
void appendVerifyChatsQueries(QVector<RawDatabase::Query>& verifyQueries)
{
verifyQueries.append(RawDatabase::Query{QStringLiteral(
"SELECT COUNT(*) FROM chats"),
[&](const QVector<QVariant>& row) {
QVERIFY(row[0].toLongLong() == 3);
}});
struct Functor {
const std::vector<QByteArray> chatIds{aPk.getByteArray(), bPk.getByteArray(), cPk.getByteArray()};
void operator()(const QVector<QVariant>& row) {
QVERIFY(std::find(chatIds.begin(), chatIds.end(), row[0].toByteArray()) != chatIds.end());
}
};
verifyQueries.append(RawDatabase::Query{QStringLiteral(
"SELECT uuid FROM chats"),
Functor()});
}
void appendVerifyAuthorsQueries(QVector<RawDatabase::Query>& verifyQueries)
{
verifyQueries.append(RawDatabase::Query{QStringLiteral(
"SELECT COUNT(*) FROM authors"),
[&](const QVector<QVariant>& row) {
QVERIFY(row[0].toLongLong() == 3);
}});
struct Functor {
const std::vector<QByteArray> authorIds{selfPk.getByteArray(), aPk.getByteArray(), bPk.getByteArray()};
void operator()(const QVector<QVariant>& row) {
QVERIFY(std::find(authorIds.begin(), authorIds.end(), row[0].toByteArray()) != authorIds.end());
}
};
verifyQueries.append(RawDatabase::Query{QStringLiteral(
"SELECT public_key FROM authors"),
Functor()});
}
void appendAddAliasesQueries(QVector<RawDatabase::Query>& setupQueries)
{
setupQueries.append(RawDatabase::Query{QStringLiteral(
"INSERT INTO aliases (id, owner, display_name) VALUES (1, 1, ?)"),
{selfName.toUtf8()}});
setupQueries.append(RawDatabase::Query{QStringLiteral(
"INSERT INTO aliases (id, owner, display_name) VALUES (2, 2, ?)"),
{aName.toUtf8()}});
setupQueries.append(RawDatabase::Query{QStringLiteral(
"INSERT INTO aliases (id, owner, display_name) VALUES (3, 3, ?)"),
{bName.toUtf8()}});
setupQueries.append(RawDatabase::Query{QStringLiteral(
"INSERT INTO aliases (id, owner, display_name) VALUES (4, 5, ?)"),
{aName.toUtf8()}});
}
void appendVerifyAliasesQueries(QVector<RawDatabase::Query>& verifyQueries)
{
verifyQueries.append(RawDatabase::Query{QStringLiteral(
"SELECT COUNT(*) FROM aliases"),
[&](const QVector<QVariant>& row) {
QVERIFY(row[0].toLongLong() == 3);
}});
struct Functor {
const std::vector<QByteArray> names{selfName.toUtf8(), aName.toUtf8(), bName.toUtf8()};
void operator()(const QVector<QVariant>& row) {
QVERIFY(std::find(names.begin(), names.end(), row[0].toByteArray()) != names.end());
}
};
verifyQueries.append(RawDatabase::Query{QStringLiteral(
"SELECT display_name FROM aliases"),
Functor()});
}
void appendAddAChatMessagesQueries(QVector<RawDatabase::Query>& setupQueries)
{
setupQueries.append(RawDatabase::Query{QStringLiteral(
"INSERT INTO history (id, message_type, timestamp, chat_id) VALUES (1, 'T', 0, '%1')").arg(aPeerId)});
setupQueries.append(RawDatabase::Query{QStringLiteral(
"INSERT INTO text_messages (id, message_type, sender_alias, message) VALUES (1, 'T', '%1', ?)").arg(aAliasId),
{QStringLiteral("Message 1 from A to Self").toUtf8()}});
setupQueries.append(RawDatabase::Query{QStringLiteral(
"INSERT INTO history (id, message_type, timestamp, chat_id) VALUES (2, 'T', 0, '%1')").arg(aPeerId)});
setupQueries.append(RawDatabase::Query{QStringLiteral(
"INSERT INTO text_messages (id, message_type, sender_alias, message) VALUES (2, 'T', '%1', ?)").arg(aAliasDuplicateId),
{QStringLiteral("Message 2 from A to Self").toUtf8()}});
setupQueries.append(RawDatabase::Query{QStringLiteral(
"INSERT INTO history (id, message_type, timestamp, chat_id) VALUES (10, 'F', 0, '%1')").arg(aPeerDuplicateId)});
setupQueries.append(RawDatabase::Query{QStringLiteral(
"INSERT INTO file_transfers (id, message_type, sender_alias, file_restart_id, "
"file_name, file_path, file_hash, file_size, direction, file_state) "
"VALUES(10, 'F', '%1', ?, 'dummy name', 'dummy/path', ?, 1024, 1, 5)").arg(aAliasId),
{QByteArray(32, 1), QByteArray(32, 2)}});
}
void appendVerifyAChatMessagesQueries(QVector<RawDatabase::Query>& verifyQueries)
{
verifyQueries.append(RawDatabase::Query{QStringLiteral(
"SELECT COUNT(*) FROM history WHERE chat_id = '%1'").arg(aChatId),
[&](const QVector<QVariant>& row) {
QVERIFY(row[0].toLongLong() == 3);
}});
verifyQueries.append(RawDatabase::Query{QStringLiteral(
"SELECT COUNT(*) FROM text_messages WHERE sender_alias = '%1'").arg(aAliasId),
[&](const QVector<QVariant>& row) {
QVERIFY(row[0].toLongLong() == 2);
}});
verifyQueries.append(RawDatabase::Query{QStringLiteral(
"SELECT COUNT(*) FROM file_transfers WHERE sender_alias = '%1'").arg(aAliasId),
[&](const QVector<QVariant>& row) {
QVERIFY(row[0].toLongLong() == 1);
}});
}
void appendAddBChatMessagesQueries(QVector<RawDatabase::Query>& setupQueries)
{
setupQueries.append(RawDatabase::Query{QStringLiteral(
"INSERT INTO history (id, message_type, timestamp, chat_id) VALUES (3, 'T', 0, '%1')").arg(bPeerId)});
setupQueries.append(RawDatabase::Query{QStringLiteral(
"INSERT INTO text_messages (id, message_type, sender_alias, message) VALUES (3, 'T', '%1', ?)").arg(bAliasId),
{QStringLiteral("Message 1 from B to Self").toUtf8()}});
setupQueries.append(RawDatabase::Query{QStringLiteral(
"INSERT INTO history (id, message_type, timestamp, chat_id) VALUES (4, 'T', 0, '%1')").arg(bPeerId)});
setupQueries.append(RawDatabase::Query{QStringLiteral(
"INSERT INTO text_messages (id, message_type, sender_alias, message) VALUES (4, 'T', '%1', ?)").arg(selfAliasId),
{QStringLiteral("Message 1 from Self to B").toUtf8()}});
setupQueries.append(RawDatabase::Query{QStringLiteral(
"INSERT INTO history (id, message_type, timestamp, chat_id) VALUES (5, 'T', 0, '%1')").arg(bPeerId)});
setupQueries.append(RawDatabase::Query{QStringLiteral(
"INSERT INTO text_messages (id, message_type, sender_alias, message) VALUES (5, 'T', '%1', ?)").arg(selfAliasId),
{QStringLiteral("Pending message 1 from Self to B").toUtf8()}});
setupQueries.append(RawDatabase::Query{QStringLiteral(
"INSERT INTO faux_offline_pending (id) VALUES (5)")});
setupQueries.append(RawDatabase::Query{QStringLiteral(
"INSERT INTO history (id, message_type, timestamp, chat_id) VALUES (8, 'S', 0, '%1')").arg(bPeerId)});
setupQueries.append(RawDatabase::Query{QStringLiteral(
"INSERT INTO system_messages (id, message_type, system_message_type) VALUES (8, 'S', 1)")});
setupQueries.append(RawDatabase::Query{QStringLiteral(
"INSERT INTO history (id, message_type, timestamp, chat_id) VALUES (9, 'F', 0, '%1')").arg(bPeerId)});
setupQueries.append(RawDatabase::Query{QStringLiteral(
"INSERT INTO file_transfers (id, message_type, sender_alias, file_restart_id, "
"file_name, file_path, file_hash, file_size, direction, file_state) "
"VALUES(9, 'F', '%1', ?, 'dummy name', 'dummy/path', ?, 1024, 0, 5)").arg(selfAliasId),
{QByteArray(32, 1), QByteArray(32, 2)}});
}
void appendVerifyBChatMessagesQueries(QVector<RawDatabase::Query>& verifyQueries)
{
verifyQueries.append(RawDatabase::Query{QStringLiteral(
"SELECT COUNT(*) FROM history WHERE chat_id = '%1'").arg(bChatId),
[&](const QVector<QVariant>& row) {
QVERIFY(row[0].toLongLong() == 5);
}});
verifyQueries.append(RawDatabase::Query{QStringLiteral(
"SELECT COUNT(*) FROM history JOIN text_messages ON "
"history.id = text_messages.id WHERE chat_id = '%1'").arg(bChatId),
[&](const QVector<QVariant>& row) {
QVERIFY(row[0].toLongLong() == 3);
}});
verifyQueries.append(RawDatabase::Query{QStringLiteral(
"SELECT COUNT(*) FROM history JOIN faux_offline_pending ON "
"history.id = faux_offline_pending.id WHERE chat_id = '%1'").arg(bChatId),
[&](const QVector<QVariant>& row) {
QVERIFY(row[0].toLongLong() == 1);
}});
verifyQueries.append(RawDatabase::Query{QStringLiteral(
"SELECT COUNT(*) FROM file_transfers WHERE sender_alias = '%1'").arg(selfAliasId),
[&](const QVector<QVariant>& row) {
QVERIFY(row[0].toLongLong() == 1);
}});
verifyQueries.append(RawDatabase::Query{QStringLiteral(
"SELECT COUNT(*) FROM history JOIN system_messages ON "
"history.id = system_messages.id WHERE chat_id = '%1'").arg(bChatId),
[&](const QVector<QVariant>& row) {
QVERIFY(row[0].toLongLong() == 1);
}});
}
void appendAddCChatMessagesQueries(QVector<RawDatabase::Query>& setupQueries)
{
setupQueries.append(RawDatabase::Query{QStringLiteral(
"INSERT INTO history (id, message_type, timestamp, chat_id) VALUES (6, 'T', 0, '%1')").arg(cPeerId)});
setupQueries.append(RawDatabase::Query{QStringLiteral(
"INSERT INTO text_messages (id, message_type, sender_alias, message) VALUES (6, 'T', '%1', ?)").arg(selfAliasId),
{QStringLiteral("Message 1 from Self to B").toUtf8()}});
setupQueries.append(RawDatabase::Query{QStringLiteral(
"INSERT INTO broken_messages (id) VALUES (6)")});
setupQueries.append(RawDatabase::Query{QStringLiteral(
"INSERT INTO history (id, message_type, timestamp, chat_id) VALUES (7, 'T', 0, '%1')").arg(cPeerId)});
setupQueries.append(RawDatabase::Query{QStringLiteral(
"INSERT INTO text_messages (id, message_type, sender_alias, message) VALUES (7, 'T', '%1', ?)").arg(selfAliasId),
{QStringLiteral("Message 1 from Self to B").toUtf8()}});
}
void appendVerifyCChatMessagesQueries(QVector<RawDatabase::Query>& verifyQueries)
{
verifyQueries.append(RawDatabase::Query{QStringLiteral(
"SELECT COUNT(*) FROM history WHERE chat_id = '%1'").arg(cChatId),
[&](const QVector<QVariant>& row) {
QVERIFY(row[0].toLongLong() == 2);
}});
verifyQueries.append(RawDatabase::Query{QStringLiteral(
"SELECT COUNT(*) FROM history JOIN broken_messages ON "
"history.id = broken_messages.id WHERE chat_id = '%1'").arg(cChatId),
[&](const QVector<QVariant>& row) {
QVERIFY(row[0].toLongLong() == 1);
}});
verifyQueries.append(RawDatabase::Query{QStringLiteral(
"SELECT COUNT(*) FROM history JOIN text_messages ON "
"history.id = text_messages.id WHERE chat_id = '%1'").arg(cChatId),
[&](const QVector<QVariant>& row) {
QVERIFY(row[0].toLongLong() == 2);
}});
}
void appendAddHistoryQueries(QVector<RawDatabase::Query>& setupQueries)
{
appendAddAChatMessagesQueries(setupQueries);
appendAddBChatMessagesQueries(setupQueries);
appendAddCChatMessagesQueries(setupQueries);
}
void appendVerifyHistoryQueries(QVector<RawDatabase::Query>& verifyQueries)
{
appendVerifyAChatMessagesQueries(verifyQueries);
appendVerifyBChatMessagesQueries(verifyQueries);
appendVerifyCChatMessagesQueries(verifyQueries);
}
} // namespace
class Test10to11 : public QObject
{
Q_OBJECT
private slots:
void test10to11();
private:
QTemporaryFile testDatabaseFile;
};
void Test10to11::test10to11()
{
QVERIFY(testDatabaseFile.open());
testDatabaseFile.close();
auto db = std::shared_ptr<RawDatabase>{new RawDatabase{testDatabaseFile.fileName(), {}, {}}};
QVERIFY(db->execNow(RawDatabase::Query{QStringLiteral("PRAGMA foreign_keys = ON;")}));
createSchemaAtVersion(db, DbUtility::schema10);
QVector<RawDatabase::Query> setupQueries;
appendAddPeersQueries(setupQueries);
appendAddAliasesQueries(setupQueries);
appendAddHistoryQueries(setupQueries);
QVERIFY(db->execNow(setupQueries));
QVERIFY(DbTo11::dbSchema10to11(*db));
verifyDb(db, DbUtility::schema11);
QVector<RawDatabase::Query> verifyQueries;
appendVerifyChatsQueries(verifyQueries);
appendVerifyAuthorsQueries(verifyQueries);
appendVerifyAliasesQueries(verifyQueries);
appendVerifyHistoryQueries(verifyQueries);
QVERIFY(db->execNow(verifyQueries));
}
QTEST_GUILESS_MAIN(Test10to11)
#include "dbTo11_test.moc"