|
|
@@ -0,0 +1,89 @@
|
|
|
+#pragma once
|
|
|
+#include "db_test_util.hpp"
|
|
|
+#include "server.hpp"
|
|
|
+#include <gtest/gtest.h>
|
|
|
+#include <httplib.h>
|
|
|
+#include <chrono>
|
|
|
+#include <memory>
|
|
|
+#include <thread>
|
|
|
+#include <vector>
|
|
|
+
|
|
|
+namespace svapi::testutil {
|
|
|
+
|
|
|
+class ApiFixture : public ::testing::Test {
|
|
|
+protected:
|
|
|
+ std::unique_ptr<DbGateway> db_;
|
|
|
+ std::unique_ptr<CollectionRegistry> registry_;
|
|
|
+ std::unique_ptr<SettingsStore> settings_;
|
|
|
+ std::unique_ptr<KeyStore> keys_;
|
|
|
+ std::unique_ptr<SessionStore> sessions_;
|
|
|
+ std::unique_ptr<ApiServer> 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_->ensureProject(project_);
|
|
|
+
|
|
|
+ registry_ = std::make_unique<CollectionRegistry>(*db_);
|
|
|
+ registry_->ensure(project_);
|
|
|
+ settings_ = std::make_unique<SettingsStore>(*db_, settingsColl_, "current");
|
|
|
+ settings_->bootstrap("");
|
|
|
+ keys_ = std::make_unique<KeyStore>(*db_, keysColl_);
|
|
|
+ keys_->bootstrap("");
|
|
|
+ adminKey_ = keys_->list().at(0).key;
|
|
|
+ sessions_ = std::make_unique<SessionStore>(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<ApiServer>(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
|