Files
ObsidianDragon/tests/fake_lite_backend.h
DanS 2daea67a1e feat(lite): M3 — new-address generation + sync-indicator confirmation
- LiteWalletController::newAddress(shielded) runs the backend "new" command ("zs"/"R" ->
  do_new_address), parses the ["addr"] response, and returns the new address; the next
  refresh lists it. Fast (local derivation), safe on the UI thread.
- fake_lite_backend returns ["zs1fakenew"]/["R1fakenew"] for "new" by args.
- testLiteWalletControllerNewAddress covers shielded/transparent + no-wallet error.

Also confirmed (no code needed): the sync-progress indicator already works for lite —
balance_tab reads state.sync.* which M2b-3 populates. Per-address balances landed in M2.

Remaining M3 is pure UI wiring (receive_tab button -> newAddress, loading/empty states),
which isn't verifiable without a GUI session.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 10:36:00 -05:00

129 lines
5.6 KiB
C++

// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
//
// Deterministic in-process fake SDXL backend for lite-wallet tests.
//
// Provides a LiteClientBridgeApi whose function pointers return canned JSON and
// track owned-string allocation/free counts, so tests can drive the lite bridge
// (and, from M1 on, the lite services) without a real litelib_* library, network,
// or the DRAGONX_ENABLE_LITE_BACKEND build flag. Build a bridge with:
//
// auto bridge = dragonx::wallet::LiteClientBridge::fromApi(dragonx::test::makeFakeLiteApi());
//
// Invariant for leak/double-free checks: after a test, g_liteFakeAlloc == g_liteFakeFreed.
#pragma once
#include "wallet/lite_client_bridge.h"
#include <atomic>
#include <chrono>
#include <cstdlib>
#include <cstring>
#include <thread>
namespace dragonx {
namespace test {
// Owned-string accounting (atomic: a detached sync thread may touch these concurrently).
inline std::atomic<long> g_liteFakeAlloc{0}; // owned strings handed to the bridge
inline std::atomic<long> g_liteFakeFreed{0}; // owned strings released via freeString
inline bool g_liteFakeWalletExists = true;
inline bool g_liteFakeServerOnline = true;
inline bool g_liteFakeShutdownCalled = false;
inline std::atomic<bool> g_liteFakeSyncBlock{false}; // when true, the "sync" command blocks
inline std::atomic<bool> g_liteFakeBadBalance{false}; // when true, "balance" returns invalid JSON
inline void resetLiteFakeCounters()
{
g_liteFakeAlloc = 0;
g_liteFakeFreed = 0;
g_liteFakeShutdownCalled = false;
}
inline char* liteFakeDup(const char* s)
{
char* p = static_cast<char*>(std::malloc(std::strlen(s) + 1));
std::strcpy(p, s);
++g_liteFakeAlloc;
return p;
}
inline bool liteFakeWalletExists(const char*) { return g_liteFakeWalletExists; }
inline char* liteFakeInitNew(bool, const char*) { return liteFakeDup("{\"result\":\"created\"}"); }
inline char* liteFakeInitFromPhrase(bool, const char*, const char*,
unsigned long long, unsigned long long, bool)
{
return liteFakeDup("{\"result\":\"restored\"}");
}
inline char* liteFakeInitExisting(bool, const char*) { return liteFakeDup("{\"result\":\"opened\"}"); }
inline char* liteFakeExecute(const char* command, const char* args)
{
// new-address generation returns a JSON array with the new address (type from args: zs/R).
if (command && std::strcmp(command, "new") == 0) {
return liteFakeDup(args && std::strcmp(args, "R") == 0 ? "[\"R1fakenew\"]" : "[\"zs1fakenew\"]");
}
// A command named "boom" yields an "Error:"-prefixed response (the bridge's
// looksLikeError contract maps that to ok=false).
if (command && std::strcmp(command, "boom") == 0) {
return liteFakeDup("Error: simulated lite backend failure");
}
// Command-appropriate canned responses matching the litelib JSON shapes (see
// tests/fixtures/lite/result_parsers.json), so the gateway/sync refresh path parses.
if (command) {
const char* c = command;
if (std::strcmp(c, "sync") == 0) {
// Simulate the real backend's blocking full sync when requested, so tests can
// verify shutdown doesn't hang on an in-flight sync.
while (g_liteFakeSyncBlock.load()) std::this_thread::sleep_for(std::chrono::milliseconds(5));
return liteFakeDup("{\"result\":\"success\"}");
}
if (std::strcmp(c, "syncstatus") == 0) // real backend shape: "syncing" is a string
return liteFakeDup("{\"syncing\":\"true\",\"synced_blocks\":1000,\"total_blocks\":1000}");
if (std::strcmp(c, "balance") == 0 && g_liteFakeBadBalance.load())
return liteFakeDup("not-json"); // simulate a shape/parse failure for one command
if (std::strcmp(c, "balance") == 0)
return liteFakeDup("{\"tbalance\":100000000,\"zbalance\":200000000,\"unconfirmed\":50000000,"
"\"verified_zbalance\":180000000,\"spendable_zbalance\":170000000}");
if (std::strcmp(c, "addresses") == 0)
return liteFakeDup("{\"z_addresses\":[\"zs1fakeaddr\"],\"t_addresses\":[\"R1fakeaddr\"]}");
if (std::strcmp(c, "notes") == 0)
return liteFakeDup("{\"unspent_notes\":[],\"utxos\":[],\"pending_notes\":[],\"pending_utxos\":[]}");
if (std::strcmp(c, "list") == 0)
return liteFakeDup("[{\"txid\":\"faketx\",\"datetime\":1700000000,\"block_height\":990,"
"\"unconfirmed\":false,\"address\":\"zs1fakeaddr\",\"amount\":150000000,\"memo\":\"\"}]");
if (std::strcmp(c, "height") == 0) return liteFakeDup("{\"height\":1000}");
if (std::strcmp(c, "info") == 0)
return liteFakeDup("{\"chain_name\":\"main\",\"version\":\"sdxl-fake\",\"latest_block_height\":1000}");
}
// Default for any other/unknown command.
return liteFakeDup("{\"version\":\"sdxl-fake\"}");
}
inline void liteFakeFree(char* v)
{
if (v) {
++g_liteFakeFreed;
std::free(v);
}
}
inline bool liteFakeServerOnline(const char*) { return g_liteFakeServerOnline; }
inline void liteFakeShutdown() { g_liteFakeShutdownCalled = true; }
inline dragonx::wallet::LiteClientBridgeApi makeFakeLiteApi()
{
dragonx::wallet::LiteClientBridgeApi api;
api.walletExists = &liteFakeWalletExists;
api.initializeNew = &liteFakeInitNew;
api.initializeNewFromPhrase = &liteFakeInitFromPhrase;
api.initializeExisting = &liteFakeInitExisting;
api.execute = &liteFakeExecute;
api.freeString = &liteFakeFree;
api.checkServerOnline = &liteFakeServerOnline;
api.shutdown = &liteFakeShutdown;
return api;
}
} // namespace test
} // namespace dragonx