use crate::domain::model::body::Body;
use crate::domain::model::issue::IssueEdit;
use crate::domain::model::record_ref::IssueRef;
use crate::domain::usecases::edit::issue::commit_issue_edits;
use crate::domain::usecases::issue::IssueRepository;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum EditIssueBodyOutcome {
Updated,
NoOp,
}
pub fn edit_issue_body(
repo: &dyn IssueRepository,
id: &IssueRef,
new_body: Body,
) -> anyhow::Result<EditIssueBodyOutcome> {
let issue = repo
.find_by_id(id)?
.ok_or_else(|| anyhow::anyhow!("issue {id} not found"))?;
if issue.content == new_body {
return Ok(EditIssueBodyOutcome::NoOp);
}
commit_issue_edits(
repo,
vec![IssueEdit::SetBody {
issue: id.clone(),
body: new_body,
}],
)?;
Ok(EditIssueBodyOutcome::Updated)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::domain::model::record_ref::IssueRef;
use crate::domain::usecases::issue::tests::{feature, FakeIssueRepository, IssueFixture};
#[test]
fn changing_the_body_saves_the_new_content() {
scenario()
.given(feature("Add login").with_id("ISSUE-0001"))
.when_edit_body("ISSUE-0001", "New body content.")
.then_saved_body("New body content.")
.then_updated();
}
#[test]
fn submitting_the_same_body_is_a_noop() {
scenario()
.given(
feature("Add login")
.with_id("ISSUE-0001")
.with_body("Same body."),
)
.when_edit_body("ISSUE-0001", "Same body.")
.then_noop();
}
#[test]
fn editing_the_body_of_an_unknown_issue_returns_an_error() {
scenario()
.when_edit_body("ISSUE-0099", "body")
.then_err_contains("not found");
}
fn scenario() -> Scenario {
Scenario {
repo: FakeIssueRepository::new(),
}
}
struct Scenario {
repo: FakeIssueRepository,
}
impl Scenario {
fn given(mut self, fixture: IssueFixture) -> Self {
let raw = fixture
.id
.as_deref()
.expect("given() requires an explicit id — use .with_id()")
.to_string();
let numeric = IssueRef::new(&raw).unwrap();
self.repo.push_issue(fixture.build(numeric));
self
}
fn when_edit_body(self, id: &str, new_body: &str) -> EditOutcome {
let issue_ref = IssueRef::new(id).unwrap();
let result = edit_issue_body(&self.repo, &issue_ref, Body::new(new_body));
EditOutcome {
repo: self.repo,
result,
}
}
}
struct EditOutcome {
repo: FakeIssueRepository,
result: anyhow::Result<EditIssueBodyOutcome>,
}
impl EditOutcome {
fn then_saved_body(self, expected: &str) -> Self {
let saved = self.repo.last_saved().expect("expected a save, got none");
assert_eq!(saved.content, Body::new(expected));
self
}
fn then_updated(self) -> Self {
assert_eq!(
self.result.as_ref().expect("expected Ok, got Err"),
&EditIssueBodyOutcome::Updated
);
self
}
fn then_noop(self) -> Self {
assert_eq!(
self.result.as_ref().expect("expected Ok, got Err"),
&EditIssueBodyOutcome::NoOp
);
assert!(self.repo.last_saved().is_none(), "expected nothing saved");
self
}
fn then_err_contains(self, substring: &str) {
let msg = self.result.expect_err("expected Err, got Ok").to_string();
assert!(
msg.contains(substring),
"expected {substring:?}, got {msg:?}"
);
}
}
}