2026-04-14 13:59:13 -04:00
|
|
|
// Unit tests for StorageModuleImpl.
|
2026-04-06 10:07:22 -04:00
|
|
|
// All libstorage C functions are mocked at link time via mock_libstorage.cpp.
|
2026-04-14 13:59:13 -04:00
|
|
|
// Async mocks invoke the callback immediately so the condvar is signalled
|
|
|
|
|
// before waitSync's first check.
|
2026-04-06 10:07:22 -04:00
|
|
|
|
|
|
|
|
#include <logos_test.h>
|
|
|
|
|
#include "storage_module_plugin.h"
|
|
|
|
|
|
2026-04-14 13:59:13 -04:00
|
|
|
#include <nlohmann/json.hpp>
|
|
|
|
|
using json = nlohmann::json;
|
|
|
|
|
|
|
|
|
|
// Helper: create an impl with a mocked, successfully initialized storage context.
|
|
|
|
|
static StorageModuleImpl* createInitializedImpl(LogosTestContext& t) {
|
2026-04-06 10:07:22 -04:00
|
|
|
t.mockCFunction("storage_new").returns(1);
|
2026-04-14 13:59:13 -04:00
|
|
|
auto* impl = new StorageModuleImpl();
|
|
|
|
|
LOGOS_ASSERT_TRUE(impl->init("{\"data-dir\":\"/tmp/test\"}"));
|
|
|
|
|
return impl;
|
2026-04-06 10:07:22 -04:00
|
|
|
}
|
|
|
|
|
|
2026-04-14 13:59:13 -04:00
|
|
|
// init
|
2026-04-06 10:07:22 -04:00
|
|
|
|
|
|
|
|
LOGOS_TEST(init_succeeds_when_storage_new_returns_context) {
|
|
|
|
|
auto t = LogosTestContext("storage_module");
|
|
|
|
|
t.mockCFunction("storage_new").returns(1);
|
|
|
|
|
|
2026-04-14 13:59:13 -04:00
|
|
|
StorageModuleImpl impl;
|
|
|
|
|
LOGOS_ASSERT_TRUE(impl.init("{\"data-dir\":\"/tmp/test\"}"));
|
2026-04-06 10:07:22 -04:00
|
|
|
LOGOS_ASSERT(t.cFunctionCalled("storage_new"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LOGOS_TEST(init_fails_when_storage_new_returns_null) {
|
|
|
|
|
auto t = LogosTestContext("storage_module");
|
|
|
|
|
t.mockCFunction("storage_new").returns(0);
|
|
|
|
|
|
2026-04-14 13:59:13 -04:00
|
|
|
StorageModuleImpl impl;
|
|
|
|
|
LOGOS_ASSERT_FALSE(impl.init("{\"data-dir\":\"/tmp/test\"}"));
|
2026-04-06 10:07:22 -04:00
|
|
|
}
|
|
|
|
|
|
2026-04-14 13:59:13 -04:00
|
|
|
// version
|
2026-04-06 10:07:22 -04:00
|
|
|
|
|
|
|
|
LOGOS_TEST(version_returns_mocked_string) {
|
|
|
|
|
auto t = LogosTestContext("storage_module");
|
2026-04-14 13:59:13 -04:00
|
|
|
auto* impl = createInitializedImpl(t);
|
2026-04-06 10:07:22 -04:00
|
|
|
|
|
|
|
|
t.mockCFunction("storage_version").returns("1.2.3-test");
|
2026-04-14 13:59:13 -04:00
|
|
|
StdLogosResult vr = impl->version();
|
2026-04-06 10:07:22 -04:00
|
|
|
|
2026-04-14 13:59:13 -04:00
|
|
|
LOGOS_ASSERT_TRUE(vr.success);
|
|
|
|
|
LOGOS_ASSERT_EQ(vr.value.get<std::string>(), std::string("1.2.3-test"));
|
2026-04-06 10:07:22 -04:00
|
|
|
LOGOS_ASSERT(t.cFunctionCalled("storage_version"));
|
|
|
|
|
|
2026-04-14 13:59:13 -04:00
|
|
|
impl->destroy();
|
|
|
|
|
delete impl;
|
2026-04-06 10:07:22 -04:00
|
|
|
}
|
|
|
|
|
|
2026-06-24 17:14:12 +04:00
|
|
|
// The value is injected at build time from metadata.json; the fallback
|
|
|
|
|
// "0.0.0-dev" only survives when the build failed to pass the define, so
|
|
|
|
|
// asserting against it verifies the injection pipeline actually ran.
|
|
|
|
|
LOGOS_TEST(moduleVersion_is_injected_from_build) {
|
|
|
|
|
StorageModuleImpl impl;
|
|
|
|
|
std::string v = impl.moduleVersion();
|
|
|
|
|
|
|
|
|
|
LOGOS_ASSERT_FALSE(v.empty());
|
|
|
|
|
LOGOS_ASSERT(v != std::string("0.0.0-dev"));
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-14 13:59:13 -04:00
|
|
|
// start / stop
|
2026-04-06 10:07:22 -04:00
|
|
|
|
|
|
|
|
LOGOS_TEST(start_returns_true_after_init) {
|
|
|
|
|
auto t = LogosTestContext("storage_module");
|
2026-04-14 13:59:13 -04:00
|
|
|
auto* impl = createInitializedImpl(t);
|
2026-04-06 10:07:22 -04:00
|
|
|
|
2026-04-14 13:59:13 -04:00
|
|
|
LOGOS_ASSERT_TRUE(impl->start());
|
2026-04-06 10:07:22 -04:00
|
|
|
LOGOS_ASSERT(t.cFunctionCalled("storage_start"));
|
|
|
|
|
|
2026-04-14 13:59:13 -04:00
|
|
|
impl->destroy();
|
|
|
|
|
delete impl;
|
2026-04-06 10:07:22 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LOGOS_TEST(start_returns_false_without_init) {
|
|
|
|
|
auto t = LogosTestContext("storage_module");
|
2026-04-14 13:59:13 -04:00
|
|
|
StorageModuleImpl impl;
|
|
|
|
|
LOGOS_ASSERT_FALSE(impl.start());
|
2026-04-06 10:07:22 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LOGOS_TEST(stop_fails_without_init) {
|
|
|
|
|
auto t = LogosTestContext("storage_module");
|
2026-04-14 13:59:13 -04:00
|
|
|
StorageModuleImpl impl;
|
|
|
|
|
LOGOS_ASSERT_FALSE(impl.stop().success);
|
2026-04-06 10:07:22 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LOGOS_TEST(stop_succeeds_after_init) {
|
|
|
|
|
auto t = LogosTestContext("storage_module");
|
2026-04-14 13:59:13 -04:00
|
|
|
auto* impl = createInitializedImpl(t);
|
2026-04-06 10:07:22 -04:00
|
|
|
|
2026-04-14 13:59:13 -04:00
|
|
|
LOGOS_ASSERT_TRUE(impl->stop().success);
|
2026-04-06 10:07:22 -04:00
|
|
|
LOGOS_ASSERT(t.cFunctionCalled("storage_stop"));
|
|
|
|
|
|
2026-04-14 13:59:13 -04:00
|
|
|
impl->destroy();
|
|
|
|
|
delete impl;
|
2026-04-06 10:07:22 -04:00
|
|
|
}
|
|
|
|
|
|
2026-04-14 13:59:13 -04:00
|
|
|
// destroy
|
2026-04-06 10:07:22 -04:00
|
|
|
|
2026-06-09 12:07:59 +04:00
|
|
|
LOGOS_TEST(destroy_without_init_returns_error) {
|
2026-04-06 10:07:22 -04:00
|
|
|
auto t = LogosTestContext("storage_module");
|
2026-04-14 13:59:13 -04:00
|
|
|
StorageModuleImpl impl;
|
2026-06-09 12:07:59 +04:00
|
|
|
// destroy() must not touch the C API when there is no context
|
|
|
|
|
LOGOS_ASSERT_FALSE(impl.destroy().success);
|
|
|
|
|
LOGOS_ASSERT(!t.cFunctionCalled("storage_destroy"));
|
2026-04-06 10:07:22 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LOGOS_TEST(destroy_succeeds_after_init) {
|
|
|
|
|
auto t = LogosTestContext("storage_module");
|
2026-04-14 13:59:13 -04:00
|
|
|
auto* impl = createInitializedImpl(t);
|
2026-04-06 10:07:22 -04:00
|
|
|
|
2026-04-14 13:59:13 -04:00
|
|
|
LOGOS_ASSERT_TRUE(impl->destroy().success);
|
2026-04-06 10:07:22 -04:00
|
|
|
LOGOS_ASSERT(t.cFunctionCalled("storage_close"));
|
|
|
|
|
LOGOS_ASSERT(t.cFunctionCalled("storage_destroy"));
|
|
|
|
|
|
2026-04-14 13:59:13 -04:00
|
|
|
delete impl;
|
2026-04-06 10:07:22 -04:00
|
|
|
}
|
|
|
|
|
|
2026-04-14 13:59:13 -04:00
|
|
|
// peerId / spr / dataDir
|
2026-04-06 10:07:22 -04:00
|
|
|
|
|
|
|
|
LOGOS_TEST(peerId_returns_mocked_value) {
|
|
|
|
|
auto t = LogosTestContext("storage_module");
|
2026-04-14 13:59:13 -04:00
|
|
|
auto* impl = createInitializedImpl(t);
|
2026-04-06 10:07:22 -04:00
|
|
|
|
|
|
|
|
t.mockCFunction("storage_peer_id").returns("QmTestPeerId123");
|
2026-04-14 13:59:13 -04:00
|
|
|
StdLogosResult r = impl->peerId();
|
2026-04-06 10:07:22 -04:00
|
|
|
|
2026-04-14 13:59:13 -04:00
|
|
|
LOGOS_ASSERT_TRUE(r.success);
|
|
|
|
|
LOGOS_ASSERT_EQ(r.value.get<std::string>(), std::string("QmTestPeerId123"));
|
2026-04-06 10:07:22 -04:00
|
|
|
|
2026-04-14 13:59:13 -04:00
|
|
|
impl->destroy();
|
|
|
|
|
delete impl;
|
2026-04-06 10:07:22 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LOGOS_TEST(spr_returns_mocked_value) {
|
|
|
|
|
auto t = LogosTestContext("storage_module");
|
2026-04-14 13:59:13 -04:00
|
|
|
auto* impl = createInitializedImpl(t);
|
2026-04-06 10:07:22 -04:00
|
|
|
|
|
|
|
|
t.mockCFunction("storage_spr").returns("spr:ABCD1234");
|
2026-04-14 13:59:13 -04:00
|
|
|
StdLogosResult r = impl->spr();
|
2026-04-06 10:07:22 -04:00
|
|
|
|
2026-04-14 13:59:13 -04:00
|
|
|
LOGOS_ASSERT_TRUE(r.success);
|
|
|
|
|
LOGOS_ASSERT_EQ(r.value.get<std::string>(), std::string("spr:ABCD1234"));
|
2026-04-06 10:07:22 -04:00
|
|
|
|
2026-04-14 13:59:13 -04:00
|
|
|
impl->destroy();
|
|
|
|
|
delete impl;
|
2026-04-06 10:07:22 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LOGOS_TEST(dataDir_returns_mocked_value) {
|
|
|
|
|
auto t = LogosTestContext("storage_module");
|
2026-04-14 13:59:13 -04:00
|
|
|
auto* impl = createInitializedImpl(t);
|
2026-04-06 10:07:22 -04:00
|
|
|
|
|
|
|
|
t.mockCFunction("storage_repo").returns("/tmp/test-data");
|
2026-04-14 13:59:13 -04:00
|
|
|
StdLogosResult r = impl->dataDir();
|
2026-04-06 10:07:22 -04:00
|
|
|
|
2026-04-14 13:59:13 -04:00
|
|
|
LOGOS_ASSERT_TRUE(r.success);
|
|
|
|
|
LOGOS_ASSERT_EQ(r.value.get<std::string>(), std::string("/tmp/test-data"));
|
2026-04-06 10:07:22 -04:00
|
|
|
|
2026-04-14 13:59:13 -04:00
|
|
|
impl->destroy();
|
|
|
|
|
delete impl;
|
2026-04-06 10:07:22 -04:00
|
|
|
}
|
|
|
|
|
|
2026-04-14 13:59:13 -04:00
|
|
|
LOGOS_TEST(peerId_returns_failure_without_init) {
|
2026-04-06 10:07:22 -04:00
|
|
|
auto t = LogosTestContext("storage_module");
|
2026-04-14 13:59:13 -04:00
|
|
|
StorageModuleImpl impl;
|
|
|
|
|
LOGOS_ASSERT_FALSE(impl.peerId().success);
|
2026-04-06 10:07:22 -04:00
|
|
|
}
|
|
|
|
|
|
2026-04-14 13:59:13 -04:00
|
|
|
// debug
|
2026-04-06 10:07:22 -04:00
|
|
|
|
2026-04-14 13:59:13 -04:00
|
|
|
LOGOS_TEST(debug_returns_parsed_map) {
|
2026-04-06 10:07:22 -04:00
|
|
|
auto t = LogosTestContext("storage_module");
|
2026-04-14 13:59:13 -04:00
|
|
|
auto* impl = createInitializedImpl(t);
|
2026-04-06 10:07:22 -04:00
|
|
|
|
|
|
|
|
t.mockCFunction("storage_debug")
|
|
|
|
|
.returns(R"({"id":"QmNode","addrs":[],"announceAddresses":[],"table":{}})");
|
2026-04-14 13:59:13 -04:00
|
|
|
StdLogosResult r = impl->debug();
|
2026-04-06 10:07:22 -04:00
|
|
|
|
2026-04-14 13:59:13 -04:00
|
|
|
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"));
|
2026-04-06 10:07:22 -04:00
|
|
|
|
2026-04-14 13:59:13 -04:00
|
|
|
impl->destroy();
|
|
|
|
|
delete impl;
|
2026-04-06 10:07:22 -04:00
|
|
|
}
|
|
|
|
|
|
2026-06-09 14:03:32 +04:00
|
|
|
LOGOS_TEST(debug_fails_on_invalid_json) {
|
|
|
|
|
auto t = LogosTestContext("storage_module");
|
|
|
|
|
auto* impl = createInitializedImpl(t);
|
|
|
|
|
|
2026-06-09 14:16:46 +04:00
|
|
|
t.mockCFunction("storage_debug").returns("not json");
|
2026-06-09 14:03:32 +04:00
|
|
|
StdLogosResult r = impl->debug();
|
|
|
|
|
LOGOS_ASSERT_FALSE(r.success);
|
|
|
|
|
|
|
|
|
|
impl->destroy();
|
|
|
|
|
delete impl;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-22 19:44:54 +02:00
|
|
|
// collectMetrics
|
|
|
|
|
|
|
|
|
|
LOGOS_TEST(collectMetrics_returns_parsed_metrics) {
|
|
|
|
|
auto t = LogosTestContext("storage_module");
|
|
|
|
|
auto* impl = createInitializedImpl(t);
|
|
|
|
|
|
|
|
|
|
t.mockCFunction("storage_get_metrics")
|
|
|
|
|
.returns(R"({"metrics":[{"name":"test_metric","type":"gauge","help":"Test metric","value":1.0,"labels":{}}]})");
|
|
|
|
|
LogosMap r = impl->collectMetrics();
|
|
|
|
|
|
|
|
|
|
LOGOS_ASSERT_TRUE(r.is_object());
|
|
|
|
|
LOGOS_ASSERT_TRUE(r.contains("metrics"));
|
|
|
|
|
LOGOS_ASSERT_TRUE(r["metrics"].is_array());
|
|
|
|
|
LOGOS_ASSERT_EQ(static_cast<int>(r["metrics"].size()), 1);
|
|
|
|
|
LOGOS_ASSERT(t.cFunctionCalled("storage_get_metrics"));
|
|
|
|
|
|
|
|
|
|
impl->destroy();
|
|
|
|
|
delete impl;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-23 12:00:55 +02:00
|
|
|
LOGOS_TEST(collectMetrics_returns_empty_metrics_on_libstorage_error) {
|
|
|
|
|
auto t = LogosTestContext("storage_module");
|
|
|
|
|
auto* impl = createInitializedImpl(t);
|
|
|
|
|
|
|
|
|
|
t.mockCFunction("storage_get_metrics").returns(1);
|
|
|
|
|
LogosMap r = impl->collectMetrics();
|
|
|
|
|
|
|
|
|
|
LOGOS_ASSERT_TRUE(r.is_object());
|
|
|
|
|
LOGOS_ASSERT_TRUE(r.contains("metrics"));
|
|
|
|
|
LOGOS_ASSERT_TRUE(r["metrics"].is_array());
|
|
|
|
|
LOGOS_ASSERT_TRUE(r["metrics"].empty());
|
|
|
|
|
|
|
|
|
|
impl->destroy();
|
|
|
|
|
delete impl;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-22 19:44:54 +02:00
|
|
|
LOGOS_TEST(collectMetrics_returns_empty_metrics_on_invalid_json) {
|
|
|
|
|
auto t = LogosTestContext("storage_module");
|
|
|
|
|
auto* impl = createInitializedImpl(t);
|
|
|
|
|
|
|
|
|
|
t.mockCFunction("storage_get_metrics").returns("not json");
|
|
|
|
|
LogosMap r = impl->collectMetrics();
|
|
|
|
|
|
|
|
|
|
LOGOS_ASSERT_TRUE(r.is_object());
|
|
|
|
|
LOGOS_ASSERT_TRUE(r.contains("metrics"));
|
|
|
|
|
LOGOS_ASSERT_TRUE(r["metrics"].is_array());
|
|
|
|
|
LOGOS_ASSERT_TRUE(r["metrics"].empty());
|
|
|
|
|
|
|
|
|
|
impl->destroy();
|
|
|
|
|
delete impl;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-23 11:38:44 +02:00
|
|
|
LOGOS_TEST(collectMetrics_returns_empty_metrics_when_payload_is_array) {
|
|
|
|
|
auto t = LogosTestContext("storage_module");
|
|
|
|
|
auto* impl = createInitializedImpl(t);
|
|
|
|
|
|
|
|
|
|
t.mockCFunction("storage_get_metrics").returns(R"([])");
|
|
|
|
|
LogosMap r = impl->collectMetrics();
|
|
|
|
|
|
|
|
|
|
LOGOS_ASSERT_TRUE(r.is_object());
|
|
|
|
|
LOGOS_ASSERT_TRUE(r.contains("metrics"));
|
|
|
|
|
LOGOS_ASSERT_TRUE(r["metrics"].is_array());
|
|
|
|
|
LOGOS_ASSERT_TRUE(r["metrics"].empty());
|
|
|
|
|
|
|
|
|
|
impl->destroy();
|
|
|
|
|
delete impl;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LOGOS_TEST(collectMetrics_returns_empty_metrics_when_metrics_missing) {
|
|
|
|
|
auto t = LogosTestContext("storage_module");
|
|
|
|
|
auto* impl = createInitializedImpl(t);
|
|
|
|
|
|
|
|
|
|
t.mockCFunction("storage_get_metrics").returns(R"({"other":[]})");
|
|
|
|
|
LogosMap r = impl->collectMetrics();
|
|
|
|
|
|
|
|
|
|
LOGOS_ASSERT_TRUE(r.is_object());
|
|
|
|
|
LOGOS_ASSERT_TRUE(r.contains("metrics"));
|
|
|
|
|
LOGOS_ASSERT_TRUE(r["metrics"].is_array());
|
|
|
|
|
LOGOS_ASSERT_TRUE(r["metrics"].empty());
|
|
|
|
|
|
|
|
|
|
impl->destroy();
|
|
|
|
|
delete impl;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LOGOS_TEST(collectMetrics_returns_empty_metrics_when_metrics_is_not_array) {
|
|
|
|
|
auto t = LogosTestContext("storage_module");
|
|
|
|
|
auto* impl = createInitializedImpl(t);
|
|
|
|
|
|
|
|
|
|
t.mockCFunction("storage_get_metrics").returns(R"({"metrics":{}})");
|
|
|
|
|
LogosMap r = impl->collectMetrics();
|
|
|
|
|
|
|
|
|
|
LOGOS_ASSERT_TRUE(r.is_object());
|
|
|
|
|
LOGOS_ASSERT_TRUE(r.contains("metrics"));
|
|
|
|
|
LOGOS_ASSERT_TRUE(r["metrics"].is_array());
|
|
|
|
|
LOGOS_ASSERT_TRUE(r["metrics"].empty());
|
|
|
|
|
|
|
|
|
|
impl->destroy();
|
|
|
|
|
delete impl;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-14 13:59:13 -04:00
|
|
|
// updateLogLevel
|
2026-04-06 10:07:22 -04:00
|
|
|
|
2026-04-14 13:59:13 -04:00
|
|
|
LOGOS_TEST(updateLogLevel_returns_true_on_success) {
|
2026-04-06 10:07:22 -04:00
|
|
|
auto t = LogosTestContext("storage_module");
|
2026-04-14 13:59:13 -04:00
|
|
|
auto* impl = createInitializedImpl(t);
|
2026-04-06 10:07:22 -04:00
|
|
|
|
2026-04-14 13:59:13 -04:00
|
|
|
LOGOS_ASSERT_TRUE(impl->updateLogLevel("DEBUG").success);
|
2026-04-06 10:07:22 -04:00
|
|
|
LOGOS_ASSERT(t.cFunctionCalled("storage_log_level"));
|
|
|
|
|
|
2026-04-14 13:59:13 -04:00
|
|
|
impl->destroy();
|
|
|
|
|
delete impl;
|
2026-04-06 10:07:22 -04:00
|
|
|
}
|
|
|
|
|
|
2026-04-14 13:59:13 -04:00
|
|
|
// exists
|
2026-04-06 10:07:22 -04:00
|
|
|
|
|
|
|
|
LOGOS_TEST(exists_returns_true_when_cid_found) {
|
|
|
|
|
auto t = LogosTestContext("storage_module");
|
2026-04-14 13:59:13 -04:00
|
|
|
auto* impl = createInitializedImpl(t);
|
2026-04-06 10:07:22 -04:00
|
|
|
|
|
|
|
|
t.mockCFunction("storage_exists").returns("true");
|
2026-04-14 13:59:13 -04:00
|
|
|
StdLogosResult r = impl->exists("QmSomeCid");
|
|
|
|
|
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
|
|
|
impl->destroy();
|
|
|
|
|
delete impl;
|
2026-04-06 10:07:22 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LOGOS_TEST(exists_returns_false_when_cid_not_found) {
|
|
|
|
|
auto t = LogosTestContext("storage_module");
|
2026-04-14 13:59:13 -04:00
|
|
|
auto* impl = createInitializedImpl(t);
|
2026-04-06 10:07:22 -04:00
|
|
|
|
|
|
|
|
t.mockCFunction("storage_exists").returns("false");
|
2026-04-14 13:59:13 -04:00
|
|
|
StdLogosResult r = impl->exists("QmMissingCid");
|
|
|
|
|
LOGOS_ASSERT_TRUE(r.success);
|
|
|
|
|
LOGOS_ASSERT_FALSE(r.value.get<bool>());
|
2026-04-06 10:07:22 -04:00
|
|
|
|
2026-04-14 13:59:13 -04:00
|
|
|
impl->destroy();
|
|
|
|
|
delete impl;
|
2026-04-06 10:07:22 -04:00
|
|
|
}
|
|
|
|
|
|
2026-06-22 16:31:49 +04:00
|
|
|
// togglePrivateQueries
|
|
|
|
|
|
|
|
|
|
LOGOS_TEST(togglePrivateQueries_returns_previous_state) {
|
|
|
|
|
auto t = LogosTestContext("storage_module");
|
|
|
|
|
auto* impl = createInitializedImpl(t);
|
|
|
|
|
|
|
|
|
|
t.mockCFunction("storage_toggle_private_queries").returns("false");
|
|
|
|
|
StdLogosResult r = impl->togglePrivateQueries(true);
|
|
|
|
|
LOGOS_ASSERT_TRUE(r.success);
|
|
|
|
|
LOGOS_ASSERT_FALSE(r.value.get<bool>());
|
|
|
|
|
LOGOS_ASSERT(t.cFunctionCalled("storage_toggle_private_queries"));
|
|
|
|
|
|
|
|
|
|
impl->destroy();
|
|
|
|
|
delete impl;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LOGOS_TEST(togglePrivateQueries_maps_true_previous_state) {
|
|
|
|
|
auto t = LogosTestContext("storage_module");
|
|
|
|
|
auto* impl = createInitializedImpl(t);
|
|
|
|
|
|
|
|
|
|
t.mockCFunction("storage_toggle_private_queries").returns("true");
|
|
|
|
|
StdLogosResult r = impl->togglePrivateQueries(false);
|
|
|
|
|
LOGOS_ASSERT_TRUE(r.success);
|
|
|
|
|
LOGOS_ASSERT_TRUE(r.value.get<bool>());
|
|
|
|
|
|
|
|
|
|
impl->destroy();
|
|
|
|
|
delete impl;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-14 13:59:13 -04:00
|
|
|
// fetch / remove
|
2026-04-06 10:07:22 -04:00
|
|
|
|
|
|
|
|
LOGOS_TEST(fetch_calls_storage_fetch) {
|
|
|
|
|
auto t = LogosTestContext("storage_module");
|
2026-04-14 13:59:13 -04:00
|
|
|
auto* impl = createInitializedImpl(t);
|
2026-04-06 10:07:22 -04:00
|
|
|
|
2026-04-14 13:59:13 -04:00
|
|
|
LOGOS_ASSERT_TRUE(impl->fetch("QmSomeCid").success);
|
2026-04-06 10:07:22 -04:00
|
|
|
LOGOS_ASSERT(t.cFunctionCalled("storage_fetch"));
|
|
|
|
|
|
2026-04-14 13:59:13 -04:00
|
|
|
impl->destroy();
|
|
|
|
|
delete impl;
|
2026-04-06 10:07:22 -04:00
|
|
|
}
|
|
|
|
|
|
2026-06-28 21:06:57 +04:00
|
|
|
LOGOS_TEST(remove_dispatches_and_emits_event) {
|
|
|
|
|
logos_test::EventCapture events;
|
2026-04-06 10:07:22 -04:00
|
|
|
auto t = LogosTestContext("storage_module");
|
2026-04-14 13:59:13 -04:00
|
|
|
auto* impl = createInitializedImpl(t);
|
2026-04-06 10:07:22 -04:00
|
|
|
|
2026-04-14 13:59:13 -04:00
|
|
|
LOGOS_ASSERT_TRUE(impl->remove("QmSomeCid").success);
|
2026-04-06 10:07:22 -04:00
|
|
|
LOGOS_ASSERT(t.cFunctionCalled("storage_delete"));
|
2026-06-28 21:06:57 +04:00
|
|
|
LOGOS_ASSERT_TRUE(events.has("storageRemoveDone"));
|
2026-04-06 10:07:22 -04:00
|
|
|
|
2026-04-14 13:59:13 -04:00
|
|
|
impl->destroy();
|
|
|
|
|
delete impl;
|
2026-04-06 10:07:22 -04:00
|
|
|
}
|
|
|
|
|
|
2026-04-14 13:59:13 -04:00
|
|
|
// space
|
2026-04-06 10:07:22 -04:00
|
|
|
|
2026-04-14 13:59:13 -04:00
|
|
|
LOGOS_TEST(space_returns_parsed_map) {
|
2026-04-06 10:07:22 -04:00
|
|
|
auto t = LogosTestContext("storage_module");
|
2026-04-14 13:59:13 -04:00
|
|
|
auto* impl = createInitializedImpl(t);
|
2026-04-06 10:07:22 -04:00
|
|
|
|
|
|
|
|
t.mockCFunction("storage_space")
|
|
|
|
|
.returns(R"({"totalBlocks":100,"quotaMaxBytes":1000,"quotaUsedBytes":50,"quotaReservedBytes":10})");
|
2026-04-14 13:59:13 -04:00
|
|
|
StdLogosResult r = impl->space();
|
2026-04-06 10:07:22 -04:00
|
|
|
|
2026-04-14 13:59:13 -04:00
|
|
|
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"));
|
2026-04-06 10:07:22 -04:00
|
|
|
|
2026-04-14 13:59:13 -04:00
|
|
|
impl->destroy();
|
|
|
|
|
delete impl;
|
2026-04-06 10:07:22 -04:00
|
|
|
}
|
|
|
|
|
|
2026-04-14 13:59:13 -04:00
|
|
|
// manifests
|
2026-04-06 10:07:22 -04:00
|
|
|
|
|
|
|
|
LOGOS_TEST(manifests_returns_parsed_list) {
|
|
|
|
|
auto t = LogosTestContext("storage_module");
|
2026-04-14 13:59:13 -04:00
|
|
|
auto* impl = createInitializedImpl(t);
|
2026-04-06 10:07:22 -04:00
|
|
|
|
|
|
|
|
t.mockCFunction("storage_list")
|
|
|
|
|
.returns(R"([{"cid":"QmABC","manifest":{"treeCid":"QmTree","datasetSize":1024,"blockSize":64,"filename":"test.txt","mimetype":"text/plain"}}])");
|
2026-04-14 13:59:13 -04:00
|
|
|
StdLogosResult r = impl->manifests();
|
2026-04-06 10:07:22 -04:00
|
|
|
|
2026-04-14 13:59:13 -04:00
|
|
|
LOGOS_ASSERT_TRUE(r.success);
|
|
|
|
|
LOGOS_ASSERT_TRUE(r.value.is_array());
|
|
|
|
|
LOGOS_ASSERT_EQ(static_cast<int>(r.value.size()), 1);
|
2026-04-06 10:07:22 -04:00
|
|
|
|
2026-04-14 13:59:13 -04:00
|
|
|
impl->destroy();
|
|
|
|
|
delete impl;
|
2026-04-06 10:07:22 -04:00
|
|
|
}
|
|
|
|
|
|
2026-04-14 13:59:13 -04:00
|
|
|
// downloadManifest
|
2026-04-06 10:07:22 -04:00
|
|
|
|
2026-06-26 15:36:28 +04:00
|
|
|
LOGOS_TEST(downloadManifest_dispatches_and_emits_event) {
|
|
|
|
|
logos_test::EventCapture events;
|
2026-04-06 10:07:22 -04:00
|
|
|
auto t = LogosTestContext("storage_module");
|
2026-04-14 13:59:13 -04:00
|
|
|
auto* impl = createInitializedImpl(t);
|
2026-04-06 10:07:22 -04:00
|
|
|
|
|
|
|
|
t.mockCFunction("storage_download_manifest")
|
|
|
|
|
.returns(R"({"treeCid":"QmTree","datasetSize":2048,"blockSize":64,"filename":"data.bin","mimetype":"application/octet-stream"})");
|
2026-04-14 13:59:13 -04:00
|
|
|
StdLogosResult r = impl->downloadManifest("QmSomeCid");
|
2026-04-06 10:07:22 -04:00
|
|
|
|
2026-04-14 13:59:13 -04:00
|
|
|
LOGOS_ASSERT_TRUE(r.success);
|
2026-06-26 15:36:28 +04:00
|
|
|
LOGOS_ASSERT_TRUE(events.has("storageDownloadManifestDone"));
|
2026-04-06 10:07:22 -04:00
|
|
|
|
2026-04-14 13:59:13 -04:00
|
|
|
impl->destroy();
|
|
|
|
|
delete impl;
|
2026-04-06 10:07:22 -04:00
|
|
|
}
|
|
|
|
|
|
2026-04-14 13:59:13 -04:00
|
|
|
// uploadInit / uploadFinalize / uploadCancel
|
2026-04-06 10:07:22 -04:00
|
|
|
|
|
|
|
|
LOGOS_TEST(uploadInit_returns_session_id) {
|
|
|
|
|
auto t = LogosTestContext("storage_module");
|
2026-04-14 13:59:13 -04:00
|
|
|
auto* impl = createInitializedImpl(t);
|
2026-04-06 10:07:22 -04:00
|
|
|
|
|
|
|
|
t.mockCFunction("storage_upload_init").returns("session-abc-123");
|
2026-04-14 13:59:13 -04:00
|
|
|
StdLogosResult r = impl->uploadInit("test.txt", 65536);
|
2026-04-06 10:07:22 -04:00
|
|
|
|
2026-04-14 13:59:13 -04:00
|
|
|
LOGOS_ASSERT_TRUE(r.success);
|
|
|
|
|
LOGOS_ASSERT_EQ(r.value.get<std::string>(), std::string("session-abc-123"));
|
2026-04-06 10:07:22 -04:00
|
|
|
|
2026-04-14 13:59:13 -04:00
|
|
|
impl->destroy();
|
|
|
|
|
delete impl;
|
2026-04-06 10:07:22 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LOGOS_TEST(uploadFinalize_returns_cid) {
|
|
|
|
|
auto t = LogosTestContext("storage_module");
|
2026-04-14 13:59:13 -04:00
|
|
|
auto* impl = createInitializedImpl(t);
|
2026-04-06 10:07:22 -04:00
|
|
|
|
|
|
|
|
t.mockCFunction("storage_upload_finalize").returns("QmFinalCid");
|
2026-04-14 13:59:13 -04:00
|
|
|
StdLogosResult r = impl->uploadFinalize("session-abc-123");
|
2026-04-06 10:07:22 -04:00
|
|
|
|
2026-04-14 13:59:13 -04:00
|
|
|
LOGOS_ASSERT_TRUE(r.success);
|
|
|
|
|
LOGOS_ASSERT_EQ(r.value.get<std::string>(), std::string("QmFinalCid"));
|
2026-04-06 10:07:22 -04:00
|
|
|
LOGOS_ASSERT(t.cFunctionCalled("storage_upload_finalize"));
|
|
|
|
|
|
2026-04-14 13:59:13 -04:00
|
|
|
impl->destroy();
|
|
|
|
|
delete impl;
|
2026-04-06 10:07:22 -04:00
|
|
|
}
|
|
|
|
|
|
2026-04-14 13:59:13 -04:00
|
|
|
LOGOS_TEST(uploadCancel_returns_true) {
|
2026-04-06 10:07:22 -04:00
|
|
|
auto t = LogosTestContext("storage_module");
|
2026-04-14 13:59:13 -04:00
|
|
|
auto* impl = createInitializedImpl(t);
|
2026-04-06 10:07:22 -04:00
|
|
|
|
2026-04-14 13:59:13 -04:00
|
|
|
LOGOS_ASSERT_TRUE(impl->uploadCancel("session-abc-123").success);
|
2026-04-06 10:07:22 -04:00
|
|
|
LOGOS_ASSERT(t.cFunctionCalled("storage_upload_cancel"));
|
|
|
|
|
|
2026-04-14 13:59:13 -04:00
|
|
|
impl->destroy();
|
|
|
|
|
delete impl;
|
2026-04-06 10:07:22 -04:00
|
|
|
}
|
|
|
|
|
|
2026-04-14 13:59:13 -04:00
|
|
|
// uploadUrl input validation
|
2026-04-06 10:07:22 -04:00
|
|
|
|
2026-04-14 13:59:13 -04:00
|
|
|
LOGOS_TEST(uploadUrl_fails_with_nonexistent_file) {
|
2026-04-06 10:07:22 -04:00
|
|
|
auto t = LogosTestContext("storage_module");
|
2026-04-14 13:59:13 -04:00
|
|
|
auto* impl = createInitializedImpl(t);
|
2026-04-06 10:07:22 -04:00
|
|
|
|
2026-04-14 13:59:13 -04:00
|
|
|
StdLogosResult r = impl->uploadUrl("/nonexistent/path/file.txt", 65536);
|
|
|
|
|
LOGOS_ASSERT_FALSE(r.success);
|
|
|
|
|
|
|
|
|
|
impl->destroy();
|
|
|
|
|
delete impl;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LOGOS_TEST(uploadUrl_fails_with_zero_chunk_size) {
|
|
|
|
|
auto t = LogosTestContext("storage_module");
|
|
|
|
|
auto* impl = createInitializedImpl(t);
|
|
|
|
|
|
|
|
|
|
StdLogosResult r = impl->uploadUrl("/tmp/test.txt", 0);
|
|
|
|
|
LOGOS_ASSERT_FALSE(r.success);
|
|
|
|
|
|
|
|
|
|
impl->destroy();
|
|
|
|
|
delete impl;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// downloadCancel
|
|
|
|
|
|
|
|
|
|
LOGOS_TEST(downloadCancel_returns_true) {
|
|
|
|
|
auto t = LogosTestContext("storage_module");
|
|
|
|
|
auto* impl = createInitializedImpl(t);
|
|
|
|
|
|
|
|
|
|
LOGOS_ASSERT_TRUE(impl->downloadCancel("QmSomeCid").success);
|
2026-04-06 10:07:22 -04:00
|
|
|
LOGOS_ASSERT(t.cFunctionCalled("storage_download_cancel"));
|
|
|
|
|
|
2026-04-14 13:59:13 -04:00
|
|
|
impl->destroy();
|
|
|
|
|
delete impl;
|
2026-04-06 10:07:22 -04:00
|
|
|
}
|
|
|
|
|
|
2026-06-09 14:16:46 +04:00
|
|
|
LOGOS_TEST(downloadChunks_cancels_session_when_stream_fails) {
|
|
|
|
|
auto t = LogosTestContext("storage_module");
|
|
|
|
|
auto* impl = createInitializedImpl(t);
|
|
|
|
|
|
|
|
|
|
// storage_download_init succeeds, but the stream dispatch fails: the
|
|
|
|
|
// already-open session must be cancelled and the call must report failure.
|
|
|
|
|
t.mockCFunction("storage_download_stream").returns(1);
|
|
|
|
|
StdLogosResult r = impl->downloadChunks("QmSomeCid", false, 65536);
|
|
|
|
|
LOGOS_ASSERT_FALSE(r.success);
|
|
|
|
|
LOGOS_ASSERT(t.cFunctionCalled("storage_download_cancel"));
|
|
|
|
|
|
|
|
|
|
impl->destroy();
|
|
|
|
|
delete impl;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-14 13:59:13 -04:00
|
|
|
// connect
|
2026-04-06 10:07:22 -04:00
|
|
|
|
2026-04-14 13:59:13 -04:00
|
|
|
LOGOS_TEST(connect_fails_without_init) {
|
2026-04-06 10:07:22 -04:00
|
|
|
auto t = LogosTestContext("storage_module");
|
2026-04-14 13:59:13 -04:00
|
|
|
StorageModuleImpl impl;
|
|
|
|
|
LOGOS_ASSERT_FALSE(impl.connect("QmPeer", {"addr1"}).success);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LOGOS_TEST(connect_succeeds_after_init) {
|
|
|
|
|
auto t = LogosTestContext("storage_module");
|
|
|
|
|
auto* impl = createInitializedImpl(t);
|
|
|
|
|
|
|
|
|
|
LOGOS_ASSERT_TRUE(impl->connect("QmPeer", {"/ip4/127.0.0.1/tcp/1234"}).success);
|
|
|
|
|
LOGOS_ASSERT(t.cFunctionCalled("storage_connect"));
|
|
|
|
|
|
|
|
|
|
impl->destroy();
|
|
|
|
|
delete impl;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-23 20:24:44 +02:00
|
|
|
// Event wiring — typed `logos_events:` methods are forwarded to
|
|
|
|
|
// logos_test::recordEvent by tests/storage_events_test.cpp, so tests observe
|
|
|
|
|
// them via EventCapture. Construct the capture before the impl so it outlives
|
|
|
|
|
// any background thread the impl's destructor joins.
|
2026-04-14 13:59:13 -04:00
|
|
|
|
|
|
|
|
LOGOS_TEST(start_emits_storageStart_event) {
|
2026-06-23 20:24:44 +02:00
|
|
|
logos_test::EventCapture events;
|
2026-04-14 13:59:13 -04:00
|
|
|
auto t = LogosTestContext("storage_module");
|
|
|
|
|
auto* impl = createInitializedImpl(t);
|
|
|
|
|
|
|
|
|
|
LOGOS_ASSERT_TRUE(impl->start());
|
2026-06-23 20:24:44 +02:00
|
|
|
LOGOS_ASSERT_TRUE(events.has("storageStart"));
|
2026-04-14 13:59:13 -04:00
|
|
|
|
|
|
|
|
impl->destroy();
|
|
|
|
|
delete impl;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LOGOS_TEST(stop_emits_storageStop_event) {
|
2026-06-23 20:24:44 +02:00
|
|
|
logos_test::EventCapture events;
|
2026-04-14 13:59:13 -04:00
|
|
|
auto t = LogosTestContext("storage_module");
|
|
|
|
|
auto* impl = createInitializedImpl(t);
|
|
|
|
|
|
|
|
|
|
LOGOS_ASSERT_TRUE(impl->stop().success);
|
2026-06-23 20:24:44 +02:00
|
|
|
LOGOS_ASSERT_TRUE(events.has("storageStop"));
|
2026-04-14 13:59:13 -04:00
|
|
|
|
|
|
|
|
impl->destroy();
|
|
|
|
|
delete impl;
|
2026-04-06 10:07:22 -04:00
|
|
|
}
|