diff --git a/apps/amm/src/SequencerClient.cpp b/apps/amm/src/SequencerClient.cpp index afe14ad..14cd3e0 100644 --- a/apps/amm/src/SequencerClient.cpp +++ b/apps/amm/src/SequencerClient.cpp @@ -155,6 +155,8 @@ void SequencerClient::readAccount(const QString& accountId, }); return; } + if (forceRefresh) + m_cache.remove(accountId); if (!forceRefresh && m_cache.contains(accountId)) { const WalletAccountRead cached = m_cache.value(accountId); QTimer::singleShot(0, this, diff --git a/apps/amm/tests/cpp/NewPositionRuntimeTest.cpp b/apps/amm/tests/cpp/NewPositionRuntimeTest.cpp index 308b80e..a212ed0 100644 --- a/apps/amm/tests/cpp/NewPositionRuntimeTest.cpp +++ b/apps/amm/tests/cpp/NewPositionRuntimeTest.cpp @@ -14,6 +14,8 @@ #include #include +#include + namespace { bool expect(bool condition, const char* message) { @@ -48,6 +50,7 @@ namespace { } int requestCount() const { return m_requestCount; } + void failNextRequest() { ++m_failuresRemaining; } private: void process(QTcpSocket* socket) @@ -71,10 +74,16 @@ namespace { return; ++m_requestCount; + const bool fail = m_failuresRemaining > 0; + if (fail) + --m_failuresRemaining; const QByteArray payload = QByteArrayLiteral( R"({"jsonrpc":"2.0","id":1,"result":null})"); - QByteArray response = QByteArrayLiteral( - "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: "); + QByteArray response = fail + ? QByteArrayLiteral( + "HTTP/1.1 500 Internal Server Error\r\nContent-Type: application/json\r\nContent-Length: ") + : QByteArrayLiteral( + "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: "); response += QByteArray::number(payload.size()); response += QByteArrayLiteral("\r\nConnection: close\r\n\r\n"); response += payload; @@ -86,6 +95,7 @@ namespace { QTcpServer m_server; QHash m_requests; int m_requestCount = 0; + int m_failuresRemaining = 0; }; class FakeWallet final : public WalletProvider { @@ -262,6 +272,28 @@ namespace { loop.exec(); return completed; } + + bool waitForAccounts(SequencerClient& sequencer, + const QStringList& accountIds, + bool forceRefresh, + QVector* reads) + { + bool completed = false; + QEventLoop loop; + QTimer timeout; + timeout.setSingleShot(true); + QObject::connect(&timeout, &QTimer::timeout, &loop, &QEventLoop::quit); + sequencer.readAccounts(accountIds, forceRefresh, + [&](QVector result) { + *reads = std::move(result); + completed = true; + loop.quit(); + }); + timeout.start(3000); + if (!completed) + loop.exec(); + return completed; + } } int main(int argc, char** argv) @@ -391,5 +423,27 @@ int main(int argc, char** argv) "forced context should reread config and wallet holding")) return 1; + server.failNextRequest(); + QVector failedRefresh; + if (!expect(waitForAccounts(sequencer, { holding.address }, true, + &failedRefresh), + "failed forced holding refresh should complete")) + return 1; + if (!expect(failedRefresh.size() == 1 && !failedRefresh.constFirst().ok(), + "forced holding refresh should surface the sequencer failure")) + return 1; + const int requestsAfterFailure = server.requestCount(); + QVector recoveredRead; + if (!expect(waitForAccounts(sequencer, { holding.address }, false, + &recoveredRead), + "holding read after failure should complete")) + return 1; + if (!expect(server.requestCount() == requestsAfterFailure + 1, + "failed forced refresh should evict the stale holding cache")) + return 1; + if (!expect(recoveredRead.size() == 1 && recoveredRead.constFirst().ok(), + "holding read should recover from the sequencer")) + return 1; + return 0; }