* implemented stubbed IGraphContextService methods * feat: wire graph communication updates in ipc, mcp and cli * fix: fix call graph recall and add profiling coverage to graph calls sites * fix: fixes for work where we added agent navigation to graph tooling * fix(corpus): clean FTS and KG orphans on document delete * fix(repair): clean orphans, honor cancellation, surface stats * fix(rm): order content removal before metadata/KG delete. keep manifest while content is still referenced * fix(daemon): seed built-in content extractor for readiness * chore(cli): remove dead stubs and unwired helpers * feat(search): named-collection scoping + provable relevance gate * fix(refactor): rm dead stubs, inline simd, add proto/metrics tests * chore: rm dead interfaces/enums, consolidate guards, add tests * refactor: rm dr vaporware, collapse download handlers, clean list flags * docs: rm stale testing investigation logs * fix: address Copilot review findings from PR #50 - relationship_ops: return existing id when INSERT OR IGNORE skips - document_crud_ops: propagate FTS5 cleanup error (was silent skip) - builtin_text_content_extractor: unsigned char cast for tolower, add cctype - mcp_server download_jobs: validate job_id for status/cancel actions - list_command tests: unsigned char cast for isspace, add cctype include
985 lines
40 KiB
C++
985 lines
40 KiB
C++
#include <catch2/catch_test_macros.hpp>
|
|
|
|
#include <yams/api/mobile_bindings.h>
|
|
|
|
#include "tests/common/fixture_manager.h"
|
|
#include "tests/common/search_corpus_presets.h"
|
|
#include "tests/integration/daemon/test_daemon_harness.h"
|
|
|
|
#include <algorithm>
|
|
#include <chrono>
|
|
#include <cstdint>
|
|
#include <cstring>
|
|
#include <filesystem>
|
|
#include <fstream>
|
|
#include <random>
|
|
#include <string>
|
|
#include <thread>
|
|
|
|
#include <nlohmann/json.hpp>
|
|
|
|
namespace {
|
|
|
|
std::string make_unique_temp_dir() {
|
|
auto base = std::filesystem::temp_directory_path();
|
|
std::random_device rd;
|
|
std::mt19937 rng(rd());
|
|
std::uniform_int_distribution<std::uint64_t> dist;
|
|
std::filesystem::path candidate;
|
|
do {
|
|
candidate = base / ("yams-mobile-abi-" + std::to_string(dist(rng)));
|
|
} while (std::filesystem::exists(candidate));
|
|
std::filesystem::create_directories(candidate);
|
|
return candidate.string();
|
|
}
|
|
|
|
struct TempDirGuard {
|
|
explicit TempDirGuard(std::string path) : path(std::move(path)) {}
|
|
~TempDirGuard() {
|
|
if (!path.empty()) {
|
|
std::error_code ec;
|
|
std::filesystem::remove_all(path, ec);
|
|
}
|
|
}
|
|
std::string path;
|
|
};
|
|
|
|
bool wait_for_document(yams_mobile_context_t* ctx, const std::string& hash,
|
|
std::chrono::milliseconds timeout) {
|
|
const auto deadline = std::chrono::steady_clock::now() + timeout;
|
|
while (std::chrono::steady_clock::now() < deadline) {
|
|
yams_mobile_document_get_request getReq{};
|
|
getReq.header = yams_mobile_request_header_default();
|
|
getReq.document_hash = hash.c_str();
|
|
getReq.metadata_only = 1;
|
|
|
|
yams_mobile_document_get_result_t* result = nullptr;
|
|
const auto status = yams_mobile_get_document(ctx, &getReq, &result);
|
|
if (status == YAMS_MOBILE_STATUS_OK) {
|
|
yams_mobile_document_get_result_destroy(result);
|
|
return true;
|
|
}
|
|
if (result != nullptr)
|
|
yams_mobile_document_get_result_destroy(result);
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool wait_for_corpus_status_documents(yams_mobile_context_t* ctx, std::uint64_t minDocuments,
|
|
std::chrono::milliseconds timeout) {
|
|
const auto deadline = std::chrono::steady_clock::now() + timeout;
|
|
while (std::chrono::steady_clock::now() < deadline) {
|
|
yams_mobile_vector_status_request statusReq{};
|
|
statusReq.header = yams_mobile_request_header_default();
|
|
|
|
yams_mobile_vector_status_result_t* result = nullptr;
|
|
const auto status = yams_mobile_get_vector_status(ctx, &statusReq, &result);
|
|
if (status == YAMS_MOBILE_STATUS_OK && result != nullptr) {
|
|
const auto payload = yams_mobile_vector_status_result_json(result);
|
|
if (payload.data != nullptr) {
|
|
const auto parsed = nlohmann::json::parse(std::string(payload.data, payload.length),
|
|
nullptr, false);
|
|
if (!parsed.is_discarded() && parsed.value("documents", 0ULL) >= minDocuments) {
|
|
yams_mobile_vector_status_result_destroy(result);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
if (result != nullptr)
|
|
yams_mobile_vector_status_result_destroy(result);
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool store_document_with_retry(yams_mobile_context_t* ctx,
|
|
const yams_mobile_document_store_request& request,
|
|
yams_mobile_string_view* outHash,
|
|
std::chrono::milliseconds timeout) {
|
|
const auto deadline = std::chrono::steady_clock::now() + timeout;
|
|
while (std::chrono::steady_clock::now() < deadline) {
|
|
const auto status = yams_mobile_store_document(ctx, &request, outHash);
|
|
if (status == YAMS_MOBILE_STATUS_OK) {
|
|
return true;
|
|
}
|
|
|
|
if (status != YAMS_MOBILE_STATUS_INTERNAL_ERROR &&
|
|
status != YAMS_MOBILE_STATUS_UNAVAILABLE) {
|
|
return false;
|
|
}
|
|
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool wait_for_search_results(yams_mobile_context_t* ctx, const char* query,
|
|
std::chrono::milliseconds timeout) {
|
|
const auto deadline = std::chrono::steady_clock::now() + timeout;
|
|
while (std::chrono::steady_clock::now() < deadline) {
|
|
yams_mobile_search_request searchReq{};
|
|
searchReq.header = yams_mobile_request_header_default();
|
|
searchReq.query = query;
|
|
searchReq.limit = 10;
|
|
|
|
yams_mobile_search_result_t* result = nullptr;
|
|
const auto status = yams_mobile_search_execute(ctx, &searchReq, &result);
|
|
if (status == YAMS_MOBILE_STATUS_OK && result != nullptr) {
|
|
const auto payload = yams_mobile_search_result_json(result);
|
|
if (payload.data != nullptr) {
|
|
const auto parsed = nlohmann::json::parse(std::string(payload.data, payload.length),
|
|
nullptr, false);
|
|
if (!parsed.is_discarded() && parsed.value("total", 0ULL) >= 1U) {
|
|
yams_mobile_search_result_destroy(result);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
if (result != nullptr)
|
|
yams_mobile_search_result_destroy(result);
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void require_stable_update_payload(const nlohmann::json& parsed, const std::string& expectedHash) {
|
|
REQUIRE(parsed.contains("success"));
|
|
REQUIRE(parsed.contains("hash"));
|
|
REQUIRE(parsed.contains("contentUpdated"));
|
|
REQUIRE(parsed.contains("metadataUpdated"));
|
|
REQUIRE(parsed.contains("tagsUpdated"));
|
|
REQUIRE(parsed.contains("updatesApplied"));
|
|
REQUIRE(parsed.contains("tagsAdded"));
|
|
REQUIRE(parsed.contains("tagsRemoved"));
|
|
|
|
CHECK(parsed.at("hash").template get<std::string>() == expectedHash);
|
|
CHECK(parsed.at("success").template get<bool>());
|
|
CHECK(parsed.at("metadataUpdated").template get<bool>());
|
|
CHECK(parsed.at("updatesApplied").template get<std::uint64_t>() == 1U);
|
|
CHECK(parsed.at("tagsUpdated").template get<bool>());
|
|
CHECK(parsed.at("tagsAdded").template get<std::uint64_t>() == 1U);
|
|
CHECK(parsed.at("tagsRemoved").template get<std::uint64_t>() == 0U);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
TEST_CASE("Mobile context create rejects null config pointer", "[mobile][abi]") {
|
|
yams_mobile_context_t* ctx = nullptr;
|
|
CHECK(yams_mobile_context_create(nullptr, &ctx) == YAMS_MOBILE_STATUS_INVALID_ARGUMENT);
|
|
CHECK(ctx == nullptr);
|
|
}
|
|
|
|
TEST_CASE("Mobile context create rejects null context out pointer", "[mobile][abi]") {
|
|
yams_mobile_context_config config = yams_mobile_context_config_default();
|
|
CHECK(yams_mobile_context_create(&config, nullptr) == YAMS_MOBILE_STATUS_INVALID_ARGUMENT);
|
|
}
|
|
|
|
TEST_CASE("Mobile context destroy is safe with null", "[mobile][abi]") {
|
|
yams_mobile_context_destroy(nullptr);
|
|
SUCCEED("no crash on null destroy");
|
|
}
|
|
|
|
TEST_CASE("Mobile ABI version matches header macros", "[mobile][abi]") {
|
|
const auto version = yams_mobile_get_version();
|
|
CHECK(version.major == YAMS_MOBILE_API_VERSION_MAJOR);
|
|
CHECK(version.minor == YAMS_MOBILE_API_VERSION_MINOR);
|
|
CHECK(version.patch == YAMS_MOBILE_API_VERSION_PATCH);
|
|
}
|
|
|
|
TEST_CASE("Mobile corpus document round trip works in embedded mode", "[mobile][abi]") {
|
|
const auto workingDir = make_unique_temp_dir();
|
|
const auto cacheDir = workingDir + "/cache";
|
|
TempDirGuard guard(workingDir);
|
|
|
|
yams_mobile_context_config config = yams_mobile_context_config_default();
|
|
config.working_directory = workingDir.c_str();
|
|
config.cache_directory = cacheDir.c_str();
|
|
|
|
yams_mobile_context_t* ctx = nullptr;
|
|
REQUIRE(yams_mobile_context_create(&config, &ctx) == YAMS_MOBILE_STATUS_OK);
|
|
INFO(yams_mobile_last_error_message());
|
|
REQUIRE(ctx != nullptr);
|
|
|
|
yams::test::FixtureManager fixtures;
|
|
auto spec = yams::test::mobileSearchCorpusSpec();
|
|
auto corpus = fixtures.createSearchCorpus(spec);
|
|
REQUIRE_FALSE(corpus.fixtures.empty());
|
|
|
|
const auto documentPath = corpus.fixtures.front().path.string();
|
|
const char* tags[] = {"mobile", "fixture", nullptr};
|
|
|
|
yams_mobile_document_store_request store{};
|
|
store.header = yams_mobile_request_header_default();
|
|
store.path = documentPath.c_str();
|
|
store.tags = tags;
|
|
store.tag_count = 2;
|
|
store.sync_now = 1;
|
|
|
|
yams_mobile_string_view stored_hash{};
|
|
REQUIRE(store_document_with_retry(ctx, store, &stored_hash, std::chrono::seconds(5)));
|
|
INFO(yams_mobile_last_error_message());
|
|
REQUIRE(stored_hash.length > 0U);
|
|
|
|
yams_mobile_list_request listRequest{};
|
|
listRequest.header = yams_mobile_request_header_default();
|
|
listRequest.limit = 25;
|
|
yams_mobile_list_result_t* listResult = nullptr;
|
|
REQUIRE(yams_mobile_list_documents(ctx, &listRequest, &listResult) == YAMS_MOBILE_STATUS_OK);
|
|
INFO(yams_mobile_last_error_message());
|
|
REQUIRE(listResult != nullptr);
|
|
|
|
const auto listJson = yams_mobile_list_result_json(listResult);
|
|
REQUIRE(listJson.data != nullptr);
|
|
const auto parsed = nlohmann::json::parse(std::string(listJson.data, listJson.length));
|
|
REQUIRE(parsed.contains("documents"));
|
|
const auto& documents = parsed.at("documents");
|
|
REQUIRE_FALSE(documents.empty());
|
|
|
|
const auto hashString = std::string(stored_hash.data, stored_hash.length);
|
|
const bool found = std::any_of(documents.begin(), documents.end(), [&](const auto& entry) {
|
|
return entry.contains("hash") && entry.at("hash").template get<std::string>() == hashString;
|
|
});
|
|
CHECK(found);
|
|
|
|
yams_mobile_list_result_destroy(listResult);
|
|
|
|
const auto documentName = std::filesystem::path(documentPath).filename().string();
|
|
|
|
const char* addTags[] = {"smoke-updated", nullptr};
|
|
const char* removeTags[] = {"fixture", nullptr};
|
|
const char* metadataKeys[] = {"source", nullptr};
|
|
const char* metadataValues[] = {"mobile-smoke", nullptr};
|
|
|
|
yams_mobile_update_request updateReq{};
|
|
updateReq.header = yams_mobile_request_header_default();
|
|
updateReq.hash = hashString.c_str();
|
|
updateReq.add_tags = addTags;
|
|
updateReq.add_tag_count = 1;
|
|
updateReq.remove_tags = removeTags;
|
|
updateReq.remove_tag_count = 1;
|
|
updateReq.metadata_keys = metadataKeys;
|
|
updateReq.metadata_values = metadataValues;
|
|
updateReq.metadata_count = 1;
|
|
|
|
yams_mobile_update_result_t* updateResult = nullptr;
|
|
REQUIRE(yams_mobile_update_document(ctx, &updateReq, &updateResult) == YAMS_MOBILE_STATUS_OK);
|
|
INFO(yams_mobile_last_error_message());
|
|
REQUIRE(updateResult != nullptr);
|
|
const auto updateJson = yams_mobile_update_result_json(updateResult);
|
|
REQUIRE(updateJson.data != nullptr);
|
|
const auto parsedUpdate =
|
|
nlohmann::json::parse(std::string(updateJson.data, updateJson.length));
|
|
CHECK(parsedUpdate.value("success", false));
|
|
yams_mobile_update_result_destroy(updateResult);
|
|
|
|
yams_mobile_search_request searchRequest{};
|
|
searchRequest.header = yams_mobile_request_header_default();
|
|
searchRequest.query = "offline";
|
|
searchRequest.limit = 10;
|
|
yams_mobile_search_result_t* searchResult = nullptr;
|
|
CHECK(yams_mobile_search_execute(ctx, &searchRequest, &searchResult) == YAMS_MOBILE_STATUS_OK);
|
|
INFO(yams_mobile_last_error_message());
|
|
if (searchResult) {
|
|
const auto payload = yams_mobile_search_result_json(searchResult);
|
|
CHECK(payload.data != nullptr);
|
|
yams_mobile_search_result_destroy(searchResult);
|
|
}
|
|
|
|
yams_mobile_delete_request deleteReq{};
|
|
deleteReq.header = yams_mobile_request_header_default();
|
|
deleteReq.name = documentName.c_str();
|
|
deleteReq.dry_run = 0;
|
|
|
|
yams_mobile_delete_result_t* deleteResult = nullptr;
|
|
REQUIRE(yams_mobile_delete_by_name(ctx, &deleteReq, &deleteResult) == YAMS_MOBILE_STATUS_OK);
|
|
INFO(yams_mobile_last_error_message());
|
|
REQUIRE(deleteResult != nullptr);
|
|
const auto deleteJson = yams_mobile_delete_result_json(deleteResult);
|
|
REQUIRE(deleteJson.data != nullptr);
|
|
const auto parsedDelete =
|
|
nlohmann::json::parse(std::string(deleteJson.data, deleteJson.length));
|
|
REQUIRE(parsedDelete.contains("count"));
|
|
CHECK(parsedDelete.at("count").template get<std::size_t>() >= 1U);
|
|
yams_mobile_delete_result_destroy(deleteResult);
|
|
|
|
yams_mobile_document_get_request getAfterDelete{};
|
|
getAfterDelete.header = yams_mobile_request_header_default();
|
|
getAfterDelete.document_hash = hashString.c_str();
|
|
getAfterDelete.metadata_only = 1;
|
|
yams_mobile_document_get_result_t* getAfterDeleteResult = nullptr;
|
|
CHECK(yams_mobile_get_document(ctx, &getAfterDelete, &getAfterDeleteResult) ==
|
|
YAMS_MOBILE_STATUS_NOT_FOUND);
|
|
|
|
yams_mobile_context_destroy(ctx);
|
|
}
|
|
|
|
TEST_CASE("Mobile daemon mode context fails gracefully when socket is missing", "[mobile][abi]") {
|
|
const auto workingDir = make_unique_temp_dir();
|
|
TempDirGuard guard(workingDir);
|
|
|
|
yams_mobile_context_config config = yams_mobile_context_config_default();
|
|
config.working_directory = workingDir.c_str();
|
|
config.backend_mode = YAMS_MOBILE_BACKEND_DAEMON;
|
|
config.daemon_socket_path = "/tmp/yams_mobile_nonexistent_socket_test.sock";
|
|
|
|
yams_mobile_context_t* ctx = nullptr;
|
|
const auto status = yams_mobile_context_create(&config, &ctx);
|
|
|
|
CHECK(status != YAMS_MOBILE_STATUS_OK);
|
|
CHECK(ctx == nullptr);
|
|
|
|
const char* err = yams_mobile_last_error_message();
|
|
REQUIRE(err != nullptr);
|
|
CHECK(std::strlen(err) > 0U);
|
|
}
|
|
|
|
TEST_CASE("Mobile invalid backend mode is rejected", "[mobile][abi]") {
|
|
const auto workingDir = make_unique_temp_dir();
|
|
TempDirGuard guard(workingDir);
|
|
|
|
yams_mobile_context_config config = yams_mobile_context_config_default();
|
|
config.working_directory = workingDir.c_str();
|
|
config.backend_mode = 99;
|
|
|
|
yams_mobile_context_t* ctx = nullptr;
|
|
const auto status = yams_mobile_context_create(&config, &ctx);
|
|
CHECK(status == YAMS_MOBILE_STATUS_INVALID_ARGUMENT);
|
|
CHECK(ctx == nullptr);
|
|
}
|
|
|
|
TEST_CASE("Embedded mobile graph query returns corpus graph state", "[mobile][abi]") {
|
|
const auto workingDir = make_unique_temp_dir();
|
|
TempDirGuard guard(workingDir);
|
|
|
|
yams_mobile_context_config config = yams_mobile_context_config_default();
|
|
config.working_directory = workingDir.c_str();
|
|
config.backend_mode = YAMS_MOBILE_BACKEND_EMBEDDED;
|
|
|
|
yams_mobile_context_t* ctx = nullptr;
|
|
REQUIRE(yams_mobile_context_create(&config, &ctx) == YAMS_MOBILE_STATUS_OK);
|
|
INFO(yams_mobile_last_error_message());
|
|
REQUIRE(ctx != nullptr);
|
|
|
|
yams::test::FixtureManager fixtures;
|
|
auto spec = yams::test::mobileSearchCorpusSpec();
|
|
auto corpus = fixtures.createSearchCorpus(spec);
|
|
REQUIRE_FALSE(corpus.fixtures.empty());
|
|
|
|
const auto documentPath = corpus.fixtures.front().path.string();
|
|
yams_mobile_document_store_request store{};
|
|
store.header = yams_mobile_request_header_default();
|
|
store.path = documentPath.c_str();
|
|
store.sync_now = 1;
|
|
|
|
yams_mobile_string_view stored_hash{};
|
|
REQUIRE(yams_mobile_store_document(ctx, &store, &stored_hash) == YAMS_MOBILE_STATUS_OK);
|
|
INFO(yams_mobile_last_error_message());
|
|
REQUIRE(stored_hash.length > 0U);
|
|
const auto hashString = std::string(stored_hash.data, stored_hash.length);
|
|
|
|
yams_mobile_graph_query_request graphReq{};
|
|
graphReq.header = yams_mobile_request_header_default();
|
|
graphReq.document_hash = hashString.c_str();
|
|
graphReq.max_depth = 1;
|
|
graphReq.max_results = 10;
|
|
yams_mobile_graph_query_result_t* graphResult = nullptr;
|
|
|
|
REQUIRE(yams_mobile_graph_query(ctx, &graphReq, &graphResult) == YAMS_MOBILE_STATUS_OK);
|
|
INFO(yams_mobile_last_error_message());
|
|
REQUIRE(graphResult != nullptr);
|
|
|
|
const auto graphJson = yams_mobile_graph_query_result_json(graphResult);
|
|
REQUIRE(graphJson.data != nullptr);
|
|
const auto parsed = nlohmann::json::parse(std::string(graphJson.data, graphJson.length));
|
|
CHECK(parsed.contains("kgAvailable"));
|
|
CHECK(parsed.at("kgAvailable").template get<bool>());
|
|
CHECK(parsed.contains("originNode"));
|
|
CHECK(parsed.at("originNode").at("documentHash").template get<std::string>() == hashString);
|
|
CHECK(parsed.contains("connectedNodes"));
|
|
CHECK(parsed.contains("edges"));
|
|
|
|
yams_mobile_graph_query_result_destroy(graphResult);
|
|
|
|
yams_mobile_context_destroy(ctx);
|
|
}
|
|
|
|
TEST_CASE("Embedded mobile corpus status reports readiness without warmup control",
|
|
"[mobile][abi]") {
|
|
const auto workingDir = make_unique_temp_dir();
|
|
TempDirGuard guard(workingDir);
|
|
|
|
yams_mobile_context_config config = yams_mobile_context_config_default();
|
|
config.working_directory = workingDir.c_str();
|
|
config.backend_mode = YAMS_MOBILE_BACKEND_EMBEDDED;
|
|
|
|
yams_mobile_context_t* ctx = nullptr;
|
|
REQUIRE(yams_mobile_context_create(&config, &ctx) == YAMS_MOBILE_STATUS_OK);
|
|
INFO(yams_mobile_last_error_message());
|
|
REQUIRE(ctx != nullptr);
|
|
|
|
yams::test::FixtureManager fixtures;
|
|
auto spec = yams::test::mobileSearchCorpusSpec();
|
|
auto corpus = fixtures.createSearchCorpus(spec);
|
|
REQUIRE_FALSE(corpus.fixtures.empty());
|
|
|
|
const auto documentPath = corpus.fixtures.front().path.string();
|
|
yams_mobile_document_store_request store{};
|
|
store.header = yams_mobile_request_header_default();
|
|
store.path = documentPath.c_str();
|
|
|
|
yams_mobile_string_view stored_hash{};
|
|
REQUIRE(yams_mobile_store_document(ctx, &store, &stored_hash) == YAMS_MOBILE_STATUS_OK);
|
|
INFO(yams_mobile_last_error_message());
|
|
REQUIRE(stored_hash.length > 0U);
|
|
|
|
yams_mobile_vector_status_request statusReq{};
|
|
statusReq.header = yams_mobile_request_header_default();
|
|
statusReq.warmup = 1;
|
|
|
|
yams_mobile_vector_status_result_t* statusResult = nullptr;
|
|
REQUIRE(yams_mobile_get_vector_status(ctx, &statusReq, &statusResult) == YAMS_MOBILE_STATUS_OK);
|
|
INFO(yams_mobile_last_error_message());
|
|
REQUIRE(statusResult != nullptr);
|
|
|
|
const auto statusJson = yams_mobile_vector_status_result_json(statusResult);
|
|
REQUIRE(statusJson.data != nullptr);
|
|
const auto parsed = nlohmann::json::parse(std::string(statusJson.data, statusJson.length));
|
|
CHECK(parsed.at("statusType").template get<std::string>() == "corpus");
|
|
CHECK(parsed.at("backendMode").template get<std::string>() == "embedded");
|
|
CHECK(parsed.at("documents").template get<std::uint64_t>() >= 1U);
|
|
CHECK(parsed.at("indexedDocuments").template get<std::uint64_t>() >= 1U);
|
|
CHECK(parsed.at("corpusReady").template get<bool>());
|
|
CHECK(parsed.at("indexReady").template get<bool>());
|
|
CHECK_FALSE(parsed.at("supportsWarmup").template get<bool>());
|
|
CHECK(parsed.at("warmupRequested").template get<bool>());
|
|
CHECK(parsed.at("warmupIgnored").template get<bool>());
|
|
CHECK(parsed.contains("corpusStats"));
|
|
CHECK(parsed.at("corpusStats").at("doc_count").template get<std::int64_t>() >= 1);
|
|
|
|
yams_mobile_vector_status_result_destroy(statusResult);
|
|
yams_mobile_context_destroy(ctx);
|
|
}
|
|
|
|
TEST_CASE("Daemon mobile get_document honors include_content flag", "[mobile][abi][daemon]") {
|
|
yams::test::DaemonHarness harness({.enableAutoRepair = false});
|
|
REQUIRE(harness.start(std::chrono::seconds(10)));
|
|
|
|
const auto workingDir = make_unique_temp_dir();
|
|
TempDirGuard guard(workingDir);
|
|
const auto documentPath = std::filesystem::path(workingDir) / "daemon-mobile-note.txt";
|
|
|
|
{
|
|
std::ofstream out(documentPath);
|
|
REQUIRE(out.is_open());
|
|
out << "offline memory for daemon mobile bindings test\n";
|
|
}
|
|
|
|
yams_mobile_context_config config = yams_mobile_context_config_default();
|
|
config.working_directory = workingDir.c_str();
|
|
config.backend_mode = YAMS_MOBILE_BACKEND_DAEMON;
|
|
const auto daemonSocketPath = harness.socketPath().string();
|
|
config.daemon_socket_path = daemonSocketPath.c_str();
|
|
|
|
yams_mobile_context_t* ctx = nullptr;
|
|
REQUIRE(yams_mobile_context_create(&config, &ctx) == YAMS_MOBILE_STATUS_OK);
|
|
INFO(yams_mobile_last_error_message());
|
|
REQUIRE(ctx != nullptr);
|
|
|
|
yams_mobile_document_store_request store{};
|
|
store.header = yams_mobile_request_header_default();
|
|
store.path = documentPath.c_str();
|
|
|
|
yams_mobile_string_view stored_hash{};
|
|
REQUIRE(yams_mobile_store_document(ctx, &store, &stored_hash) == YAMS_MOBILE_STATUS_OK);
|
|
INFO(yams_mobile_last_error_message());
|
|
REQUIRE(stored_hash.length > 0U);
|
|
|
|
const auto hashString = std::string(stored_hash.data, stored_hash.length);
|
|
REQUIRE(wait_for_document(ctx, hashString, std::chrono::seconds(5)));
|
|
|
|
yams_mobile_document_get_request getWithoutContent{};
|
|
getWithoutContent.header = yams_mobile_request_header_default();
|
|
getWithoutContent.document_hash = hashString.c_str();
|
|
getWithoutContent.include_content = 0;
|
|
getWithoutContent.include_extracted_text = 0;
|
|
|
|
yams_mobile_document_get_result_t* noContentResult = nullptr;
|
|
REQUIRE(yams_mobile_get_document(ctx, &getWithoutContent, &noContentResult) ==
|
|
YAMS_MOBILE_STATUS_OK);
|
|
INFO(yams_mobile_last_error_message());
|
|
REQUIRE(noContentResult != nullptr);
|
|
|
|
const auto noContentPayload = yams_mobile_document_get_result_json(noContentResult);
|
|
REQUIRE(noContentPayload.data != nullptr);
|
|
const auto noContentBody = yams_mobile_document_get_result_content(noContentResult);
|
|
CHECK((noContentBody.data == nullptr || noContentBody.length == 0U));
|
|
yams_mobile_document_get_result_destroy(noContentResult);
|
|
|
|
yams_mobile_document_get_request getWithContent{};
|
|
getWithContent.header = yams_mobile_request_header_default();
|
|
getWithContent.document_hash = hashString.c_str();
|
|
getWithContent.include_content = 1;
|
|
getWithContent.include_extracted_text = 0;
|
|
|
|
yams_mobile_document_get_result_t* withContentResult = nullptr;
|
|
REQUIRE(yams_mobile_get_document(ctx, &getWithContent, &withContentResult) ==
|
|
YAMS_MOBILE_STATUS_OK);
|
|
INFO(yams_mobile_last_error_message());
|
|
REQUIRE(withContentResult != nullptr);
|
|
|
|
const auto withContentBody = yams_mobile_document_get_result_content(withContentResult);
|
|
REQUIRE(withContentBody.data != nullptr);
|
|
CHECK(withContentBody.length > 0U);
|
|
const auto withContentString = std::string(withContentBody.data, withContentBody.length);
|
|
CHECK(withContentString.find("offline memory for daemon mobile bindings test") !=
|
|
std::string::npos);
|
|
yams_mobile_document_get_result_destroy(withContentResult);
|
|
|
|
yams_mobile_context_destroy(ctx);
|
|
harness.stop();
|
|
}
|
|
|
|
TEST_CASE("Mobile update_document returns a stable payload shape across backends",
|
|
"[mobile][abi][parity]") {
|
|
auto run_case = [](yams_mobile_backend_mode backendMode) {
|
|
const auto workingDir = make_unique_temp_dir();
|
|
TempDirGuard guard(workingDir);
|
|
const auto documentPath = std::filesystem::path(workingDir) / "mobile-update-shape.txt";
|
|
|
|
{
|
|
std::ofstream out(documentPath);
|
|
REQUIRE(out.is_open());
|
|
out << "stable update payload\n";
|
|
}
|
|
|
|
std::optional<yams::test::DaemonHarness> harness;
|
|
yams_mobile_context_config config = yams_mobile_context_config_default();
|
|
config.working_directory = workingDir.c_str();
|
|
config.backend_mode = backendMode;
|
|
std::string daemonSocketPath;
|
|
if (backendMode == YAMS_MOBILE_BACKEND_DAEMON) {
|
|
harness.emplace(yams::test::DaemonHarnessOptions{.enableAutoRepair = false});
|
|
REQUIRE(harness->start(std::chrono::seconds(10)));
|
|
daemonSocketPath = harness->socketPath().string();
|
|
config.daemon_socket_path = daemonSocketPath.c_str();
|
|
}
|
|
|
|
yams_mobile_context_t* ctx = nullptr;
|
|
REQUIRE(yams_mobile_context_create(&config, &ctx) == YAMS_MOBILE_STATUS_OK);
|
|
INFO(yams_mobile_last_error_message());
|
|
REQUIRE(ctx != nullptr);
|
|
|
|
yams_mobile_document_store_request store{};
|
|
store.header = yams_mobile_request_header_default();
|
|
store.path = documentPath.c_str();
|
|
|
|
yams_mobile_string_view storedHash{};
|
|
REQUIRE(store_document_with_retry(ctx, store, &storedHash, std::chrono::seconds(5)));
|
|
INFO(yams_mobile_last_error_message());
|
|
REQUIRE(storedHash.length > 0U);
|
|
|
|
const auto hashString = std::string(storedHash.data, storedHash.length);
|
|
if (backendMode == YAMS_MOBILE_BACKEND_DAEMON) {
|
|
REQUIRE(wait_for_document(ctx, hashString, std::chrono::seconds(5)));
|
|
}
|
|
|
|
const char* addTags[] = {"stable-tag", nullptr};
|
|
const char* metadataKeys[] = {"source", nullptr};
|
|
const char* metadataValues[] = {"stable-shape-test", nullptr};
|
|
|
|
yams_mobile_update_request updateReq{};
|
|
updateReq.header = yams_mobile_request_header_default();
|
|
updateReq.hash = hashString.c_str();
|
|
updateReq.add_tags = addTags;
|
|
updateReq.add_tag_count = 1;
|
|
updateReq.metadata_keys = metadataKeys;
|
|
updateReq.metadata_values = metadataValues;
|
|
updateReq.metadata_count = 1;
|
|
|
|
yams_mobile_update_result_t* updateResult = nullptr;
|
|
REQUIRE(yams_mobile_update_document(ctx, &updateReq, &updateResult) ==
|
|
YAMS_MOBILE_STATUS_OK);
|
|
INFO(yams_mobile_last_error_message());
|
|
REQUIRE(updateResult != nullptr);
|
|
|
|
const auto updateJson = yams_mobile_update_result_json(updateResult);
|
|
REQUIRE(updateJson.data != nullptr);
|
|
const auto parsed = nlohmann::json::parse(std::string(updateJson.data, updateJson.length));
|
|
require_stable_update_payload(parsed, hashString);
|
|
|
|
yams_mobile_update_result_destroy(updateResult);
|
|
yams_mobile_context_destroy(ctx);
|
|
if (harness) {
|
|
harness->stop();
|
|
}
|
|
};
|
|
|
|
SECTION("embedded") {
|
|
run_case(YAMS_MOBILE_BACKEND_EMBEDDED);
|
|
}
|
|
|
|
SECTION("daemon") {
|
|
run_case(YAMS_MOBILE_BACKEND_DAEMON);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("Mobile search returns a stable payload shape across backends", "[mobile][abi][parity]") {
|
|
auto run_case = [](yams_mobile_backend_mode backendMode) {
|
|
const auto workingDir = make_unique_temp_dir();
|
|
TempDirGuard guard(workingDir);
|
|
const auto documentPath = std::filesystem::path(workingDir) / "mobile-search-shape.txt";
|
|
|
|
{
|
|
std::ofstream out(documentPath);
|
|
REQUIRE(out.is_open());
|
|
out << "offline search parity test\n";
|
|
}
|
|
|
|
std::optional<yams::test::DaemonHarness> harness;
|
|
yams_mobile_context_config config = yams_mobile_context_config_default();
|
|
config.working_directory = workingDir.c_str();
|
|
config.backend_mode = backendMode;
|
|
std::string daemonSocketPath;
|
|
if (backendMode == YAMS_MOBILE_BACKEND_DAEMON) {
|
|
harness.emplace(yams::test::DaemonHarnessOptions{.enableAutoRepair = false});
|
|
REQUIRE(harness->start(std::chrono::seconds(10)));
|
|
daemonSocketPath = harness->socketPath().string();
|
|
config.daemon_socket_path = daemonSocketPath.c_str();
|
|
}
|
|
|
|
yams_mobile_context_t* ctx = nullptr;
|
|
REQUIRE(yams_mobile_context_create(&config, &ctx) == YAMS_MOBILE_STATUS_OK);
|
|
INFO(yams_mobile_last_error_message());
|
|
REQUIRE(ctx != nullptr);
|
|
|
|
yams_mobile_document_store_request store{};
|
|
store.header = yams_mobile_request_header_default();
|
|
store.path = documentPath.c_str();
|
|
|
|
yams_mobile_string_view storedHash{};
|
|
REQUIRE(store_document_with_retry(ctx, store, &storedHash, std::chrono::seconds(5)));
|
|
INFO(yams_mobile_last_error_message());
|
|
REQUIRE(storedHash.length > 0U);
|
|
|
|
const auto hashString = std::string(storedHash.data, storedHash.length);
|
|
if (backendMode == YAMS_MOBILE_BACKEND_DAEMON) {
|
|
REQUIRE(wait_for_document(ctx, hashString, std::chrono::seconds(5)));
|
|
}
|
|
constexpr const char* kSearchQuery = "mobile-search-shape.txt";
|
|
REQUIRE(wait_for_search_results(ctx, kSearchQuery, std::chrono::seconds(30)));
|
|
|
|
yams_mobile_search_request searchReq{};
|
|
searchReq.header = yams_mobile_request_header_default();
|
|
searchReq.query = kSearchQuery;
|
|
searchReq.limit = 10;
|
|
|
|
yams_mobile_search_result_t* searchResult = nullptr;
|
|
REQUIRE(yams_mobile_search_execute(ctx, &searchReq, &searchResult) ==
|
|
YAMS_MOBILE_STATUS_OK);
|
|
INFO(yams_mobile_last_error_message());
|
|
REQUIRE(searchResult != nullptr);
|
|
|
|
const auto payload = yams_mobile_search_result_json(searchResult);
|
|
REQUIRE(payload.data != nullptr);
|
|
const auto parsed = nlohmann::json::parse(std::string(payload.data, payload.length));
|
|
REQUIRE(parsed.contains("total"));
|
|
REQUIRE(parsed.contains("type"));
|
|
REQUIRE(parsed.contains("executionTimeMs"));
|
|
REQUIRE(parsed.contains("usedHybrid"));
|
|
REQUIRE(parsed.contains("results"));
|
|
CHECK_FALSE(parsed.at("type").template get<std::string>().empty());
|
|
CHECK(parsed.at("usedHybrid").template get<bool>() ==
|
|
(parsed.at("type").template get<std::string>() == "hybrid"));
|
|
|
|
const auto& results = parsed.at("results");
|
|
REQUIRE_FALSE(results.empty());
|
|
const auto& first = results.front();
|
|
CHECK(first.contains("id"));
|
|
CHECK(first.contains("hash"));
|
|
CHECK(first.contains("title"));
|
|
CHECK(first.contains("path"));
|
|
CHECK(first.contains("fileName"));
|
|
CHECK(first.contains("score"));
|
|
CHECK(first.contains("mimeType"));
|
|
CHECK(first.contains("fileType"));
|
|
CHECK(first.contains("size"));
|
|
CHECK(first.contains("created"));
|
|
CHECK(first.contains("modified"));
|
|
CHECK(first.contains("indexed"));
|
|
|
|
yams_mobile_search_result_destroy(searchResult);
|
|
yams_mobile_context_destroy(ctx);
|
|
if (harness) {
|
|
harness->stop();
|
|
}
|
|
};
|
|
|
|
SECTION("embedded") {
|
|
run_case(YAMS_MOBILE_BACKEND_EMBEDDED);
|
|
}
|
|
|
|
SECTION("daemon") {
|
|
run_case(YAMS_MOBILE_BACKEND_DAEMON);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("Mobile list_documents returns a stable payload shape across backends",
|
|
"[mobile][abi][parity]") {
|
|
const auto run_case = [](std::uint32_t backendMode) {
|
|
const auto workingDir = make_unique_temp_dir();
|
|
TempDirGuard guard(workingDir);
|
|
const auto documentPath = std::filesystem::path(workingDir) / "mobile-list-shape.txt";
|
|
|
|
{
|
|
std::ofstream out(documentPath);
|
|
REQUIRE(out.is_open());
|
|
out << "list parity test\n";
|
|
}
|
|
|
|
std::optional<yams::test::DaemonHarness> harness;
|
|
yams_mobile_context_config config = yams_mobile_context_config_default();
|
|
config.working_directory = workingDir.c_str();
|
|
config.backend_mode = backendMode;
|
|
std::string daemonSocketPath;
|
|
if (backendMode == YAMS_MOBILE_BACKEND_DAEMON) {
|
|
harness.emplace(yams::test::DaemonHarnessOptions{.enableAutoRepair = false});
|
|
REQUIRE(harness->start(std::chrono::seconds(10)));
|
|
daemonSocketPath = harness->socketPath().string();
|
|
config.daemon_socket_path = daemonSocketPath.c_str();
|
|
}
|
|
|
|
yams_mobile_context_t* ctx = nullptr;
|
|
REQUIRE(yams_mobile_context_create(&config, &ctx) == YAMS_MOBILE_STATUS_OK);
|
|
INFO(yams_mobile_last_error_message());
|
|
REQUIRE(ctx != nullptr);
|
|
|
|
yams_mobile_document_store_request store{};
|
|
store.header = yams_mobile_request_header_default();
|
|
store.path = documentPath.c_str();
|
|
|
|
yams_mobile_string_view storedHash{};
|
|
REQUIRE(store_document_with_retry(ctx, store, &storedHash, std::chrono::seconds(5)));
|
|
INFO(yams_mobile_last_error_message());
|
|
REQUIRE(storedHash.length > 0U);
|
|
|
|
if (backendMode == YAMS_MOBILE_BACKEND_DAEMON) {
|
|
const auto hashString = std::string(storedHash.data, storedHash.length);
|
|
REQUIRE(wait_for_document(ctx, hashString, std::chrono::seconds(5)));
|
|
}
|
|
|
|
yams_mobile_list_request listReq{};
|
|
listReq.header = yams_mobile_request_header_default();
|
|
listReq.limit = 10;
|
|
|
|
yams_mobile_list_result_t* listResult = nullptr;
|
|
REQUIRE(yams_mobile_list_documents(ctx, &listReq, &listResult) == YAMS_MOBILE_STATUS_OK);
|
|
INFO(yams_mobile_last_error_message());
|
|
REQUIRE(listResult != nullptr);
|
|
|
|
const auto payload = yams_mobile_list_result_json(listResult);
|
|
REQUIRE(payload.data != nullptr);
|
|
const auto parsed = nlohmann::json::parse(std::string(payload.data, payload.length));
|
|
REQUIRE(parsed.contains("count"));
|
|
REQUIRE(parsed.contains("totalFound"));
|
|
REQUIRE(parsed.contains("hasMore"));
|
|
REQUIRE(parsed.contains("executionTimeMs"));
|
|
REQUIRE(parsed.contains("documents"));
|
|
|
|
const auto& documents = parsed.at("documents");
|
|
REQUIRE_FALSE(documents.empty());
|
|
const auto& first = documents.front();
|
|
CHECK(first.contains("name"));
|
|
CHECK(first.contains("fileName"));
|
|
CHECK(first.contains("hash"));
|
|
CHECK(first.contains("path"));
|
|
CHECK(first.contains("extension"));
|
|
CHECK(first.contains("size"));
|
|
CHECK(first.contains("mimeType"));
|
|
CHECK(first.contains("fileType"));
|
|
CHECK(first.contains("created"));
|
|
CHECK(first.contains("modified"));
|
|
CHECK(first.contains("indexed"));
|
|
|
|
yams_mobile_list_result_destroy(listResult);
|
|
yams_mobile_context_destroy(ctx);
|
|
if (harness) {
|
|
harness->stop();
|
|
}
|
|
};
|
|
|
|
SECTION("embedded") {
|
|
run_case(YAMS_MOBILE_BACKEND_EMBEDDED);
|
|
}
|
|
|
|
SECTION("daemon") {
|
|
run_case(YAMS_MOBILE_BACKEND_DAEMON);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("Mobile remove_document returns not_found across backends for missing hashes",
|
|
"[mobile][abi][parity]") {
|
|
const auto run_case = [](std::uint32_t backendMode) {
|
|
const auto workingDir = make_unique_temp_dir();
|
|
TempDirGuard guard(workingDir);
|
|
|
|
std::optional<yams::test::DaemonHarness> harness;
|
|
yams_mobile_context_config config = yams_mobile_context_config_default();
|
|
config.working_directory = workingDir.c_str();
|
|
config.backend_mode = backendMode;
|
|
std::string daemonSocketPath;
|
|
if (backendMode == YAMS_MOBILE_BACKEND_DAEMON) {
|
|
harness.emplace(yams::test::DaemonHarnessOptions{.enableAutoRepair = false});
|
|
REQUIRE(harness->start(std::chrono::seconds(10)));
|
|
daemonSocketPath = harness->socketPath().string();
|
|
config.daemon_socket_path = daemonSocketPath.c_str();
|
|
}
|
|
|
|
yams_mobile_context_t* ctx = nullptr;
|
|
REQUIRE(yams_mobile_context_create(&config, &ctx) == YAMS_MOBILE_STATUS_OK);
|
|
REQUIRE(ctx != nullptr);
|
|
|
|
const auto status = yams_mobile_remove_document(
|
|
ctx, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
|
|
CHECK(status == YAMS_MOBILE_STATUS_NOT_FOUND);
|
|
|
|
yams_mobile_context_destroy(ctx);
|
|
if (harness) {
|
|
harness->stop();
|
|
}
|
|
};
|
|
|
|
SECTION("embedded") {
|
|
run_case(YAMS_MOBILE_BACKEND_EMBEDDED);
|
|
}
|
|
|
|
SECTION("daemon") {
|
|
run_case(YAMS_MOBILE_BACKEND_DAEMON);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("Mobile delete_by_name returns an empty result across backends when nothing matches",
|
|
"[mobile][abi][parity]") {
|
|
const auto run_case = [](std::uint32_t backendMode) {
|
|
const auto workingDir = make_unique_temp_dir();
|
|
TempDirGuard guard(workingDir);
|
|
|
|
std::optional<yams::test::DaemonHarness> harness;
|
|
yams_mobile_context_config config = yams_mobile_context_config_default();
|
|
config.working_directory = workingDir.c_str();
|
|
config.backend_mode = backendMode;
|
|
std::string daemonSocketPath;
|
|
if (backendMode == YAMS_MOBILE_BACKEND_DAEMON) {
|
|
harness.emplace(yams::test::DaemonHarnessOptions{.enableAutoRepair = false});
|
|
REQUIRE(harness->start(std::chrono::seconds(10)));
|
|
daemonSocketPath = harness->socketPath().string();
|
|
config.daemon_socket_path = daemonSocketPath.c_str();
|
|
}
|
|
|
|
yams_mobile_context_t* ctx = nullptr;
|
|
REQUIRE(yams_mobile_context_create(&config, &ctx) == YAMS_MOBILE_STATUS_OK);
|
|
REQUIRE(ctx != nullptr);
|
|
|
|
yams_mobile_delete_request deleteReq{};
|
|
deleteReq.header = yams_mobile_request_header_default();
|
|
deleteReq.name = "missing-mobile-delete.txt";
|
|
|
|
yams_mobile_delete_result_t* deleteResult = nullptr;
|
|
REQUIRE(yams_mobile_delete_by_name(ctx, &deleteReq, &deleteResult) ==
|
|
YAMS_MOBILE_STATUS_OK);
|
|
REQUIRE(deleteResult != nullptr);
|
|
|
|
const auto payload = yams_mobile_delete_result_json(deleteResult);
|
|
REQUIRE(payload.data != nullptr);
|
|
const auto parsed = nlohmann::json::parse(std::string(payload.data, payload.length));
|
|
CHECK(parsed.value("dryRun", true) == false);
|
|
CHECK(parsed.value("count", 1ULL) == 0ULL);
|
|
REQUIRE(parsed.contains("deleted"));
|
|
REQUIRE(parsed.contains("errors"));
|
|
CHECK(parsed.at("deleted").empty());
|
|
CHECK(parsed.at("errors").empty());
|
|
|
|
yams_mobile_delete_result_destroy(deleteResult);
|
|
yams_mobile_context_destroy(ctx);
|
|
if (harness) {
|
|
harness->stop();
|
|
}
|
|
};
|
|
|
|
SECTION("embedded") {
|
|
run_case(YAMS_MOBILE_BACKEND_EMBEDDED);
|
|
}
|
|
|
|
SECTION("daemon") {
|
|
run_case(YAMS_MOBILE_BACKEND_DAEMON);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("Daemon mobile corpus status reports readiness without warmup control",
|
|
"[mobile][abi][daemon]") {
|
|
yams::test::DaemonHarness harness({.enableAutoRepair = false});
|
|
REQUIRE(harness.start(std::chrono::seconds(10)));
|
|
|
|
const auto workingDir = make_unique_temp_dir();
|
|
TempDirGuard guard(workingDir);
|
|
const auto documentPath = std::filesystem::path(workingDir) / "daemon-mobile-status.txt";
|
|
|
|
{
|
|
std::ofstream out(documentPath);
|
|
REQUIRE(out.is_open());
|
|
out << "daemon corpus status test\n";
|
|
}
|
|
|
|
yams_mobile_context_config config = yams_mobile_context_config_default();
|
|
config.working_directory = workingDir.c_str();
|
|
config.backend_mode = YAMS_MOBILE_BACKEND_DAEMON;
|
|
const auto daemonSocketPath = harness.socketPath().string();
|
|
config.daemon_socket_path = daemonSocketPath.c_str();
|
|
|
|
yams_mobile_context_t* ctx = nullptr;
|
|
REQUIRE(yams_mobile_context_create(&config, &ctx) == YAMS_MOBILE_STATUS_OK);
|
|
INFO(yams_mobile_last_error_message());
|
|
REQUIRE(ctx != nullptr);
|
|
|
|
yams_mobile_document_store_request store{};
|
|
store.header = yams_mobile_request_header_default();
|
|
store.path = documentPath.c_str();
|
|
|
|
yams_mobile_string_view stored_hash{};
|
|
REQUIRE(store_document_with_retry(ctx, store, &stored_hash, std::chrono::seconds(5)));
|
|
INFO(yams_mobile_last_error_message());
|
|
REQUIRE(stored_hash.length > 0U);
|
|
|
|
const auto hashString = std::string(stored_hash.data, stored_hash.length);
|
|
REQUIRE(wait_for_document(ctx, hashString, std::chrono::seconds(5)));
|
|
REQUIRE(wait_for_corpus_status_documents(ctx, 1, std::chrono::seconds(5)));
|
|
|
|
yams_mobile_vector_status_request statusReq{};
|
|
statusReq.header = yams_mobile_request_header_default();
|
|
statusReq.warmup = 1;
|
|
|
|
yams_mobile_vector_status_result_t* statusResult = nullptr;
|
|
REQUIRE(yams_mobile_get_vector_status(ctx, &statusReq, &statusResult) == YAMS_MOBILE_STATUS_OK);
|
|
INFO(yams_mobile_last_error_message());
|
|
REQUIRE(statusResult != nullptr);
|
|
|
|
const auto statusJson = yams_mobile_vector_status_result_json(statusResult);
|
|
REQUIRE(statusJson.data != nullptr);
|
|
const auto parsed = nlohmann::json::parse(std::string(statusJson.data, statusJson.length));
|
|
CHECK(parsed.at("statusType").template get<std::string>() == "corpus");
|
|
CHECK(parsed.at("backendMode").template get<std::string>() == "daemon");
|
|
CHECK(parsed.at("documents").template get<std::uint64_t>() >= 1U);
|
|
CHECK(parsed.at("indexedDocuments").template get<std::uint64_t>() >= 1U);
|
|
CHECK(parsed.at("corpusReady").template get<bool>());
|
|
CHECK(parsed.at("indexReady").template get<bool>());
|
|
CHECK_FALSE(parsed.at("supportsWarmup").template get<bool>());
|
|
CHECK(parsed.at("warmupRequested").template get<bool>());
|
|
CHECK(parsed.at("warmupIgnored").template get<bool>());
|
|
CHECK(parsed.contains("corpusStats"));
|
|
CHECK(parsed.at("corpusStats").at("doc_count").template get<std::int64_t>() >= 1);
|
|
|
|
yams_mobile_vector_status_result_destroy(statusResult);
|
|
yams_mobile_context_destroy(ctx);
|
|
harness.stop();
|
|
}
|