Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 27 additions & 1 deletion rust/src/hosting/nodejs/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ impl HostingClient {
Method::POST,
&format!("/apps/{app_id}/deployments"),
&[],
None,
Some(json!({})),
)
.await
}
Expand Down Expand Up @@ -515,6 +515,32 @@ mod tests {
assert_eq!(body["jobId"], "git-1");
}

#[tokio::test]
async fn publish_app_sends_empty_json_body_and_content_length() {
let server = MockServer::start_async().await;
let mock = server
.mock_async(|when, then| {
when.method(POST)
.path("/v1/hosting/nodejs/apps/app-1/deployments")
.header("content-type", "application/json")
.header_exists("content-length")
.json_body(json!({}));
then.status(200).json_body(json!({
"deploymentId": "dep-1",
"status": "pending"
}));
})
.await;

let body = client(&server.base_url())
.publish_app("app-1")
.await
.expect("publish app");

mock.assert_async().await;
assert_eq!(body["deploymentId"], "dep-1");
}

#[tokio::test]
async fn get_git_import_status_sends_job_id() {
let server = MockServer::start_async().await;
Expand Down
50 changes: 46 additions & 4 deletions rust/src/hosting/nodejs/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -351,10 +351,11 @@ fn job_get_command() -> RuntimeCommandSpec {
|ctx| async move {
let job_id = required_str(&ctx, "job-id", "--job-id")?;
let client = make_client(&ctx, &[APPS_CREATE]).await?;
let data = client
let mut data = client
.get_app_creation_status(&job_id)
.await
.map_err(client_err)?;
promote_job_fields(&mut data);
let status = data
.pointer("/job/status")
.and_then(|v| v.as_str())
Expand Down Expand Up @@ -384,6 +385,17 @@ fn job_get_command() -> RuntimeCommandSpec {
)
}

fn promote_job_fields(data: &mut Value) {
let job = data.get("job").cloned();
if let (Some(Value::Object(job)), Some(obj)) = (job, data.as_object_mut()) {
for key in ["id", "status"] {
if let Some(v) = job.get(key) {
obj.insert(key.to_owned(), v.clone());
}
}
}
}

fn deployment_list_command() -> RuntimeCommandSpec {
RuntimeCommandSpec::new_with_context(
CommandSpec::new("list", "List application deployments")
Expand Down Expand Up @@ -993,7 +1005,13 @@ fn secrets_update_command() -> RuntimeCommandSpec {
fn logs_command() -> RuntimeCommandSpec {
RuntimeCommandSpec::new_with_context(
CommandSpec::new("logs", "Get application logs")
.with_long("Fetch log entries for a Node.js hosting application.")
.with_long(
"Fetch log entries for a Node.js hosting application. \
Choose --source to select which stream to read: `stdout` and \
`stderr` are the running app's own output, while `exec` \
covers platform activity around it (source import, npm \
install, and build output all land here, not in stderr).",
)
.with_system("hosting")
.with_tier(Tier::Read)
.with_scopes(&[LOGS_READ])
Expand All @@ -1017,7 +1035,8 @@ fn logs_command() -> RuntimeCommandSpec {
.long("source")
.value_name("SOURCE")
.required(true)
.help("Log source identifier"),
.value_parser(["stdout", "stderr", "exec"])
.help("Log stream (stdout, stderr, or exec)"),
)
.with_arg(
clap::Arg::new("since")
Expand Down Expand Up @@ -1051,7 +1070,8 @@ fn logs_command() -> RuntimeCommandSpec {

#[cfg(test)]
mod tests {
use super::{is_app_creation_job_terminal, is_source_job_terminal};
use super::{is_app_creation_job_terminal, is_source_job_terminal, promote_job_fields};
use serde_json::json;

#[test]
fn source_job_terminal_status_detection() {
Expand All @@ -1070,4 +1090,26 @@ mod tests {
assert!(!is_app_creation_job_terminal(""));
assert!(!is_app_creation_job_terminal("ACTIVE"));
}

#[test]
fn promote_job_fields_lifts_id_and_status_to_top() {
let mut data = json!({
"job": { "id": "job-1", "status": "active" },
"app": { "id": "app-1", "name": "demo" },
});
promote_job_fields(&mut data);
assert_eq!(data["id"], "job-1");
assert_eq!(data["status"], "active");
assert_eq!(data["job"]["id"], "job-1");
assert_eq!(data["app"]["name"], "demo");
}

#[test]
fn promote_job_fields_is_noop_without_job() {
let mut data = json!({ "app": { "id": "app-1" } });
promote_job_fields(&mut data);
assert!(data.get("id").is_none());
assert!(data.get("status").is_none());
assert_eq!(data["app"]["id"], "app-1");
}
}