huskies: merge 1158 story Reject boilerplate user stories at save time
This commit is contained in:
@@ -11,6 +11,7 @@ pub(crate) mod io;
|
||||
pub mod state_machine;
|
||||
|
||||
use crate::io::wizard::{StepStatus, WizardState, WizardStep, format_wizard_state};
|
||||
use crate::validation::check_boilerplate_user_story;
|
||||
use std::path::Path;
|
||||
|
||||
// ── Error type ────────────────────────────────────────────────────────────────
|
||||
@@ -21,6 +22,7 @@ use std::path::Path;
|
||||
/// - [`Error::NotActive`] → 404 Not Found
|
||||
/// - [`Error::InvalidStateTransition`] → 400 Bad Request
|
||||
/// - [`Error::MissingInput`] → 400 Bad Request
|
||||
/// - [`Error::InvalidContent`] → 400 Bad Request
|
||||
/// - [`Error::GenerationFailure`] → 500 Internal Server Error
|
||||
/// - [`Error::PersistenceFailure`] → 500 Internal Server Error
|
||||
#[derive(Debug)]
|
||||
@@ -32,6 +34,8 @@ pub enum Error {
|
||||
InvalidStateTransition(String),
|
||||
/// Required input was absent (e.g. content not staged before confirming).
|
||||
MissingInput(String),
|
||||
/// Staged content failed validation (e.g. boilerplate user-story template).
|
||||
InvalidContent(String),
|
||||
/// The LLM or agent failed to generate content for the step.
|
||||
GenerationFailure(String),
|
||||
/// A filesystem read or write operation failed.
|
||||
@@ -46,6 +50,7 @@ impl std::fmt::Display for Error {
|
||||
}
|
||||
Self::InvalidStateTransition(msg) => write!(f, "Invalid state transition: {msg}"),
|
||||
Self::MissingInput(msg) => write!(f, "Missing input: {msg}"),
|
||||
Self::InvalidContent(msg) => write!(f, "Invalid content: {msg}"),
|
||||
Self::GenerationFailure(msg) => write!(f, "Generation failed: {msg}"),
|
||||
Self::PersistenceFailure(msg) => write!(f, "Persistence error: {msg}"),
|
||||
}
|
||||
@@ -104,6 +109,8 @@ pub fn status(root: &Path) -> Result<String, Error> {
|
||||
///
|
||||
/// # Errors
|
||||
/// - [`Error::NotActive`] if no wizard is active.
|
||||
/// - [`Error::InvalidContent`] if `content` matches the boilerplate
|
||||
/// "As a …, I want …, so that …" user-story template.
|
||||
/// - [`Error::PersistenceFailure`] if saving state fails.
|
||||
pub fn generate(root: &Path, content: Option<&str>) -> Result<String, Error> {
|
||||
let mut state = io::load(root).ok_or(Error::NotActive)?;
|
||||
@@ -116,6 +123,9 @@ pub fn generate(root: &Path, content: Option<&str>) -> Result<String, Error> {
|
||||
let step = state.steps[current_idx].step;
|
||||
|
||||
if let Some(c) = content {
|
||||
if let Some(err) = check_boilerplate_user_story("content", c) {
|
||||
return Err(Error::InvalidContent(err.to_string()));
|
||||
}
|
||||
state.set_step_status(step, StepStatus::AwaitingConfirmation, Some(c.to_string()));
|
||||
io::save(&state, root)?;
|
||||
return Ok(format!(
|
||||
@@ -381,6 +391,21 @@ mod tests {
|
||||
assert_eq!(state.steps[1].status, StepStatus::AwaitingConfirmation);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generate_rejects_boilerplate_user_story_content() {
|
||||
let dir = TempDir::new().unwrap();
|
||||
init_wizard(&dir);
|
||||
let err = generate(
|
||||
dir.path(),
|
||||
Some("As a user, I want this, so that I get value."),
|
||||
)
|
||||
.unwrap_err();
|
||||
assert!(matches!(err, Error::InvalidContent(_)));
|
||||
// Content must not be staged when rejected.
|
||||
let state = get_state(dir.path()).unwrap();
|
||||
assert_eq!(state.steps[1].status, StepStatus::Pending);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generate_no_content_marks_generating() {
|
||||
let dir = TempDir::new().unwrap();
|
||||
|
||||
Reference in New Issue
Block a user