154 lines
5.3 KiB
C++
154 lines
5.3 KiB
C++
// AuthLoads 离线单测:CaptchaLoad/LoginLoad 句柄行为(done/failed/abort 闸门),
|
||
// 用 FakeApiCall + 真 ApiChain 离线驱动,不联网。QSignalSpy 需 Qt6::Test。
|
||
#include <gtest/gtest.h>
|
||
|
||
#include <stdexcept>
|
||
|
||
#include <QSignalSpy>
|
||
|
||
#include "ApiChain.hpp"
|
||
#include "ApiClient.hpp"
|
||
#include "AuthLoads.hpp"
|
||
#include "AuthService.hpp"
|
||
#include "net/FakeApiCall.hpp"
|
||
|
||
using namespace geopro::net;
|
||
using geopro::net::test::FakeApiCall;
|
||
|
||
namespace {
|
||
|
||
ApiResponse captchaOk() {
|
||
ApiResponse r;
|
||
r.code = 200;
|
||
r.httpStatus = 200;
|
||
r.data = QJsonObject{{"id", "cid-1"}, {"code", "AB12"}};
|
||
return r;
|
||
}
|
||
|
||
ApiResponse tokenOk() {
|
||
ApiResponse r;
|
||
r.code = 200;
|
||
r.httpStatus = 200;
|
||
r.data = QJsonObject{{"accessToken", "Geomative deadbeef"}};
|
||
return r;
|
||
}
|
||
|
||
ApiResponse plainOk() {
|
||
ApiResponse r;
|
||
r.code = 200;
|
||
r.httpStatus = 200;
|
||
return r;
|
||
}
|
||
|
||
ApiResponse failResp() {
|
||
ApiResponse r;
|
||
r.code = 500;
|
||
r.httpStatus = 200;
|
||
r.msg = QStringLiteral("验证码错误");
|
||
return r;
|
||
}
|
||
|
||
auto isFailure = [](const ApiResponse& r) { return r.code != 200 || !r.rawError.isEmpty(); };
|
||
|
||
} // namespace
|
||
|
||
TEST(CaptchaLoad, ParsesCaptchaOnSuccess) {
|
||
auto* call = new FakeApiCall;
|
||
auto* load = new CaptchaLoad(call);
|
||
QSignalSpy doneSpy(load, &CaptchaLoad::done);
|
||
QSignalSpy failSpy(load, &CaptchaLoad::failed);
|
||
call->fire(captchaOk());
|
||
ASSERT_EQ(doneSpy.count(), 1);
|
||
EXPECT_EQ(failSpy.count(), 0);
|
||
auto cap = doneSpy.takeFirst().at(0).value<AuthService::Captcha>();
|
||
EXPECT_EQ(cap.codeId, QStringLiteral("cid-1"));
|
||
EXPECT_EQ(cap.code, QStringLiteral("AB12"));
|
||
}
|
||
|
||
TEST(CaptchaLoad, EmitsFailedOnErrorResponse) {
|
||
auto* call = new FakeApiCall;
|
||
auto* load = new CaptchaLoad(call);
|
||
QSignalSpy doneSpy(load, &CaptchaLoad::done);
|
||
QSignalSpy failSpy(load, &CaptchaLoad::failed);
|
||
call->fire(failResp());
|
||
EXPECT_EQ(doneSpy.count(), 0);
|
||
ASSERT_EQ(failSpy.count(), 1);
|
||
EXPECT_EQ(failSpy.takeFirst().at(0).toString(), QStringLiteral("验证码错误"));
|
||
}
|
||
|
||
TEST(CaptchaLoad, AbortGateSuppressesLateSignal) {
|
||
auto* call = new FakeApiCall;
|
||
auto* load = new CaptchaLoad(call);
|
||
QSignalSpy doneSpy(load, &CaptchaLoad::done);
|
||
load->abort();
|
||
EXPECT_TRUE(call->aborted);
|
||
call->fire(captchaOk()); // 迟到
|
||
EXPECT_EQ(doneSpy.count(), 0);
|
||
}
|
||
|
||
TEST(LoginLoad, EmitsTokenOnChainSuccess) {
|
||
auto* s1 = new FakeApiCall;
|
||
auto* s2 = new FakeApiCall;
|
||
QList<ApiChain::StepFactory> steps{
|
||
[&](const QList<ApiResponse>&) -> IApiCall* { return s1; },
|
||
[&](const QList<ApiResponse>&) -> IApiCall* { return s2; }};
|
||
auto* chain = new ApiChain(steps, isFailure);
|
||
auto* load = new LoginLoad(chain);
|
||
QSignalSpy doneSpy(load, &LoginLoad::done);
|
||
QSignalSpy failSpy(load, &LoginLoad::failed);
|
||
s1->fire(plainOk()); // verifyCodeCheck 通过 → 触发 step2
|
||
s2->fire(tokenOk()); // login2 返回 token
|
||
ASSERT_EQ(doneSpy.count(), 1);
|
||
EXPECT_EQ(failSpy.count(), 0);
|
||
EXPECT_EQ(doneSpy.takeFirst().at(0).toString(), QStringLiteral("Geomative deadbeef"));
|
||
}
|
||
|
||
TEST(LoginLoad, EmitsFailedWhenChainFails) {
|
||
auto* s1 = new FakeApiCall;
|
||
QList<ApiChain::StepFactory> steps{
|
||
[&](const QList<ApiResponse>&) -> IApiCall* { return s1; }};
|
||
auto* chain = new ApiChain(steps, isFailure);
|
||
auto* load = new LoginLoad(chain);
|
||
QSignalSpy doneSpy(load, &LoginLoad::done);
|
||
QSignalSpy failSpy(load, &LoginLoad::failed);
|
||
s1->fire(failResp());
|
||
EXPECT_EQ(doneSpy.count(), 0);
|
||
ASSERT_EQ(failSpy.count(), 1);
|
||
EXPECT_EQ(failSpy.takeFirst().at(0).toString(), QStringLiteral("验证码错误"));
|
||
}
|
||
|
||
TEST(LoginLoad, EmitsFailedWhenTokenMissing) {
|
||
auto* s1 = new FakeApiCall;
|
||
QList<ApiChain::StepFactory> steps{
|
||
[&](const QList<ApiResponse>&) -> IApiCall* { return s1; }};
|
||
auto* chain = new ApiChain(steps, isFailure);
|
||
auto* load = new LoginLoad(chain);
|
||
QSignalSpy doneSpy(load, &LoginLoad::done);
|
||
QSignalSpy failSpy(load, &LoginLoad::failed);
|
||
s1->fire(plainOk()); // 成功但无 accessToken
|
||
EXPECT_EQ(doneSpy.count(), 0);
|
||
ASSERT_EQ(failSpy.count(), 1);
|
||
}
|
||
|
||
// M-2:step 工厂抛异常(模拟 RSA 失败)时 LoginLoad 应 emit failed。
|
||
// 第一步用 FakeApiCall(不同步 fire,由测试手动触发),第二步工厂抛异常。
|
||
// 构造顺序:new ApiChain(同步执行 step1 工厂,s1 已建立但未 fire)
|
||
// → new LoginLoad(连接 chain 的 succeeded/failed)
|
||
// → s1->fire(plainOk())(step1 成功 → step2 工厂抛 → ApiChain emit failed
|
||
// → LoginLoad 已连接,收到 failed)。
|
||
TEST(LoginLoad, EmitsFailedWhenStepFactoryThrows) {
|
||
auto* s1 = new FakeApiCall;
|
||
QList<ApiChain::StepFactory> steps{
|
||
[&](const QList<ApiResponse>&) -> IApiCall* { return s1; },
|
||
[&](const QList<ApiResponse>&) -> IApiCall* {
|
||
throw std::runtime_error("rsa fail");
|
||
}};
|
||
auto* chain = new ApiChain(steps, isFailure);
|
||
auto* load = new LoginLoad(chain);
|
||
QSignalSpy doneSpy(load, &LoginLoad::done);
|
||
QSignalSpy failSpy(load, &LoginLoad::failed);
|
||
s1->fire(plainOk()); // step1 成功 → step2 工厂抛异常 → ApiChain emit failed
|
||
ASSERT_EQ(failSpy.count(), 1);
|
||
EXPECT_EQ(doneSpy.count(), 0);
|
||
}
|