Files
logos-storage-module/tests/test_storage_module.cpp

649 lines
22 KiB
C++
Raw Permalink Normal View History

2026-04-14 13:59:13 -04:00
// Integration tests for StorageModuleImpl - uses the REAL libstorage library.
2026-04-06 10:07:22 -04:00
// No mocking. These tests start an actual storage node, upload/download real data,
// and verify end-to-end behavior.
//
// Requires libstorage to be available in ../lib at build time.
// Skipped automatically when libstorage is not found.
#include <logos_test.h>
#include "storage_module_plugin.h"
2026-04-14 13:59:13 -04:00
#include <nlohmann/json.hpp>
2026-04-06 10:07:22 -04:00
2026-04-14 13:59:13 -04:00
#include <atomic>
#include <chrono>
#include <condition_variable>
#include <cstdio>
#include <filesystem>
#include <fstream>
2026-06-23 20:24:44 +02:00
#include <memory>
2026-04-14 13:59:13 -04:00
#include <mutex>
#include <string>
namespace fs = std::filesystem;
using json = nlohmann::json;
2026-04-06 10:07:22 -04:00
// ---------------------------------------------------------------------------
2026-04-14 13:59:13 -04:00
// base64 decode — mirrors the base64Encode helper in storage_module_plugin.cpp.
// Chunk data arriving in storageDownloadProgress events is base64-encoded so
// that arbitrary binary bytes can be safely embedded in a JSON string.
2026-04-06 10:07:22 -04:00
// ---------------------------------------------------------------------------
2026-04-14 13:59:13 -04:00
static std::string base64Decode(const std::string& in) {
static const int kDecTable[256] = {
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,62,-1,-1,-1,63,
52,53,54,55,56,57,58,59,60,61,-1,-1,-1,-1,-1,-1,
-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,
15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1,
-1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,
41,42,43,44,45,46,47,48,49,50,51,-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
2026-04-06 10:07:22 -04:00
};
2026-04-14 13:59:13 -04:00
std::string out;
out.reserve((in.size() / 4) * 3);
int val = 0, bits = -8;
for (unsigned char c : in) {
if (kDecTable[c] == -1) break;
val = (val << 6) + kDecTable[c];
bits += 6;
if (bits >= 0) {
out += static_cast<char>((val >> bits) & 0xFF);
bits -= 8;
2026-04-06 10:07:22 -04:00
}
2026-04-14 13:59:13 -04:00
}
return out;
2026-04-06 10:07:22 -04:00
}
2026-04-14 13:59:13 -04:00
static const int DEFAULT_TIMEOUT_MS = 3000;
2026-06-29 09:47:05 +04:00
static const int START_TIMEOUT_MS = 5000;
2026-04-14 13:59:13 -04:00
static const std::string LOG_FILENAME = "storage.log";
2026-04-06 10:07:22 -04:00
// ---------------------------------------------------------------------------
2026-04-14 13:59:13 -04:00
// EventWaiter - replaces QEventLoop + storageResponse signal.
2026-06-23 20:24:44 +02:00
// Collects events emitted via the typed `logos_events:` methods on
// StorageModuleImpl. The Qt-free test forwarders in
// `tests/storage_events_test.cpp` route each event through
// logos_test::recordEvent(name, payload); here we install a ScopedEventSink
// that fans them into the same (name, data) pair the existing assertions read.
2026-04-06 10:07:22 -04:00
// ---------------------------------------------------------------------------
2026-04-14 13:59:13 -04:00
2026-06-29 09:47:05 +04:00
// Events are identified by the typed `logos_events:` method, exactly as the
// plugin emits them in storage_module_plugin.cpp (e.g.
// `&StorageModuleImpl::storageUploadDone`). The Qt-free forwarders in
// storage_events_test.cpp record each one under its method name, so this is
// the single place that maps the typed identity back to that name.
using StorageEvent = void (StorageModuleImpl::*)(const std::string&);
static const char* eventName(StorageEvent e) {
if (e == &StorageModuleImpl::storageStart) return "storageStart";
if (e == &StorageModuleImpl::storageStop) return "storageStop";
if (e == &StorageModuleImpl::storageConnect) return "storageConnect";
if (e == &StorageModuleImpl::storageUploadProgress) return "storageUploadProgress";
if (e == &StorageModuleImpl::storageUploadDone) return "storageUploadDone";
if (e == &StorageModuleImpl::storageDownloadProgress) return "storageDownloadProgress";
if (e == &StorageModuleImpl::storageDownloadDone) return "storageDownloadDone";
2026-06-29 15:31:06 +04:00
if (e == &StorageModuleImpl::storageDownloadManifestDone) return "storageDownloadManifestDone";
2026-06-29 17:41:35 +04:00
if (e == &StorageModuleImpl::storageRemoveDone) return "storageRemoveDone";
2026-06-29 15:31:06 +04:00
2026-06-29 09:47:05 +04:00
return "";
}
2026-04-14 13:59:13 -04:00
struct EventWaiter {
std::mutex mtx;
std::condition_variable cv;
std::string lastEventName;
std::string lastEventData;
bool received = false;
2026-06-23 20:24:44 +02:00
std::unique_ptr<logos_test::ScopedEventSink> sink;
2026-04-14 13:59:13 -04:00
2026-06-23 20:24:44 +02:00
// Install — must be called once per impl instance. Replaces any previous
// sink installed by an earlier EventWaiter / collectDownloadChunks call.
2026-06-29 09:47:05 +04:00
// Destroy the old sink *before* creating the new one: ScopedEventSink is
// RAII over a single global slot, so constructing the replacement first
// and tearing the old one down after would leave the old destructor
// clearing the slot we just registered — silently dropping every event of
// the next node (the cause of restart timeouts).
2026-06-23 20:24:44 +02:00
void install(StorageModuleImpl* /*impl*/) {
2026-06-29 09:47:05 +04:00
sink.reset();
2026-06-23 20:24:44 +02:00
sink = std::make_unique<logos_test::ScopedEventSink>(
[this](const std::string& name, const std::string& data) {
std::unique_lock<std::mutex> lock(mtx);
lastEventName = name;
lastEventData = data;
received = true;
cv.notify_all();
});
2026-04-14 13:59:13 -04:00
}
// Reset before waiting for the next event.
void reset() {
std::unique_lock<std::mutex> lock(mtx);
received = false;
lastEventName.clear();
lastEventData.clear();
}
2026-06-29 09:47:05 +04:00
// Wait for the given typed event within timeoutMs.
bool waitFor(StorageEvent event, int timeoutMs) {
const std::string name = eventName(event);
2026-04-14 13:59:13 -04:00
std::unique_lock<std::mutex> lock(mtx);
bool ok = cv.wait_for(lock, std::chrono::milliseconds(timeoutMs),
[&] { return received && lastEventName == name; });
return ok;
}
// Non-blocking: returns the last event data string (must be called under lock or after wait).
std::string data() {
std::unique_lock<std::mutex> lock(mtx);
return lastEventData;
}
};
// ---------------------------------------------------------------------------
// Shared impl instance - restarted before each test.
// ---------------------------------------------------------------------------
static StorageModuleImpl* g_impl = nullptr;
static fs::path g_dataDir;
static EventWaiter g_waiter;
2026-04-06 10:07:22 -04:00
2026-06-22 16:31:49 +04:00
static void ensureRestarted(const json& extraConfig = json::object()) {
2026-04-14 13:59:13 -04:00
if (g_impl) {
g_impl->stop();
g_waiter.reset();
2026-06-29 09:47:05 +04:00
g_waiter.waitFor(&StorageModuleImpl::storageStop, DEFAULT_TIMEOUT_MS);
2026-04-14 13:59:13 -04:00
g_impl->destroy();
delete g_impl;
g_impl = nullptr;
g_dataDir.clear();
2026-04-06 10:07:22 -04:00
}
2026-04-14 13:59:13 -04:00
g_dataDir = fs::temp_directory_path() /
("logos-storage-integration-test-" +
std::to_string(
std::chrono::steady_clock::now().time_since_epoch().count()));
fs::create_directories(g_dataDir);
std::string logFile = (g_dataDir / LOG_FILENAME).string();
g_impl = new StorageModuleImpl();
g_waiter.install(g_impl);
2026-06-22 16:31:49 +04:00
json cfg = {
{"data-dir", g_dataDir.string()},
{"log-level", "DEBUG"},
2026-06-29 09:47:05 +04:00
{"nat", "none"},
2026-06-22 16:31:49 +04:00
{"log-file", logFile},
};
cfg.update(extraConfig);
std::string config = cfg.dump();
2026-04-14 13:59:13 -04:00
if (!g_impl->init(config)) {
throw LogosTestFailure("Failed to init storage impl.");
2026-04-06 10:07:22 -04:00
}
2026-04-07 14:20:45 +04:00
2026-04-14 13:59:13 -04:00
g_waiter.reset();
if (!g_impl->start()) {
throw LogosTestFailure("Failed to start storage impl.");
2026-04-07 14:20:45 +04:00
}
2026-06-29 09:47:05 +04:00
if (!g_waiter.waitFor(&StorageModuleImpl::storageStart, START_TIMEOUT_MS)) {
2026-04-07 14:20:45 +04:00
throw LogosTestFailure("Storage node did not start within timeout.");
}
}
2026-04-06 10:07:22 -04:00
2026-04-14 13:59:13 -04:00
// ---------------------------------------------------------------------------
// Helper: write a file and upload it, returning the CID.
// ---------------------------------------------------------------------------
static std::string uploadContent(const std::string& content,
const std::string& filename) {
fs::path filePath = g_dataDir / filename;
std::ofstream f(filePath, std::ios::binary);
f.write(content.data(), static_cast<std::streamsize>(content.size()));
f.close();
g_waiter.reset();
StdLogosResult sr = g_impl->uploadUrl(filePath.string(), 65536);
if (!sr.success) return {};
2026-06-29 09:47:05 +04:00
if (!g_waiter.waitFor(&StorageModuleImpl::storageUploadDone, DEFAULT_TIMEOUT_MS)) return {};
2026-04-14 13:59:13 -04:00
// Extract CID from JSON payload: {"success":true,"sessionId":"...","cid":"..."}
std::string d = g_waiter.data();
auto cidPos = d.find("\"cid\":\"");
if (cidPos == std::string::npos) return {};
cidPos += 7;
auto cidEnd = d.find('"', cidPos);
if (cidEnd == std::string::npos) return {};
return d.substr(cidPos, cidEnd - cidPos);
}
// ---------------------------------------------------------------------------
// Helper: collect download chunks until storageDownloadDone.
// ---------------------------------------------------------------------------
static std::string collectDownloadChunks(int timeoutMs) {
std::string collected;
std::mutex m;
std::condition_variable cv;
bool done = false;
bool success = false;
2026-06-23 20:24:44 +02:00
// Replace the global EventWaiter sink for this call only; restored at
// the end via g_waiter.install(g_impl).
logos_test::ScopedEventSink localSink(
[&](const std::string& name, const std::string& data) {
if (name == "storageDownloadProgress") {
// Extract chunk field from JSON payload.
// The chunk is base64-encoded; decode it before accumulating.
auto pos = data.find("\"chunk\":\"");
if (pos != std::string::npos) {
pos += 9;
auto end = data.find('"', pos);
if (end != std::string::npos) {
collected += base64Decode(data.substr(pos, end - pos));
}
2026-04-14 13:59:13 -04:00
}
2026-06-23 20:24:44 +02:00
} else if (name == "storageDownloadDone") {
success = data.find("\"success\":true") != std::string::npos;
std::unique_lock<std::mutex> lock(m);
done = true;
cv.notify_all();
2026-04-14 13:59:13 -04:00
}
2026-06-23 20:24:44 +02:00
});
2026-04-14 13:59:13 -04:00
std::unique_lock<std::mutex> lock(m);
cv.wait_for(lock, std::chrono::milliseconds(timeoutMs),
[&] { return done; });
// Restore normal waiter.
g_waiter.install(g_impl);
return success ? collected : std::string();
}
// integration_version
2026-04-06 10:07:22 -04:00
2026-06-29 22:06:42 +04:00
LOGOS_TEST(init_multiple_times) {
std::string g_dataDir = fs::temp_directory_path() /
("logos-storage-integration-test-" +
std::to_string(
std::chrono::steady_clock::now().time_since_epoch().count()));
g_impl = new StorageModuleImpl();
g_waiter.install(g_impl);
json cfg = {
{"data-dir", g_dataDir},
{"log-level", "DEBUG"},
{"nat", "none"},
};
std::string config = cfg.dump();
if (!g_impl->init(config)) {
throw LogosTestFailure("Failed to init storage impl.");
}
// It will not re-initialize if already initialized.
// So the init function should return false to indicate that
// no init was done but the call itself should not fail.
if (g_impl->init(config)) {
throw LogosTestFailure("Failed to init storage impl.");
}
// The call to destroy should succeed.
StdLogosResult result = g_impl->destroy();
if (!result.success) {
throw LogosTestFailure("Failed to destroy storage impl.");
}
delete g_impl;
g_impl = nullptr;
}
// integration_version
2026-04-06 10:07:22 -04:00
LOGOS_TEST(integration_version) {
2026-04-07 14:23:02 +04:00
ensureRestarted();
2026-04-14 13:59:13 -04:00
StdLogosResult r = g_impl->version();
LOGOS_ASSERT_TRUE(r.success);
LOGOS_ASSERT_FALSE(r.value.get<std::string>().empty());
2026-04-06 10:07:22 -04:00
}
2026-04-14 13:59:13 -04:00
// integration_dataDir
2026-04-06 10:07:22 -04:00
LOGOS_TEST(integration_dataDir) {
2026-04-07 14:23:02 +04:00
ensureRestarted();
2026-04-14 13:59:13 -04:00
StdLogosResult r = g_impl->dataDir();
LOGOS_ASSERT_TRUE(r.success);
LOGOS_ASSERT_EQ(r.value.get<std::string>(), g_dataDir.string());
2026-04-06 10:07:22 -04:00
}
2026-04-14 13:59:13 -04:00
// integration_peerId
2026-04-06 10:07:22 -04:00
LOGOS_TEST(integration_peerId) {
2026-04-07 14:23:02 +04:00
ensureRestarted();
2026-04-14 13:59:13 -04:00
StdLogosResult r = g_impl->peerId();
LOGOS_ASSERT_TRUE(r.success);
LOGOS_ASSERT_FALSE(r.value.get<std::string>().empty());
2026-04-06 10:07:22 -04:00
}
2026-04-14 13:59:13 -04:00
// integration_debug
2026-04-06 10:07:22 -04:00
LOGOS_TEST(integration_debug) {
2026-04-07 14:23:02 +04:00
ensureRestarted();
2026-04-14 13:59:13 -04:00
StdLogosResult r = g_impl->debug();
LOGOS_ASSERT_TRUE(r.success);
LOGOS_ASSERT_TRUE(r.value.is_object());
LOGOS_ASSERT_FALSE(r.value.empty());
LOGOS_ASSERT_TRUE(r.value.contains("id"));
LOGOS_ASSERT_TRUE(r.value.contains("addrs"));
LOGOS_ASSERT_TRUE(r.value.contains("announceAddresses"));
LOGOS_ASSERT_TRUE(r.value.contains("table"));
2026-04-06 10:07:22 -04:00
}
2026-06-22 19:44:54 +02:00
// integration_collectMetrics
LOGOS_TEST(integration_collectMetrics) {
ensureRestarted();
LogosMap r = g_impl->collectMetrics();
LOGOS_ASSERT_TRUE(r.is_object());
LOGOS_ASSERT_TRUE(r.contains("metrics"));
LOGOS_ASSERT_TRUE(r["metrics"].is_array());
if (!r["metrics"].empty()) {
const auto& metric = r["metrics"][0];
LOGOS_ASSERT_TRUE(metric.is_object());
LOGOS_ASSERT_TRUE(metric.contains("name"));
LOGOS_ASSERT_TRUE(metric.contains("type"));
LOGOS_ASSERT_TRUE(metric.contains("help"));
LOGOS_ASSERT_TRUE(metric.contains("value"));
LOGOS_ASSERT_TRUE(metric.contains("labels"));
LOGOS_ASSERT_TRUE(metric["name"].is_string());
LOGOS_ASSERT_TRUE(metric["type"].is_string());
LOGOS_ASSERT_TRUE(metric["help"].is_string());
LOGOS_ASSERT_TRUE(metric["value"].is_number());
LOGOS_ASSERT_TRUE(metric["labels"].is_object());
}
2026-06-22 19:44:54 +02:00
}
2026-04-14 13:59:13 -04:00
// integration_spr
2026-04-06 10:07:22 -04:00
LOGOS_TEST(integration_spr) {
2026-04-07 14:23:02 +04:00
ensureRestarted();
2026-04-14 13:59:13 -04:00
StdLogosResult r = g_impl->spr();
LOGOS_ASSERT_TRUE(r.success);
LOGOS_ASSERT_FALSE(r.value.get<std::string>().empty());
2026-04-06 10:07:22 -04:00
}
2026-04-14 13:59:13 -04:00
// integration_uploadFile
2026-04-06 10:07:22 -04:00
LOGOS_TEST(integration_uploadFile) {
2026-04-07 14:23:02 +04:00
ensureRestarted();
2026-04-14 13:59:13 -04:00
std::string cid = uploadContent("Hello, Logos Storage!", "test_upload.txt");
LOGOS_ASSERT_FALSE(cid.empty());
2026-04-06 10:07:22 -04:00
}
2026-04-14 13:59:13 -04:00
// integration_uploadWorkflowManual
2026-04-06 10:07:22 -04:00
LOGOS_TEST(integration_uploadWorkflowManual) {
2026-04-07 14:23:02 +04:00
ensureRestarted();
2026-04-06 10:07:22 -04:00
2026-04-14 13:59:13 -04:00
fs::path filePath = g_dataDir / "test_manual_upload.txt";
std::string content = "Hello, Logos Storage! Manual upload test.";
std::ofstream f(filePath, std::ios::binary);
f.write(content.data(), static_cast<std::streamsize>(content.size()));
2026-04-06 10:07:22 -04:00
f.close();
2026-04-14 13:59:13 -04:00
StdLogosResult initR = g_impl->uploadInit(filePath.string(), 65536);
LOGOS_ASSERT_TRUE(initR.success);
std::string sid = initR.value.get<std::string>();
LOGOS_ASSERT_FALSE(sid.empty());
2026-04-06 10:07:22 -04:00
2026-04-14 13:59:13 -04:00
LOGOS_ASSERT_TRUE(g_impl->uploadChunk(sid, content).success);
2026-04-06 10:07:22 -04:00
2026-04-14 13:59:13 -04:00
StdLogosResult finalizeR = g_impl->uploadFinalize(sid);
LOGOS_ASSERT_TRUE(finalizeR.success);
LOGOS_ASSERT_FALSE(finalizeR.value.get<std::string>().empty());
2026-04-06 10:07:22 -04:00
}
2026-04-14 13:59:13 -04:00
// integration_downloadFile
2026-04-06 10:07:22 -04:00
LOGOS_TEST(integration_downloadFile) {
2026-04-07 14:23:02 +04:00
ensureRestarted();
2026-04-06 10:07:22 -04:00
2026-04-14 13:59:13 -04:00
std::string content = "Hello, Logos Download Test!";
std::string cid = uploadContent(content, "test_download.txt");
LOGOS_ASSERT_FALSE(cid.empty());
2026-04-06 10:07:22 -04:00
2026-04-14 13:59:13 -04:00
fs::path downloadPath = g_dataDir / "test_download_result.txt";
2026-04-06 10:07:22 -04:00
2026-04-14 13:59:13 -04:00
g_waiter.reset();
StdLogosResult dlR = g_impl->downloadToUrl(cid, downloadPath.string(), false, 65536);
LOGOS_ASSERT_TRUE(dlR.success);
2026-04-06 10:07:22 -04:00
2026-06-29 09:47:05 +04:00
LOGOS_ASSERT_TRUE(g_waiter.waitFor(&StorageModuleImpl::storageDownloadDone, DEFAULT_TIMEOUT_MS));
2026-04-06 10:07:22 -04:00
2026-04-14 13:59:13 -04:00
std::ifstream in(downloadPath, std::ios::binary);
std::string downloaded((std::istreambuf_iterator<char>(in)),
std::istreambuf_iterator<char>());
LOGOS_ASSERT_EQ(downloaded, content);
2026-04-06 10:07:22 -04:00
}
2026-04-14 13:59:13 -04:00
// integration_downloadChunks
2026-04-06 10:07:22 -04:00
LOGOS_TEST(integration_downloadChunks) {
2026-04-07 14:23:02 +04:00
ensureRestarted();
2026-04-06 10:07:22 -04:00
2026-04-14 13:59:13 -04:00
std::string content = "Hello, Logos Chunks Download Test!";
std::string cid = uploadContent(content, "test_chunks_src.txt");
LOGOS_ASSERT_FALSE(cid.empty());
2026-04-06 10:07:22 -04:00
2026-06-23 20:24:44 +02:00
// collectDownloadChunks installs a local ScopedEventSink for the duration
// of the call and restores the global EventWaiter sink before returning.
2026-04-14 13:59:13 -04:00
StdLogosResult dlR = g_impl->downloadChunks(cid, false, 65536);
LOGOS_ASSERT_TRUE(dlR.success);
2026-04-06 10:07:22 -04:00
2026-04-14 13:59:13 -04:00
std::string downloaded = collectDownloadChunks(DEFAULT_TIMEOUT_MS);
LOGOS_ASSERT_FALSE(downloaded.empty());
LOGOS_ASSERT_EQ(downloaded, content);
2026-04-06 10:07:22 -04:00
}
2026-04-14 13:59:13 -04:00
// integration_exists
2026-04-06 10:07:22 -04:00
LOGOS_TEST(integration_exists) {
2026-04-07 14:23:02 +04:00
ensureRestarted();
2026-04-06 10:07:22 -04:00
2026-04-14 13:59:13 -04:00
std::string cid = uploadContent("Hello, Logos Exists Test!", "test_exists_src.txt");
LOGOS_ASSERT_FALSE(cid.empty());
2026-04-06 10:07:22 -04:00
2026-04-14 13:59:13 -04:00
StdLogosResult r = g_impl->exists(cid);
LOGOS_ASSERT_TRUE(r.success);
LOGOS_ASSERT_TRUE(r.value.get<bool>());
2026-04-06 10:07:22 -04:00
}
2026-04-14 13:59:13 -04:00
// integration_fetch
2026-04-06 10:07:22 -04:00
LOGOS_TEST(integration_fetch) {
2026-04-07 14:23:02 +04:00
ensureRestarted();
2026-04-06 10:07:22 -04:00
2026-04-14 13:59:13 -04:00
std::string cid = uploadContent("Hello, Logos Fetch Test!", "test_fetch_src.txt");
LOGOS_ASSERT_FALSE(cid.empty());
2026-04-06 10:07:22 -04:00
2026-04-14 13:59:13 -04:00
LOGOS_ASSERT_TRUE(g_impl->fetch(cid).success);
2026-04-06 10:07:22 -04:00
}
2026-04-14 13:59:13 -04:00
// integration_remove
2026-04-06 10:07:22 -04:00
LOGOS_TEST(integration_remove) {
2026-04-07 14:23:02 +04:00
ensureRestarted();
2026-04-06 10:07:22 -04:00
2026-04-14 13:59:13 -04:00
std::string cid = uploadContent("Hello, Logos Remove Test!", "test_remove_src.txt");
LOGOS_ASSERT_FALSE(cid.empty());
2026-04-06 10:07:22 -04:00
2026-04-14 13:59:13 -04:00
StdLogosResult e1 = g_impl->exists(cid);
LOGOS_ASSERT_TRUE(e1.success);
LOGOS_ASSERT_TRUE(e1.value.get<bool>());
2026-04-06 10:07:22 -04:00
2026-06-28 21:06:57 +04:00
g_waiter.reset();
2026-04-14 13:59:13 -04:00
LOGOS_ASSERT_TRUE(g_impl->remove(cid).success);
2026-04-06 10:07:22 -04:00
2026-06-29 17:41:35 +04:00
LOGOS_ASSERT_TRUE(g_waiter.waitFor(&StorageModuleImpl::storageRemoveDone, DEFAULT_TIMEOUT_MS));
2026-06-28 21:06:57 +04:00
json payload = json::parse(g_waiter.data());
LOGOS_ASSERT_TRUE(payload["success"].get<bool>());
LOGOS_ASSERT_EQ(payload["cid"].get<std::string>(), cid);
2026-04-14 13:59:13 -04:00
StdLogosResult e2 = g_impl->exists(cid);
LOGOS_ASSERT_TRUE(e2.success);
LOGOS_ASSERT_FALSE(e2.value.get<bool>());
2026-04-06 10:07:22 -04:00
}
2026-04-14 13:59:13 -04:00
// integration_space
2026-04-06 10:07:22 -04:00
LOGOS_TEST(integration_space) {
2026-04-07 14:23:02 +04:00
ensureRestarted();
2026-04-06 10:07:22 -04:00
2026-04-14 13:59:13 -04:00
StdLogosResult r = g_impl->space();
LOGOS_ASSERT_TRUE(r.success);
LOGOS_ASSERT_TRUE(r.value.is_object());
LOGOS_ASSERT_FALSE(r.value.empty());
LOGOS_ASSERT_TRUE(r.value.contains("totalBlocks"));
LOGOS_ASSERT_TRUE(r.value.contains("quotaMaxBytes"));
LOGOS_ASSERT_TRUE(r.value.contains("quotaUsedBytes"));
LOGOS_ASSERT_TRUE(r.value.contains("quotaReservedBytes"));
2026-04-06 10:07:22 -04:00
}
2026-04-14 13:59:13 -04:00
// integration_manifests
2026-04-06 10:07:22 -04:00
LOGOS_TEST(integration_manifests) {
2026-04-07 14:23:02 +04:00
ensureRestarted();
2026-04-06 10:07:22 -04:00
2026-04-14 13:59:13 -04:00
std::string content = "Hello, Logos Manifests Test!";
std::string cid = uploadContent(content, "test_manifests_src.txt");
LOGOS_ASSERT_FALSE(cid.empty());
2026-04-06 10:07:22 -04:00
2026-04-14 13:59:13 -04:00
StdLogosResult lr = g_impl->manifests();
LOGOS_ASSERT_TRUE(lr.success);
LOGOS_ASSERT_TRUE(lr.value.is_array());
LOGOS_ASSERT_FALSE(lr.value.empty());
2026-04-06 10:07:22 -04:00
bool found = false;
2026-04-14 13:59:13 -04:00
for (const auto& entry : lr.value) {
if (!entry.contains("cid") || entry["cid"].get<std::string>() != cid) continue;
found = true;
LOGOS_ASSERT_TRUE(entry.contains("treeCid"));
LOGOS_ASSERT_FALSE(entry["treeCid"].get<std::string>().empty());
break;
2026-04-06 10:07:22 -04:00
}
LOGOS_ASSERT_TRUE(found);
}
2026-04-14 13:59:13 -04:00
// integration_downloadManifest
2026-04-06 10:07:22 -04:00
LOGOS_TEST(integration_downloadManifest) {
2026-04-07 14:23:02 +04:00
ensureRestarted();
2026-04-06 10:07:22 -04:00
2026-04-14 13:59:13 -04:00
std::string content = "Hello, Logos DownloadManifest Test!";
std::string cid = uploadContent(content, "test_download_manifest_src.txt");
LOGOS_ASSERT_FALSE(cid.empty());
2026-04-06 10:07:22 -04:00
2026-06-26 15:36:28 +04:00
g_waiter.reset();
2026-04-14 13:59:13 -04:00
StdLogosResult mr = g_impl->downloadManifest(cid);
LOGOS_ASSERT_TRUE(mr.success);
2026-06-26 15:36:28 +04:00
2026-06-29 15:31:06 +04:00
LOGOS_ASSERT_TRUE(g_waiter.waitFor(&StorageModuleImpl::storageDownloadManifestDone, DEFAULT_TIMEOUT_MS));
2026-06-26 15:36:28 +04:00
json payload = json::parse(g_waiter.data());
LOGOS_ASSERT_TRUE(payload["success"].get<bool>());
LOGOS_ASSERT_EQ(payload["cid"].get<std::string>(), cid);
const auto& manifest = payload["manifest"];
LOGOS_ASSERT_TRUE(manifest.is_object());
LOGOS_ASSERT_TRUE(manifest.contains("treeCid"));
LOGOS_ASSERT_TRUE(manifest.contains("datasetSize"));
2026-04-06 10:07:22 -04:00
}
2026-04-14 13:59:13 -04:00
// integration_updateLogLevel
2026-04-06 10:07:22 -04:00
LOGOS_TEST(integration_updateLogLevel) {
2026-04-07 14:23:02 +04:00
ensureRestarted();
2026-04-06 10:07:22 -04:00
2026-04-14 13:59:13 -04:00
LOGOS_ASSERT_TRUE(g_impl->updateLogLevel("TRACE").success);
2026-04-06 10:07:22 -04:00
2026-04-14 13:59:13 -04:00
// Upload a file to generate TRACE logs.
uploadContent("Hello, Logos Log Level Test!", "test_loglevel_src.txt");
2026-04-06 10:07:22 -04:00
2026-04-14 13:59:13 -04:00
fs::path logFile = g_dataDir / LOG_FILENAME;
std::ifstream in(logFile);
std::string logContent((std::istreambuf_iterator<char>(in)),
std::istreambuf_iterator<char>());
2026-04-06 10:07:22 -04:00
2026-04-14 13:59:13 -04:00
LOGOS_ASSERT_FALSE(logContent.empty());
LOGOS_ASSERT_TRUE(logContent.find("TRC") != std::string::npos);
2026-04-06 10:07:22 -04:00
}
2026-06-22 16:31:49 +04:00
// integration_togglePrivateQueries_withoutMix
//
// This test verifies that private queries cannot be enabled without Mix being
// configured.
LOGOS_TEST(integration_togglePrivateQueries_withoutMix) {
ensureRestarted();
StdLogosResult sprRes = g_impl->spr();
LOGOS_ASSERT_TRUE(sprRes.success);
std::string proxySpr = sprRes.value.get<std::string>();
ensureRestarted({
{"mix-enabled", false},
{"dht-mix-proxy", json::array({proxySpr})},
});
// Enabling fails: Mix is not configured.
StdLogosResult on = g_impl->togglePrivateQueries(true);
LOGOS_ASSERT_FALSE(on.success);
// Disabling is always allowed and reports the previous state (off).
StdLogosResult off = g_impl->togglePrivateQueries(false);
LOGOS_ASSERT_TRUE(off.success);
LOGOS_ASSERT_FALSE(off.value.get<bool>());
}
// integration_togglePrivateQueries_withMixEnabled
//
// This test verifies that private queries can be enabled and disabled when Mix
// is configured.
LOGOS_TEST(integration_togglePrivateQueries_withMixEnabled) {
ensureRestarted();
StdLogosResult sprRes = g_impl->spr();
LOGOS_ASSERT_TRUE(sprRes.success);
std::string proxySpr = sprRes.value.get<std::string>();
ensureRestarted({
{"mix-enabled", true},
{"dht-mix-proxy", json::array({proxySpr})},
});
// Mix configured: private queries default on, so the first disable reports
// the previous state as on.
StdLogosResult off = g_impl->togglePrivateQueries(false);
LOGOS_ASSERT_TRUE(off.success);
LOGOS_ASSERT_TRUE(off.value.get<bool>());
// Re-enabling now succeeds (Mix is configured) and reports previous = off.
StdLogosResult on = g_impl->togglePrivateQueries(true);
LOGOS_ASSERT_TRUE(on.success);
LOGOS_ASSERT_FALSE(on.value.get<bool>());
}