diff options
Diffstat (limited to 'tests/notmuchfixture.h')
| -rw-r--r-- | tests/notmuchfixture.h | 114 |
1 files changed, 114 insertions, 0 deletions
diff --git a/tests/notmuchfixture.h b/tests/notmuchfixture.h new file mode 100644 index 0000000..b7287f4 --- /dev/null +++ b/tests/notmuchfixture.h @@ -0,0 +1,114 @@ +#pragma once + +#include <QDir> +#include <QFile> +#include <QProcess> +#include <QString> +#include <QTemporaryDir> +#include <QTextStream> + +/// A throwaway notmuch database in a temporary directory. +/// +/// Builds a Maildir tree, writes a notmuch config pointing at it, and runs +/// `notmuch new`. Nothing touches the developer's own ~/Mail or +/// ~/.notmuch-config: the config path is handed to the worker explicitly. +/// +/// Maildir flags are not decoration. notmuch synchronizes them with tags at +/// index time, so a file named `...:2,S` ("seen") comes out WITHOUT the unread +/// tag no matter what [new] tags says. addMessage() takes the unread state and +/// picks the filename accordingly. +class NotmuchFixture +{ +public: + /// True when the temporary tree was created. Check before use. + bool isValid() const { return m_dir.isValid(); } + + QString configPath() const { return m_dir.filePath(QStringLiteral("config")); } + QString maildirPath() const { return m_dir.filePath(QStringLiteral("mail")); } + + /// Writes one message into <folder>/cur (or new/ when unread). + /// + /// Returns false if the file could not be written. Call index() afterwards. + bool addMessage(const QString &folder, const QString &messageId, + const QString &subject, const QString &from, + const QString &date, const QString &body, + bool unread = true, const QString &inReplyTo = QString()) + { + // Unread messages must not carry the maildir "S" flag, so they go to + // new/ where no flags exist at all. + const QString sub = unread ? QStringLiteral("new") : QStringLiteral("cur"); + const QString dirPath = maildirPath() + QLatin1Char('/') + folder; + QDir dir; + if (!dir.mkpath(dirPath + QStringLiteral("/cur")) + || !dir.mkpath(dirPath + QStringLiteral("/new")) + || !dir.mkpath(dirPath + QStringLiteral("/tmp"))) { + return false; + } + + // The local part of the id makes a safe, unique, flag-free filename. + QString base = messageId; + base.remove(QLatin1Char('<')).remove(QLatin1Char('>')); + base.replace(QLatin1Char('@'), QLatin1Char('.')); + base.replace(QLatin1Char('/'), QLatin1Char('.')); + if (!unread) + base += QStringLiteral(":2,S"); + + QFile file(dirPath + QLatin1Char('/') + sub + QLatin1Char('/') + base); + if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) + return false; + + QTextStream out(&file); + out << "From: " << from << "\n" + << "To: danix@danix.xyz\n" + << "Subject: " << subject << "\n" + << "Message-ID: <" << messageId << ">\n" + << "Date: " << date << "\n"; + if (!inReplyTo.isEmpty()) + out << "In-Reply-To: <" << inReplyTo << ">\n" + << "References: <" << inReplyTo << ">\n"; + out << "\n" << body << "\n"; + out.flush(); + file.close(); + return true; + } + + /// Writes the config and runs `notmuch new`. Safe to call repeatedly. + /// Returns false (with error() set) if notmuch is missing or fails. + bool index() + { + QFile config(configPath()); + if (!config.open(QIODevice::WriteOnly | QIODevice::Text)) { + m_error = QStringLiteral("cannot write fixture config"); + return false; + } + QTextStream out(&config); + out << "[database]\n" + << "path=" << maildirPath() << "\n" + << "[new]\n" + << "tags=unread;inbox;\n"; + out.flush(); + config.close(); + + QProcess proc; + QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); + env.insert(QStringLiteral("NOTMUCH_CONFIG"), configPath()); + proc.setProcessEnvironment(env); + proc.start(QStringLiteral("notmuch"), { QStringLiteral("new") }); + if (!proc.waitForStarted(5000)) { + m_error = QStringLiteral("notmuch not found on PATH"); + return false; + } + if (!proc.waitForFinished(30000) || proc.exitCode() != 0) { + m_error = QStringLiteral("notmuch new failed: %1") + .arg(QString::fromLocal8Bit(proc.readAllStandardError())); + return false; + } + return true; + } + + QString error() const { return m_error; } + +private: + QTemporaryDir m_dir; + QString m_error; +}; |
