// SPDX-License-Identifier: Apache-2.0 // Copyright 2026 //! //! Wiremock smoke tests for `GoogleAuthClient` HTTP layer. No live HTTP. use kei_auth_google::GoogleAuthClient; use serde_json::json; use wiremock::matchers::{body_string_contains, header, method, path}; use wiremock::{Mock, MockServer, ResponseTemplate}; fn client_for(server: &MockServer) -> GoogleAuthClient { GoogleAuthClient::with_urls( format!("{}/token", server.uri()), format!("{}/userinfo", server.uri()), "client-id-xyz", "client-secret-xyz", "https://example.com/cb", ) .unwrap() } #[tokio::test] async fn token_endpoint_200_returns_access_token() { let server = MockServer::start().await; Mock::given(method("POST")) .and(path("/token")) .and(body_string_contains("grant_type=authorization_code")) .and(body_string_contains("code=abc123")) .and(body_string_contains("client_id=client-id-xyz")) .respond_with(ResponseTemplate::new(200).set_body_json(json!({ "access_token": "ya29.a0AfH-test", "expires_in": 3600, "id_token": "eyJ.fake.jwt" }))) .expect(1) .mount(&server) .await; let client = client_for(&server); let token = client.exchange_code("abc123", None).await.unwrap(); assert_eq!(token.access_token, "ya29.a0AfH-test"); assert_eq!(token.expires_in, 3600); assert_eq!(token.id_token.as_deref(), Some("eyJ.fake.jwt")); } #[tokio::test] async fn userinfo_200_returns_email_and_sub() { let server = MockServer::start().await; Mock::given(method("GET")) .and(path("/userinfo")) .and(header("authorization", "Bearer ya29.a0AfH-test")) .respond_with(ResponseTemplate::new(200).set_body_json(json!({ "sub": "1234567890", "email": "alice@example.com", "email_verified": true, "name": "Alice" }))) .expect(1) .mount(&server) .await; let client = client_for(&server); let info = client.userinfo("ya29.a0AfH-test").await.unwrap(); assert_eq!(info.sub, "1234567890"); assert_eq!(info.email, "alice@example.com"); assert!(info.email_verified); assert_eq!(info.name, "Alice"); } #[tokio::test] async fn userinfo_omits_email_verified_defaults_to_false() { let server = MockServer::start().await; Mock::given(method("GET")) .and(path("/userinfo")) .respond_with(ResponseTemplate::new(200).set_body_json(json!({ "sub": "abc", "email": "x@y.z", "name": "X" }))) .expect(1) .mount(&server) .await; let client = client_for(&server); let info = client.userinfo("any").await.unwrap(); // serde_default safe interpretation: absent ⇒ false ⇒ provider rejects. assert!(!info.email_verified); } #[tokio::test] async fn exchange_code_400_returns_api_error() { let server = MockServer::start().await; Mock::given(method("POST")) .and(path("/token")) .respond_with(ResponseTemplate::new(400).set_body_json(json!({ "error": "invalid_grant", "error_description": "Bad code" }))) .expect(1) .mount(&server) .await; let client = client_for(&server); let err = client.exchange_code("bad-code", None).await.unwrap_err(); let msg = format!("{err}"); assert!(msg.contains("api"), "expected api variant, got {msg}"); assert!(msg.contains("400"), "expected status 400 in message, got {msg}"); }