aboutsummaryrefslogtreecommitdiffstats
path: root/tests/test_mainwindow.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'tests/test_mainwindow.cpp')
-rw-r--r--tests/test_mainwindow.cpp186
1 files changed, 185 insertions, 1 deletions
diff --git a/tests/test_mainwindow.cpp b/tests/test_mainwindow.cpp
index bbdad19..90cdbc2 100644
--- a/tests/test_mainwindow.cpp
+++ b/tests/test_mainwindow.cpp
@@ -247,6 +247,7 @@ private slots:
void init();
void cleanup();
void noTestCanSeeTheRealLockTable();
+ void noSyncTestReadsTheRealSyncState();
void everyKnownActionIsRegistered();
void everyRegisteredActionIsKnown();
void configuredBindingReachesTheAction();
@@ -429,6 +430,8 @@ private slots:
void aRejectedWriteKeepsEarlierUndoHistory();
void aSuccessfulCronSyncClearsThePendingCount();
+ void anExternalSyncClearsOnlyTheAccountsItCarried();
+ void aSkippedExternalSyncClearsNothing();
void aFailedCronSyncLeavesThePendingCount();
void anUnreadableSyncLogLeavesThePendingCount();
void anUnknownExternalStateClearsNothing();
@@ -624,6 +627,53 @@ void TestMainWindow::noTestCanSeeTheRealLockTable()
QVERIFY(MainWindow::locksPath().startsWith(QDir::tempPath()));
}
+void TestMainWindow::noSyncTestReadsTheRealSyncState()
+{
+ // The same guard as noTestCanSeeTheRealLockTable(), for the two paths a
+ // Config falls back to when a test does not name them, and it exists
+ // because that fallback bit twice in one sitting (item 174).
+ //
+ // A test writing "[sync]\nlog=..." and nothing else leaves syncStatus()
+ // pointing at the developer's real ~/.local/state/qtmaildir/syncstatus.json.
+ // Two tests asserting that a FAILED run leaves the pending count alone
+ // therefore read the last real cron run, found "ok", and passed against a
+ // broken clear. Pinning only the status key has the mirror problem: the log
+ // then defaults to the real mailsync.log.
+ //
+ // Asserted on Config rather than on any one test, so a new sync test that
+ // forgets one key fails here with a message naming the reason rather than
+ // failing mysteriously whenever the developer's last sync happened to
+ // succeed.
+ QTemporaryDir dir;
+ QVERIFY(dir.isValid());
+
+ const QString path = dir.filePath(QStringLiteral("qtmaildir.conf"));
+ QFile file(path);
+ QVERIFY(file.open(QIODevice::WriteOnly | QIODevice::Text));
+ file.write("[sync]\ncommand=/bin/true\n");
+ file.close();
+
+ Config config;
+ config.load(path);
+
+ // Both DO default to the real paths, which is correct for the application
+ // and is exactly the trap for a test. This documents the behaviour so the
+ // requirement below is obviously about the tests rather than the defaults.
+ QCOMPARE(config.syncLog(), MailSync::defaultLogPath());
+ QCOMPARE(config.syncStatus(), MailSync::defaultStatusPath());
+
+ QVERIFY2(MailSync::defaultStatusPath().contains(
+ QStringLiteral(".local/state/qtmaildir/syncstatus.json")),
+ "the default status path moved: assets/mailsync.sh writes the old "
+ "one, and the two must agree or every external sync reads as "
+ "Unknown");
+
+ // Any test asserting on what a sync did must name BOTH keys in its own
+ // config, pointing them inside its own QTemporaryDir. There is no fixture
+ // that can enforce it, since Config is loaded per test, so this is the
+ // reminder that fails loudly if the defaults ever stop being real paths.
+}
+
void TestMainWindow::everyKnownActionIsRegistered()
{
// KeyMap::knownActions() is what loadOverrides() validates config bindings
@@ -7015,7 +7065,17 @@ void loadConfigWithSyncLog(Config &config, const QTemporaryDir &dir,
const QString path = dir.filePath(QStringLiteral("qtmaildir.conf"));
QFile file(path);
QVERIFY(file.open(QIODevice::WriteOnly | QIODevice::Text));
- file.write(QStringLiteral("[sync]\nlog=%1\n").arg(logPath).toUtf8());
+ // The status file is pointed at this test's own directory even though these
+ // tests are about the LOG, and the omission cost two false greens: without
+ // it Config falls back to the real ~/.local/state/qtmaildir/syncstatus.json,
+ // so a test asserting that a FAILED log leaves the count alone read the
+ // developer's own last cron run, found "ok" and cleared. Same rule as the
+ // lock table: no test may observe the machine's real sync state. Pointing
+ // it at a file that does not exist makes readStatus() return Unknown, which
+ // is exactly the fallback-to-log case these tests mean to exercise.
+ file.write(QStringLiteral("[sync]\nlog=%1\nstatus=%2\n")
+ .arg(logPath, dir.filePath(QStringLiteral("no-status.json")))
+ .toUtf8());
file.close();
config.load(path);
@@ -7056,6 +7116,130 @@ void runExternalSync(MainWindow &window, SyncMonitor::State ending)
} // namespace
+/// Item 174. A run this process did not start now reports what it DID, in the
+/// status file assets/mailsync.sh writes, instead of being inferred from the
+/// log's RUN END banner.
+///
+/// The property that banner could never express: WHICH channels the run
+/// carried. The local sync path has always narrowed its clear to the accounts
+/// it carried (onSyncFinished's snapshot-and-subtract); the external path had
+/// no way to and cleared everything, so an edit to an account the run did not
+/// touch was reported as shipped when it had not been.
+void TestMainWindow::anExternalSyncClearsOnlyTheAccountsItCarried()
+{
+ QTemporaryDir dir;
+ QVERIFY(dir.isValid());
+
+ const QString statusPath = dir.filePath(QStringLiteral("syncstatus.json"));
+ QFile status(statusPath);
+ QVERIFY(status.open(QIODevice::WriteOnly));
+ // Timestamped NOW rather than with a fixed date: the status file is only
+ // read as this run's result when it is at least as new as the sync that
+ // just ended, so a fixture dated in the past is correctly ignored as stale
+ // and the test would exercise the log fallback instead.
+ const QString now =
+ QDateTime::currentDateTime().toString(Qt::ISODate);
+ // A run that carried ONE of the two accounts.
+ status.write(QStringLiteral(R"({"version": 1, "run_id": "r",
+ "started": "%1", "ended": "%1",
+ "state": "ok", "channels": ["work"],
+ "mbsync_status": 0, "notmuch_status": 0})")
+ .arg(now).toUtf8());
+ status.close();
+
+ const QString confPath = dir.filePath(QStringLiteral("qtmaildir.conf"));
+ QFile conf(confPath);
+ QVERIFY(conf.open(QIODevice::WriteOnly | QIODevice::Text));
+ // BOTH keys, always. Pinning only one leaves the other defaulting to the
+ // developer's real ~/.local/state file, and a test then reads their last
+ // cron run instead of its own fixture: that is how two tests in this group
+ // went green against a broken clear before this was noticed.
+ conf.write(QStringLiteral("[sync]\nstatus=%1\nlog=%2\n"
+ "[account.work]\nmaildir=work\ntrash=trash\n"
+ "[account.personal]\nmaildir=personal\ntrash=trash\n")
+ .arg(statusPath,
+ dir.filePath(QStringLiteral("no-log.log")))
+ .toUtf8());
+ conf.close();
+
+ Config config;
+ config.load(confPath);
+ QCOMPARE(config.syncStatus(), statusPath);
+
+ MainWindow window(config);
+ auto *label = window.findChild<QLabel *>(QStringLiteral("pendingEdits"));
+ QVERIFY(label);
+
+ // An edit on each account. Only the first is carried by the run above.
+ QVERIFY(QMetaObject::invokeMethod(&window, "noteEditedAccountForTesting",
+ Q_ARG(QString, QStringLiteral("work"))));
+ QVERIFY(QMetaObject::invokeMethod(&window, "noteEditedAccountForTesting",
+ Q_ARG(QString,
+ QStringLiteral("personal"))));
+ recordOneEdit(window, QStringLiteral("m1"), QStringLiteral("flagged"));
+ QVERIFY2(!label->isHidden(), "the edit was not counted at all");
+
+ runExternalSync(window, SyncMonitor::State::Idle);
+
+ // The account the run carried is gone; the one it did not is still waiting.
+ // Asserting only that something cleared would pass against the old blanket
+ // clear, which is the behaviour this replaces.
+ QVERIFY2(!window.editedAccountsForTesting().contains(
+ QStringLiteral("work")),
+ "the account the sync carried is still marked as edited");
+ QVERIFY2(window.editedAccountsForTesting().contains(
+ QStringLiteral("personal")),
+ "an account the sync never carried was cleared anyway, which is "
+ "the blanket clear this replaces");
+}
+
+/// Item 125, the half this closes. A run that SKIPPED because another held the
+/// lock did the work of neither: it must clear no edits, and before the status
+/// file there was nothing to tell the application it had happened at all.
+void TestMainWindow::aSkippedExternalSyncClearsNothing()
+{
+ QTemporaryDir dir;
+ QVERIFY(dir.isValid());
+
+ const QString statusPath = dir.filePath(QStringLiteral("syncstatus.json"));
+ QFile status(statusPath);
+ QVERIFY(status.open(QIODevice::WriteOnly));
+ // NOW, for the staleness reason the other test records.
+ const QString now =
+ QDateTime::currentDateTime().toString(Qt::ISODate);
+ status.write(QStringLiteral(R"({"version": 1, "run_id": "r",
+ "started": "%1", "ended": "%1",
+ "state": "skipped", "channels": ["-a"],
+ "mbsync_status": -1, "notmuch_status": -1})")
+ .arg(now).toUtf8());
+ status.close();
+
+ const QString confPath = dir.filePath(QStringLiteral("qtmaildir.conf"));
+ QFile conf(confPath);
+ QVERIFY(conf.open(QIODevice::WriteOnly | QIODevice::Text));
+ conf.write(QStringLiteral("[sync]\nstatus=%1\nlog=%2\n")
+ .arg(statusPath,
+ dir.filePath(QStringLiteral("no-log.log")))
+ .toUtf8());
+ conf.close();
+
+ Config config;
+ config.load(confPath);
+ MainWindow window(config);
+
+ auto *label = window.findChild<QLabel *>(QStringLiteral("pendingEdits"));
+ QVERIFY(label);
+
+ recordOneEdit(window, QStringLiteral("m1"), QStringLiteral("flagged"));
+ QVERIFY(!label->isHidden());
+
+ runExternalSync(window, SyncMonitor::State::Idle);
+
+ QVERIFY2(!label->isHidden(),
+ "a SKIPPED run cleared the pending count: it synced nothing, so "
+ "the edits are still only local");
+}
+
void TestMainWindow::aSuccessfulCronSyncClearsThePendingCount()
{
// The reported defect: edits applied, cron syncs, indicator still says N.