#pragma once #include "db_test_util.hpp" #include "server.hpp" #include #include #include #include #include #include namespace svapi::testutil { class ApiFixture : public ::testing::Test { protected: std::unique_ptr db_; std::unique_ptr registry_; std::unique_ptr settings_; std::unique_ptr keys_; std::unique_ptr sessions_; std::unique_ptr server_; std::thread serverThread_; int port_ = 0; std::string adminKey_; std::string project_; // a project the admin owns; tests create collections under it httplib::Server mockOpenAi_; std::thread mockThread_; int mockPort_ = 0, mockDim_ = 4; std::string settingsColl_ = "svapitest_api_settings"; std::string keysColl_ = "svapitest_api_keys"; void SetUp() override { db_ = connectOrNull(); if (!db_) GTEST_SKIP() << "smartbotic-database not reachable"; project_ = tmpName("apiproj"); db_->client().dropCollection(settingsColl_); db_->client().dropCollection(keysColl_); db_->dropProject(project_); // DB v2.3.1 Stage-F caveat: dropProject does not delete collection data. // Drop the project's registry collection explicitly so ensure() starts fresh. db_->client().dropCollection(qualify(project_, "vectorapi_collections")); db_->ensureProject(project_); registry_ = std::make_unique(*db_); registry_->ensure(project_); settings_ = std::make_unique(*db_, settingsColl_, "current"); settings_->bootstrap(""); keys_ = std::make_unique(*db_, keysColl_); keys_->bootstrap(""); adminKey_ = keys_->list().at(0).key; sessions_ = std::make_unique(std::chrono::minutes(60)); mockOpenAi_.Post("/v1/embeddings", [this](const httplib::Request&, httplib::Response& res) { nlohmann::json emb = nlohmann::json::array(); for (int i = 0; i < mockDim_; ++i) emb.push_back(0.1f * (i + 1)); res.set_content(nlohmann::json{{"data", {{{"embedding", emb}}}}}.dump(), "application/json"); }); mockPort_ = mockOpenAi_.bind_to_any_port("127.0.0.1"); mockThread_ = std::thread([this]{ mockOpenAi_.listen_after_bind(); }); Settings s = *settings_->snapshot(); s.openaiApiBase = "http://127.0.0.1:" + std::to_string(mockPort_); s.openaiApiKey = "test"; settings_->save(s); ServerDeps deps{*db_, *settings_, *keys_, *registry_, *sessions_, "", apiDocsDir()}; server_ = std::make_unique(std::move(deps)); server_->registerRoutes(); port_ = server_->bindToAnyPort("127.0.0.1"); serverThread_ = std::thread([this]{ server_->listenAfterBind(); }); httplib::Client probe("127.0.0.1", port_); for (int i = 0; i < 200; ++i) { if (probe.Get("/healthz")) break; std::this_thread::sleep_for(std::chrono::milliseconds(10)); } } void TearDown() override { if (server_) server_->stop(); if (serverThread_.joinable()) serverThread_.join(); mockOpenAi_.stop(); if (mockThread_.joinable()) mockThread_.join(); if (db_) { db_->client().dropCollection(settingsColl_); db_->client().dropCollection(keysColl_); db_->dropProject(project_); } } httplib::Client http(const std::string& bearer) { httplib::Client c("127.0.0.1", port_); c.set_default_headers({{"Authorization", "Bearer " + bearer}}); return c; } httplib::Client admin() { return http(adminKey_); } httplib::Client noAuth() { return httplib::Client("127.0.0.1", port_); } static std::string apiDocsDir() { return SVAPI_API_DOCS_DIR; } }; } // namespace svapi::testutil