文件
csp/backend/tests/lark_http_test.cc
2026-02-23 20:02:46 +08:00

150 行
4.9 KiB
C++

#include <catch2/catch_test_macros.hpp>
#include "csp/app_state.h"
#include "csp/controllers/lark_controller.h"
#include "csp/services/crawler_service.h"
#include "csp/services/lark_bot_service.h"
#include <drogon/HttpRequest.h>
#include <cstdlib>
#include <future>
#include <optional>
#include <string>
namespace {
class ScopedEnv {
public:
ScopedEnv(std::string key, std::optional<std::string> value)
: key_(std::move(key)) {
const char* old = std::getenv(key_.c_str());
if (old) old_ = std::string(old);
if (value.has_value()) {
::setenv(key_.c_str(), value->c_str(), 1);
} else {
::unsetenv(key_.c_str());
}
}
~ScopedEnv() {
if (old_.has_value()) {
::setenv(key_.c_str(), old_->c_str(), 1);
} else {
::unsetenv(key_.c_str());
}
}
private:
std::string key_;
std::optional<std::string> old_;
};
drogon::HttpResponsePtr CallEvents(csp::controllers::LarkController& ctl,
const Json::Value& body) {
auto req = drogon::HttpRequest::newHttpJsonRequest(body);
req->setMethod(drogon::Post);
std::promise<drogon::HttpResponsePtr> p;
ctl.events(req, [&p](const drogon::HttpResponsePtr& resp) { p.set_value(resp); });
return p.get_future().get();
}
Json::Value MakeTextEventBody() {
Json::Value body;
body["header"]["event_type"] = "im.message.receive_v1";
body["header"]["event_id"] = "evt-1";
body["event"]["sender"]["sender_id"]["open_id"] = "ou_xxx";
body["event"]["message"]["message_type"] = "text";
body["event"]["message"]["message_id"] = "om_xxx";
body["event"]["message"]["chat_id"] = "oc_xxx";
Json::Value content;
content["text"] = "你好";
Json::StreamWriterBuilder wb;
wb["indentation"] = "";
body["event"]["message"]["content"] = Json::writeString(wb, content);
return body;
}
} // namespace
TEST_CASE("lark url verification challenge pass") {
ScopedEnv enabled("CSP_LARK_BOT_ENABLED", "1");
ScopedEnv token("CSP_LARK_VERIFICATION_TOKEN", "verify_token");
ScopedEnv app_id("CSP_LARK_APP_ID", "cli_test");
ScopedEnv app_secret("CSP_LARK_APP_SECRET", "secret_test");
csp::services::LarkBotService::Instance().ConfigureFromEnv();
csp::controllers::LarkController ctl;
Json::Value body;
body["challenge"] = "challenge-abc";
body["token"] = "verify_token";
auto resp = CallEvents(ctl, body);
REQUIRE(resp->statusCode() == drogon::k200OK);
const auto json = resp->jsonObject();
REQUIRE(json != nullptr);
REQUIRE((*json)["challenge"].asString() == "challenge-abc");
}
TEST_CASE("lark url verification token mismatch") {
ScopedEnv enabled("CSP_LARK_BOT_ENABLED", "1");
ScopedEnv token("CSP_LARK_VERIFICATION_TOKEN", "verify_token");
ScopedEnv app_id("CSP_LARK_APP_ID", "cli_test");
ScopedEnv app_secret("CSP_LARK_APP_SECRET", "secret_test");
csp::services::LarkBotService::Instance().ConfigureFromEnv();
csp::controllers::LarkController ctl;
Json::Value body;
body["challenge"] = "challenge-abc";
body["token"] = "bad_token";
auto resp = CallEvents(ctl, body);
REQUIRE(resp->statusCode() == drogon::k401Unauthorized);
}
TEST_CASE("lark events ignored when bot disabled") {
ScopedEnv enabled("CSP_LARK_BOT_ENABLED", "0");
ScopedEnv token("CSP_LARK_VERIFICATION_TOKEN", std::nullopt);
ScopedEnv app_id("CSP_LARK_APP_ID", std::nullopt);
ScopedEnv app_secret("CSP_LARK_APP_SECRET", std::nullopt);
csp::services::LarkBotService::Instance().ConfigureFromEnv();
csp::controllers::LarkController ctl;
auto resp = CallEvents(ctl, MakeTextEventBody());
REQUIRE(resp->statusCode() == drogon::k200OK);
const auto json = resp->jsonObject();
REQUIRE(json != nullptr);
REQUIRE((*json)["code"].asInt() == 0);
}
TEST_CASE("lark text url queued into crawler targets") {
csp::AppState::Instance().Init(":memory:");
ScopedEnv enabled("CSP_LARK_BOT_ENABLED", "1");
ScopedEnv token("CSP_LARK_VERIFICATION_TOKEN", std::nullopt);
ScopedEnv app_id("CSP_LARK_APP_ID", "cli_test");
ScopedEnv app_secret("CSP_LARK_APP_SECRET", "secret_test");
ScopedEnv open_base("CSP_LARK_OPEN_BASE_URL", "invalid-url");
csp::services::LarkBotService::Instance().ConfigureFromEnv();
csp::controllers::LarkController ctl;
auto body = MakeTextEventBody();
Json::Value content;
content["text"] = "请收录 https://one.hao.work/news/?a=1";
Json::StreamWriterBuilder wb;
wb["indentation"] = "";
body["event"]["message"]["content"] = Json::writeString(wb, content);
auto resp = CallEvents(ctl, body);
REQUIRE(resp->statusCode() == drogon::k200OK);
const auto json = resp->jsonObject();
REQUIRE(json != nullptr);
REQUIRE((*json)["code"].asInt() == 0);
REQUIRE((*json)["msg"].asString() == "crawler targets queued");
csp::services::CrawlerService crawler(csp::AppState::Instance().db());
const auto targets = crawler.ListTargets("", 10);
REQUIRE(targets.size() == 1);
REQUIRE(targets[0].normalized_url == "https://one.hao.work/news");
}