#![cfg(feature = "legacy_claude")]
use anyhow::Result;
use tempfile::TempDir;
use xchecker::runner::Runner;
use xchecker::claude::ClaudeWrapper;
use xchecker::orchestrator::{OrchestratorConfig, PhaseOrchestrator};
use xchecker::types::PhaseId;
#[allow(clippy::duplicate_mod)]
#[path = "test_support/mod.rs"]
mod test_support;
#[tokio::test]
#[ignore = "requires_claude_stub"]
async fn test_m1_gate_requirements_phase_integration() -> Result<()> {
let temp_dir = TempDir::new()?;
let _cwd_guard = test_support::CwdGuard::new(temp_dir.path())?;
let spec_id = "m1-gate-test";
let orchestrator = PhaseOrchestrator::new(spec_id)?;
let config = OrchestratorConfig {
dry_run: false,
config: {
let mut map = std::collections::HashMap::new();
map.insert(
"claude_cli_path".to_string(),
"cargo run --bin claude-stub --".to_string(),
);
map.insert("claude_scenario".to_string(), "success".to_string());
map.insert("verbose".to_string(), "true".to_string());
map
},
full_config: None,
selectors: None,
strict_validation: false,
redactor: Default::default(),
hooks: None,
};
let result = orchestrator.execute_requirements_phase(&config).await?;
assert!(
result.success,
"Requirements phase should complete successfully"
);
assert_eq!(result.exit_code, 0, "Exit code should be 0 for success");
assert_eq!(
result.phase,
PhaseId::Requirements,
"Phase should be Requirements"
);
assert!(!result.artifact_paths.is_empty(), "Should create artifacts");
assert_eq!(
result.artifact_paths.len(),
2,
"Should create 2 artifacts (.md and .core.yaml)"
);
assert!(result.receipt_path.is_some(), "Should create receipt");
let receipt_path = result.receipt_path.unwrap();
assert!(receipt_path.exists(), "Receipt file should exist");
let receipt_content = std::fs::read_to_string(&receipt_path)?;
let receipt: xchecker::types::Receipt = serde_json::from_str(&receipt_content)?;
assert_eq!(
receipt.spec_id, spec_id,
"Receipt should have correct spec_id"
);
assert_eq!(
receipt.phase, "requirements",
"Receipt should have correct phase"
);
assert!(
!receipt.xchecker_version.is_empty(),
"Receipt should have xchecker version"
);
assert!(
!receipt.claude_cli_version.is_empty(),
"Receipt should have Claude CLI version"
);
assert!(
!receipt.model_full_name.is_empty(),
"Receipt should have model full name"
);
assert!(
!receipt.canonicalization_version.is_empty(),
"Receipt should have canonicalization version"
);
assert_eq!(
receipt.exit_code, 0,
"Receipt should record successful exit code"
);
assert!(
!receipt.outputs.is_empty(),
"Receipt should have output file hashes"
);
for output in &receipt.outputs {
assert!(!output.path.is_empty(), "Output path should not be empty");
assert!(
!output.blake3_canonicalized.is_empty(),
"Output hash should not be empty"
);
assert_eq!(
output.blake3_canonicalized.len(),
64,
"BLAKE3 hash should be 64 characters"
);
}
let spec_dir = temp_dir.path().join(".xchecker/specs").join(spec_id);
let artifacts_dir = spec_dir.join("artifacts");
let requirements_md = artifacts_dir.join("00-requirements.md");
let requirements_yaml = artifacts_dir.join("00-requirements.core.yaml");
assert!(
requirements_md.exists(),
"Requirements markdown should exist"
);
assert!(requirements_yaml.exists(), "Requirements YAML should exist");
let md_content = std::fs::read_to_string(&requirements_md)?;
assert!(
md_content.contains("# Requirements Document"),
"Should have proper title"
);
assert!(
md_content.contains("## Introduction"),
"Should have introduction"
);
assert!(
md_content.contains("**User Story:**"),
"Should have user stories"
);
assert!(
md_content.contains("#### Acceptance Criteria"),
"Should have acceptance criteria"
);
assert!(
md_content.contains("WHEN"),
"Should have EARS format criteria"
);
assert!(
md_content.contains("THEN"),
"Should have EARS format criteria"
);
assert!(
md_content.contains("SHALL"),
"Should have EARS format criteria"
);
println!("✓ M1 Gate Requirements phase integration test passed");
println!("✓ R4.1: Claude CLI integration validated");
println!("✓ R4.4: Structured output handling validated");
println!("✓ R2.1: Receipt metadata completeness validated");
Ok(())
}
#[tokio::test]
#[ignore = "requires_claude_stub"]
async fn test_m1_gate_claude_wrapper_parsing() -> Result<()> {
let wrapper = ClaudeWrapper::new(Some("haiku".to_string()), Runner::native())?;
let sample_json = concat!(
r#"{"type": "conversation_start", "conversation": {"id": "conv_123"}}"#,
"\n",
r#"{"type": "message_start", "message": {"id": "msg_123", "role": "assistant"}}"#,
"\n",
r#"{"type": "content_block_start", "index": 0, "content_block": {"type": "text", "text": ""}}"#,
"\n",
r#"{"type": "content_block_delta", "index": 0, "delta": {"type": "text_delta", "text": "Hello"}}"#,
"\n",
r#"{"type": "content_block_delta", "index": 0, "delta": {"type": "text_delta", "text": " World"}}"#,
"\n",
r#"{"type": "content_block_stop", "index": 0}"#,
"\n",
r#"{"type": "message_stop", "message": {"id": "msg_123", "model": "haiku", "stop_reason": "end_turn", "usage": {"input_tokens": 10, "output_tokens": 5}}}"#
);
let (content, metadata) = wrapper.parse_stream_json(sample_json)?;
assert_eq!(content, "Hello World", "Should parse content correctly");
assert_eq!(metadata.input_tokens, Some(10), "Should parse input tokens");
assert_eq!(
metadata.output_tokens,
Some(5),
"Should parse output tokens"
);
assert_eq!(
metadata.model,
Some("haiku".to_string()),
"Should parse model"
);
assert_eq!(
metadata.stop_reason,
Some("end_turn".to_string()),
"Should parse stop reason"
);
println!("✓ Claude wrapper stream-json parsing test passed");
Ok(())
}
#[tokio::test]
#[ignore = "requires_claude_stub"]
async fn test_m1_gate_model_resolution() -> Result<()> {
let wrapper_with_alias = ClaudeWrapper::new(Some("sonnet".to_string()), Runner::native())?;
let (alias, full_name) = wrapper_with_alias.get_model_info();
assert_eq!(
alias,
Some("sonnet".to_string()),
"Should preserve model alias"
);
assert_eq!(full_name, "haiku", "Should resolve alias to full name");
let version = wrapper_with_alias.get_version();
assert!(!version.is_empty(), "Should capture Claude CLI version");
println!("✓ Model resolution and version capture test passed");
Ok(())
}