From 35539234ff651ab0c40b80aaa9cf6ffaeaf58f2d Mon Sep 17 00:00:00 2001 From: Origami404 Date: Wed, 8 Apr 2026 22:58:16 +0800 Subject: [PATCH] feat: implement fetch pipeline and e2e coverage --- src/lib.rs | 25 ++++- tests/e2e_smoke_tests.rs | 196 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 218 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index e179de8..efc50f7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,4 @@ +use std::env; use std::path::Path; use clap::Parser; @@ -11,6 +12,8 @@ pub mod output; pub mod render; use crate::cli::{Cli, Commands, OutputFormat}; +use crate::gitea::client::GiteaClient; +use crate::normalize::normalize_bundle; use crate::output::write_output; use crate::render::json::{parse_json, render_json}; use crate::render::markdown::render_markdown; @@ -25,11 +28,27 @@ pub fn run() -> anyhow::Result<()> { let md = render_markdown(&doc); write_output(args.out.as_deref().map(Path::new), &md)?; } - Commands::Fetch(_args) => { - let _ = OutputFormat::Markdown; - let _ = render_json; + Commands::Fetch(args) => { + let token = required_env("GITEA_PR_CLI_API_TOKEN")?; + let base_url = required_env("GITEA_PR_CLI_URL")?; + let repo = required_env("GITEA_PR_CLI_REPO")?; + + let client = GiteaClient::new(base_url, token); + let bundle = client.fetch_pr_bundle(&repo, args.pr_index)?; + let doc = normalize_bundle(&repo, bundle); + + let rendered = match args.format { + OutputFormat::Markdown => render_markdown(&doc), + OutputFormat::Json => render_json(&doc)?, + }; + + write_output(args.out.as_deref().map(Path::new), &rendered)?; } } Ok(()) } + +fn required_env(name: &'static str) -> anyhow::Result { + env::var(name).map_err(|_| anyhow::anyhow!("missing required environment variable: {name}")) +} diff --git a/tests/e2e_smoke_tests.rs b/tests/e2e_smoke_tests.rs index a024bc4..777926a 100644 --- a/tests/e2e_smoke_tests.rs +++ b/tests/e2e_smoke_tests.rs @@ -1,4 +1,5 @@ use assert_cmd::Command; +use mockito::Server; use tempfile::NamedTempFile; #[test] @@ -92,3 +93,198 @@ fn render_md_writes_to_out_file_when_requested() { assert!(written.contains("# org/repo `#2` t2")); assert!(written.contains("## Metadata")); } + +#[test] +fn fetch_fails_when_required_env_is_missing() { + let assert = Command::cargo_bin("gitea-pr-review") + .unwrap() + .args(["fetch", "1"]) + .env_remove("GITEA_PR_CLI_API_TOKEN") + .env_remove("GITEA_PR_CLI_URL") + .env_remove("GITEA_PR_CLI_REPO") + .assert() + .failure(); + + let stderr = String::from_utf8(assert.get_output().stderr.clone()).unwrap(); + assert!( + stderr.contains("missing required environment variable: GITEA_PR_CLI_API_TOKEN"), + "stderr was: {stderr}" + ); +} + +#[test] +fn fetch_writes_markdown_to_stdout_by_default() { + let mut server = Server::new(); + let pull_path = "/api/v1/repos/org/repo/pulls/7"; + + server + .mock("GET", pull_path) + .with_status(200) + .with_body( + r#"{ + "number": 7, + "title": "Fix parser", + "state": "open", + "body": "PR body", + "user": { "login": "alice" }, + "base": { "ref": "main" }, + "head": { "ref": "feature/x" }, + "created_at": "2026-04-08T10:00:00Z", + "updated_at": "2026-04-08T11:00:00Z", + "merged_at": null, + "additions": 3, + "deletions": 1, + "changed_files": 1 +}"#, + ) + .create(); + server + .mock("GET", "/api/v1/repos/org/repo/pulls/7/reviews") + .with_status(200) + .with_body( + r#"[ + { + "id": 55, + "state": "COMMENT", + "user": { "login": "bob" }, + "submitted_at": "2026-04-08T12:00:00Z" + } +]"#, + ) + .create(); + server + .mock("GET", "/api/v1/repos/org/repo/pulls/7/reviews/55/comments") + .with_status(200) + .with_body( + r#"[ + { + "id": 501, + "body": "please fix", + "created_at": "2026-04-08T12:01:00Z", + "updated_at": null, + "user": { "login": "bob" }, + "path": "src/main.rs", + "line": 12, + "pull_request_review_id": 55, + "original_position": 1, + "position": 1, + "commit_id": "abcdef123456", + "original_commit_id": "abcdef123456", + "diff_hunk": "@@ -1,1 +1,1 @@" + } +]"#, + ) + .create(); + server + .mock("GET", "/api/v1/repos/org/repo/pulls/7/commits") + .with_status(200) + .with_body( + r#"[ + { + "sha": "abcdef1234567890", + "commit": { + "message": "fix: parser\n\nbody", + "author": { "name": "alice", "date": "2026-04-08T10:30:00Z" } + } + } +]"#, + ) + .create(); + server + .mock("GET", "/api/v1/repos/org/repo/pulls/7/files") + .with_status(200) + .with_body( + r#"[ + { + "filename": "src/main.rs", + "additions": 3, + "deletions": 1, + "changes": 4, + "status": "modified", + "previous_filename": null + } +]"#, + ) + .create(); + + let assert = Command::cargo_bin("gitea-pr-review") + .unwrap() + .args(["fetch", "7"]) + .env("GITEA_PR_CLI_API_TOKEN", "token") + .env("GITEA_PR_CLI_URL", server.url()) + .env("GITEA_PR_CLI_REPO", "org/repo") + .assert() + .success(); + + let stdout = String::from_utf8(assert.get_output().stdout.clone()).unwrap(); + assert!(stdout.contains("# org/repo `#7` Fix parser")); + assert!(stdout.contains("## Commits")); + assert!(stdout.contains("## Diff Stat")); + assert!(stdout.contains("## Review 1 (COMMENT)")); +} + +#[test] +fn fetch_writes_json_to_out_file_when_requested() { + let mut server = Server::new(); + let pull_path = "/api/v1/repos/org/repo/pulls/8"; + + server + .mock("GET", pull_path) + .with_status(200) + .with_body( + r#"{ + "number": 8, + "title": "Add feature", + "state": "open", + "body": "PR body", + "user": { "login": "alice" }, + "base": { "ref": "main" }, + "head": { "ref": "feature/y" }, + "created_at": "2026-04-08T10:00:00Z", + "updated_at": "2026-04-08T11:00:00Z", + "merged_at": null, + "additions": 0, + "deletions": 0, + "changed_files": 0 +}"#, + ) + .create(); + server + .mock("GET", "/api/v1/repos/org/repo/pulls/8/reviews") + .with_status(200) + .with_body("[]") + .create(); + server + .mock("GET", "/api/v1/repos/org/repo/pulls/8/commits") + .with_status(200) + .with_body("[]") + .create(); + server + .mock("GET", "/api/v1/repos/org/repo/pulls/8/files") + .with_status(200) + .with_body("[]") + .create(); + + let output = NamedTempFile::new().unwrap(); + + Command::cargo_bin("gitea-pr-review") + .unwrap() + .args([ + "fetch", + "8", + "--format", + "json", + "--out", + output.path().to_str().unwrap(), + ]) + .env("GITEA_PR_CLI_API_TOKEN", "token") + .env("GITEA_PR_CLI_URL", server.url()) + .env("GITEA_PR_CLI_REPO", "org/repo") + .assert() + .success(); + + let written = std::fs::read_to_string(output.path()).unwrap(); + assert!(written.contains("\"repo\": \"org/repo\"")); + assert!(written.contains("\"pr_index\": 8")); + assert!(written.contains("\"head_branch\": \"feature/y\"")); +}