// AuthLoads 离线单测:CaptchaLoad/LoginLoad 句柄行为(done/failed/abort 闸门), // 用 FakeApiCall + 真 ApiChain 离线驱动,不联网。QSignalSpy 需 Qt6::Test。 #include #include #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(); 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 steps{ [&](const QList&) -> IApiCall* { return s1; }, [&](const QList&) -> 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 steps{ [&](const QList&) -> 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 steps{ [&](const QList&) -> 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); }