api_fixture.hpp 3.6 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889
  1. #pragma once
  2. #include "db_test_util.hpp"
  3. #include "server.hpp"
  4. #include <gtest/gtest.h>
  5. #include <httplib.h>
  6. #include <chrono>
  7. #include <memory>
  8. #include <thread>
  9. #include <vector>
  10. namespace svapi::testutil {
  11. class ApiFixture : public ::testing::Test {
  12. protected:
  13. std::unique_ptr<DbGateway> db_;
  14. std::unique_ptr<CollectionRegistry> registry_;
  15. std::unique_ptr<SettingsStore> settings_;
  16. std::unique_ptr<KeyStore> keys_;
  17. std::unique_ptr<SessionStore> sessions_;
  18. std::unique_ptr<ApiServer> server_;
  19. std::thread serverThread_;
  20. int port_ = 0;
  21. std::string adminKey_;
  22. std::string project_; // a project the admin owns; tests create collections under it
  23. httplib::Server mockOpenAi_;
  24. std::thread mockThread_;
  25. int mockPort_ = 0, mockDim_ = 4;
  26. std::string settingsColl_ = "svapitest_api_settings";
  27. std::string keysColl_ = "svapitest_api_keys";
  28. void SetUp() override {
  29. db_ = connectOrNull();
  30. if (!db_) GTEST_SKIP() << "smartbotic-database not reachable";
  31. project_ = tmpName("apiproj");
  32. db_->client().dropCollection(settingsColl_);
  33. db_->client().dropCollection(keysColl_);
  34. db_->dropProject(project_);
  35. db_->ensureProject(project_);
  36. registry_ = std::make_unique<CollectionRegistry>(*db_);
  37. registry_->ensure(project_);
  38. settings_ = std::make_unique<SettingsStore>(*db_, settingsColl_, "current");
  39. settings_->bootstrap("");
  40. keys_ = std::make_unique<KeyStore>(*db_, keysColl_);
  41. keys_->bootstrap("");
  42. adminKey_ = keys_->list().at(0).key;
  43. sessions_ = std::make_unique<SessionStore>(std::chrono::minutes(60));
  44. mockOpenAi_.Post("/v1/embeddings", [this](const httplib::Request&, httplib::Response& res) {
  45. nlohmann::json emb = nlohmann::json::array();
  46. for (int i = 0; i < mockDim_; ++i) emb.push_back(0.1f * (i + 1));
  47. res.set_content(nlohmann::json{{"data", {{{"embedding", emb}}}}}.dump(), "application/json");
  48. });
  49. mockPort_ = mockOpenAi_.bind_to_any_port("127.0.0.1");
  50. mockThread_ = std::thread([this]{ mockOpenAi_.listen_after_bind(); });
  51. Settings s = *settings_->snapshot();
  52. s.openaiApiBase = "http://127.0.0.1:" + std::to_string(mockPort_);
  53. s.openaiApiKey = "test";
  54. settings_->save(s);
  55. ServerDeps deps{*db_, *settings_, *keys_, *registry_, *sessions_, "", apiDocsDir()};
  56. server_ = std::make_unique<ApiServer>(std::move(deps));
  57. server_->registerRoutes();
  58. port_ = server_->bindToAnyPort("127.0.0.1");
  59. serverThread_ = std::thread([this]{ server_->listenAfterBind(); });
  60. httplib::Client probe("127.0.0.1", port_);
  61. for (int i = 0; i < 200; ++i) { if (probe.Get("/healthz")) break;
  62. std::this_thread::sleep_for(std::chrono::milliseconds(10)); }
  63. }
  64. void TearDown() override {
  65. if (server_) server_->stop();
  66. if (serverThread_.joinable()) serverThread_.join();
  67. mockOpenAi_.stop();
  68. if (mockThread_.joinable()) mockThread_.join();
  69. if (db_) { db_->client().dropCollection(settingsColl_);
  70. db_->client().dropCollection(keysColl_);
  71. db_->dropProject(project_); }
  72. }
  73. httplib::Client http(const std::string& bearer) {
  74. httplib::Client c("127.0.0.1", port_);
  75. c.set_default_headers({{"Authorization", "Bearer " + bearer}});
  76. return c;
  77. }
  78. httplib::Client admin() { return http(adminKey_); }
  79. httplib::Client noAuth() { return httplib::Client("127.0.0.1", port_); }
  80. static std::string apiDocsDir() { return SVAPI_API_DOCS_DIR; }
  81. };
  82. } // namespace svapi::testutil