use crate::{
GcpHttpClient, Result,
ops::appengine::AppengineOps,
types::appengine::{Application, Service},
};
pub struct AppEngineClient<'a> {
ops: AppengineOps<'a>,
}
impl<'a> AppEngineClient<'a> {
pub(crate) fn new(client: &'a GcpHttpClient) -> Self {
Self {
ops: AppengineOps::new(client),
}
}
pub async fn get_app(&self, project: &str) -> Result<Application> {
self.ops.get_app(project).await
}
pub async fn list_services(&self, app_id: &str) -> Result<Vec<Service>> {
let mut all = Vec::new();
let mut page_token = String::new();
loop {
let resp = self.ops.list_services(app_id, "100", &page_token).await?;
all.extend(resp.services);
match resp.next_page_token {
Some(tok) if !tok.is_empty() => page_token = tok,
_ => break,
}
}
Ok(all)
}
pub async fn get_service(&self, app_id: &str, service_id: &str) -> Result<Service> {
self.ops.get_service(app_id, service_id).await
}
}
#[cfg(test)]
mod tests {
use serde_json::json;
#[tokio::test]
async fn test_get_app() {
let mut mock = crate::MockClient::new();
mock.expect_get("/v1/apps/my-project")
.returning_json(json!({
"name": "apps/my-project",
"id": "my-project",
"locationId": "us-central",
"servingStatus": "SERVING",
"defaultHostname": "my-project.appspot.com",
"sslPolicy": "DEFAULT"
}))
.times(1);
let client = crate::GcpHttpClient::from_mock(mock);
let ae = client.appengine();
let result = ae.get_app("my-project").await;
assert!(result.is_ok());
let app = result.unwrap();
assert_eq!(app.name, "apps/my-project");
assert_eq!(app.id.as_deref(), Some("my-project"));
assert_eq!(app.location_id.as_deref(), Some("us-central"));
assert_eq!(app.serving_status.as_deref(), Some("SERVING"));
assert_eq!(app.ssl_policy.as_deref(), Some("DEFAULT"));
}
#[tokio::test]
async fn test_list_services() {
let mut mock = crate::MockClient::new();
mock.expect_get("/v1/apps/my-project/services?pageSize=100")
.returning_json(json!({
"services": [
{
"name": "apps/my-project/services/default",
"id": "default",
"networkSettings": {
"ingressTrafficAllowed": "INGRESS_TRAFFIC_ALLOWED_ALL"
}
}
]
}))
.times(1);
let client = crate::GcpHttpClient::from_mock(mock);
let ae = client.appengine();
let result = ae.list_services("my-project").await;
assert!(result.is_ok());
let services = result.unwrap();
assert_eq!(services.len(), 1);
assert_eq!(services[0].name, "apps/my-project/services/default");
assert_eq!(services[0].id.as_deref(), Some("default"));
let ns = services[0].network_settings.as_ref().unwrap();
assert_eq!(
ns.ingress_traffic_allowed.as_deref(),
Some("INGRESS_TRAFFIC_ALLOWED_ALL")
);
}
#[tokio::test]
async fn test_list_services_paginated() {
let mut mock = crate::MockClient::new();
mock.expect_get("/v1/apps/my-project/services?pageSize=100&pageToken=token456")
.returning_json(json!({
"services": [{"name": "apps/my-project/services/api", "id": "api"}]
}))
.times(1);
mock.expect_get("/v1/apps/my-project/services?pageSize=100")
.returning_json(json!({
"services": [{"name": "apps/my-project/services/default", "id": "default"}],
"nextPageToken": "token456"
}))
.times(1);
let client = crate::GcpHttpClient::from_mock(mock);
let ae = client.appengine();
let result = ae.list_services("my-project").await;
assert!(result.is_ok());
let services = result.unwrap();
assert_eq!(services.len(), 2);
assert_eq!(services[0].id.as_deref(), Some("default"));
assert_eq!(services[1].id.as_deref(), Some("api"));
}
#[tokio::test]
async fn test_get_service() {
let mut mock = crate::MockClient::new();
mock.expect_get("/v1/apps/my-project/services/default")
.returning_json(json!({
"name": "apps/my-project/services/default",
"id": "default",
"networkSettings": {
"ingressTrafficAllowed": "INGRESS_TRAFFIC_ALLOWED_INTERNAL_ONLY"
},
"labels": {"env": "prod"}
}))
.times(1);
let client = crate::GcpHttpClient::from_mock(mock);
let ae = client.appengine();
let result = ae.get_service("my-project", "default").await;
assert!(result.is_ok());
let svc = result.unwrap();
assert_eq!(svc.name, "apps/my-project/services/default");
assert_eq!(svc.id.as_deref(), Some("default"));
let ns = svc.network_settings.as_ref().unwrap();
assert_eq!(
ns.ingress_traffic_allowed.as_deref(),
Some("INGRESS_TRAFFIC_ALLOWED_INTERNAL_ONLY")
);
assert_eq!(svc.labels.get("env").map(|s| s.as_str()), Some("prod"));
}
}