Skip to main content

oven_cli/agents/
merger.rs

1use anyhow::{Context, Result};
2use askama::Template;
3
4use super::AgentContext;
5
6#[derive(Template)]
7#[template(path = "merger.txt")]
8struct MergerPrompt<'a> {
9    ctx: &'a AgentContext,
10    auto_merge: bool,
11    pr_number: u32,
12}
13
14pub fn build_prompt(ctx: &AgentContext, auto_merge: bool) -> Result<String> {
15    let pr_number = ctx.pr_number.context("merger requires a PR number")?;
16    let tmpl = MergerPrompt { ctx, auto_merge, pr_number };
17    tmpl.render().context("rendering merger template")
18}
19
20#[cfg(test)]
21mod tests {
22    use super::*;
23
24    fn sample_context() -> AgentContext {
25        AgentContext {
26            issue_number: 42,
27            issue_title: "Fix auth bug".to_string(),
28            issue_body: "details".to_string(),
29            branch: "oven/issue-42-abc".to_string(),
30            pr_number: Some(99),
31            test_command: None,
32            lint_command: None,
33            review_findings: None,
34            cycle: 1,
35            target_repo: None,
36            issue_source: "github".to_string(),
37            base_branch: "main".to_string(),
38        }
39    }
40
41    #[test]
42    fn prompt_references_pr_number() {
43        let prompt = build_prompt(&sample_context(), false).unwrap();
44        assert!(prompt.contains("PR #99"));
45        assert!(prompt.contains("#42"));
46    }
47
48    #[test]
49    fn prompt_without_merge() {
50        let prompt = build_prompt(&sample_context(), false).unwrap();
51        assert!(!prompt.contains("gh pr merge"));
52    }
53
54    #[test]
55    fn prompt_with_merge() {
56        let prompt = build_prompt(&sample_context(), true).unwrap();
57        assert!(prompt.contains("gh pr merge 99"));
58        assert!(prompt.contains("--squash"));
59        assert!(prompt.contains("--delete-branch"));
60    }
61
62    #[test]
63    fn prompt_includes_issue_close_when_auto_merge() {
64        let prompt = build_prompt(&sample_context(), true).unwrap();
65        assert!(prompt.contains("gh issue close 42"));
66    }
67
68    #[test]
69    fn prompt_no_pr_edit() {
70        let prompt = build_prompt(&sample_context(), true).unwrap();
71        assert!(!prompt.contains("gh pr edit"));
72        assert!(!prompt.contains("gh pr ready"));
73    }
74
75    #[test]
76    fn prompt_includes_merge_summary_output() {
77        let prompt = build_prompt(&sample_context(), false).unwrap();
78        assert!(prompt.contains("Merge Summary"));
79    }
80
81    #[test]
82    fn prompt_skips_issue_close_in_multi_repo() {
83        let mut ctx = sample_context();
84        ctx.target_repo = Some("backend-api".to_string());
85        let prompt = build_prompt(&ctx, true).unwrap();
86        assert!(prompt.contains("gh pr merge 99"));
87        assert!(!prompt.contains("gh issue close"));
88    }
89
90    #[test]
91    fn prompt_includes_issue_close_in_single_repo() {
92        let prompt = build_prompt(&sample_context(), true).unwrap();
93        assert!(prompt.contains("gh issue close 42"));
94    }
95
96    #[test]
97    fn prompt_skips_issue_close_for_local_source() {
98        let mut ctx = sample_context();
99        ctx.issue_source = "local".to_string();
100        let prompt = build_prompt(&ctx, true).unwrap();
101        assert!(prompt.contains("gh pr merge 99"));
102        assert!(!prompt.contains("gh issue close"));
103    }
104
105    #[test]
106    fn prompt_fails_without_pr_number() {
107        let mut ctx = sample_context();
108        ctx.pr_number = None;
109        let result = build_prompt(&ctx, false);
110        assert!(result.is_err());
111        assert!(result.unwrap_err().to_string().contains("PR number"));
112    }
113}