1use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct Spec {
9 pub id: String,
11 pub name: String,
13 pub version: String,
15 pub requirements: Vec<Requirement>,
17 pub design: Option<Design>,
19 pub tasks: Vec<Task>,
21 pub metadata: SpecMetadata,
23 pub inheritance: Option<SpecInheritance>,
25}
26
27#[derive(Debug, Clone, Serialize, Deserialize)]
29pub struct SpecMetadata {
30 pub author: Option<String>,
32 pub created_at: DateTime<Utc>,
34 pub updated_at: DateTime<Utc>,
36 pub phase: SpecPhase,
38 pub status: SpecStatus,
40}
41
42#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
44#[serde(rename_all = "lowercase")]
45pub enum SpecPhase {
46 Discovery,
48 Requirements,
50 Design,
52 Tasks,
54 Execution,
56}
57
58#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
60#[serde(rename_all = "lowercase")]
61pub enum SpecStatus {
62 Draft,
64 InReview,
66 Approved,
68 Archived,
70}
71
72#[derive(Debug, Clone, Serialize, Deserialize)]
74pub struct SpecInheritance {
75 pub parent_id: Option<String>,
77 pub precedence_level: u32,
79 pub merged_from: Vec<String>,
81}
82
83#[derive(Debug, Clone, Serialize, Deserialize)]
85pub struct Requirement {
86 pub id: String,
88 pub user_story: String,
90 pub acceptance_criteria: Vec<AcceptanceCriterion>,
92 pub priority: Priority,
94}
95
96#[derive(Debug, Clone, Serialize, Deserialize)]
98pub struct AcceptanceCriterion {
99 pub id: String,
101 pub when: String,
103 pub then: String,
105}
106
107#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
109#[serde(rename_all = "lowercase")]
110pub enum Priority {
111 Must,
113 Should,
115 Could,
117}
118
119#[derive(Debug, Clone, Serialize, Deserialize)]
121pub struct Design {
122 pub overview: String,
124 pub architecture: String,
126 pub components: Vec<Component>,
128 pub data_models: Vec<DataModel>,
130 pub correctness_properties: Vec<Property>,
132}
133
134#[derive(Debug, Clone, Serialize, Deserialize)]
136pub struct Component {
137 pub name: String,
139 pub description: String,
141}
142
143#[derive(Debug, Clone, Serialize, Deserialize)]
145pub struct DataModel {
146 pub name: String,
148 pub description: String,
150}
151
152#[derive(Debug, Clone, Serialize, Deserialize)]
154pub struct Property {
155 pub id: String,
157 pub description: String,
159 pub validates: Vec<String>,
161}
162
163#[derive(Debug, Clone, Serialize, Deserialize)]
165pub struct Task {
166 pub id: String,
168 pub description: String,
170 pub subtasks: Vec<Task>,
172 pub requirements: Vec<String>,
174 pub status: TaskStatus,
176 pub optional: bool,
178}
179
180#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
182#[serde(rename_all = "snake_case")]
183pub enum TaskStatus {
184 NotStarted,
186 InProgress,
188 Complete,
190}
191
192#[derive(Debug, Clone, Serialize, Deserialize)]
194pub struct Steering {
195 pub rules: Vec<SteeringRule>,
197 pub standards: Vec<Standard>,
199 pub templates: Vec<TemplateRef>,
201}
202
203#[derive(Debug, Clone, Serialize, Deserialize)]
205pub struct SteeringRule {
206 pub id: String,
208 pub description: String,
210 pub pattern: String,
212 pub action: String,
214}
215
216#[derive(Debug, Clone, Serialize, Deserialize)]
218pub struct Standard {
219 pub id: String,
221 pub description: String,
223}
224
225#[derive(Debug, Clone, Serialize, Deserialize)]
227pub struct TemplateRef {
228 pub id: String,
230 pub path: String,
232}
233
234#[derive(Debug, Clone, Serialize, Deserialize)]
236pub struct SpecChange {
237 pub id: String,
239 pub spec_id: String,
241 pub timestamp: DateTime<Utc>,
243 pub author: Option<String>,
245 pub rationale: String,
247 pub changes: Vec<ChangeDetail>,
249}
250
251#[derive(Debug, Clone, Serialize, Deserialize)]
253pub struct ChangeDetail {
254 pub field: String,
256 pub old_value: Option<String>,
258 pub new_value: Option<String>,
260}
261
262#[derive(Debug, Clone, Default)]
264pub struct SpecQuery {
265 pub name: Option<String>,
267 pub spec_type: Option<SpecType>,
269 pub status: Option<SpecStatus>,
271 pub priority: Option<Priority>,
273 pub phase: Option<SpecPhase>,
275 pub custom_filters: Vec<(String, String)>,
277}
278
279#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
281#[serde(rename_all = "lowercase")]
282pub enum SpecType {
283 Feature,
285 Component,
287 Task,
289}
290
291#[derive(Debug, Clone, Serialize, Deserialize)]
293pub struct ConversationMessage {
294 pub id: String,
296 pub spec_id: String,
298 pub role: MessageRole,
300 pub content: String,
302 pub timestamp: DateTime<Utc>,
304}
305
306#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
308#[serde(rename_all = "lowercase")]
309pub enum MessageRole {
310 User,
312 Assistant,
314 System,
316}
317
318#[derive(Debug, Clone, Serialize, Deserialize)]
320pub struct ApprovalGate {
321 pub phase: SpecPhase,
323 pub approved: bool,
325 pub approved_at: Option<DateTime<Utc>>,
327 pub approved_by: Option<String>,
329 pub feedback: Option<String>,
331}
332
333#[derive(Debug, Clone, Serialize, Deserialize)]
335pub struct SpecWritingSession {
336 pub id: String,
338 pub spec_id: String,
340 pub phase: SpecPhase,
342 pub conversation_history: Vec<ConversationMessage>,
344 pub approval_gates: Vec<ApprovalGate>,
346 pub created_at: DateTime<Utc>,
348 pub updated_at: DateTime<Utc>,
350}
351
352#[cfg(test)]
353mod tests {
354 use super::*;
355
356 #[test]
361 fn test_spec_metadata_creation() {
362 let now = Utc::now();
363 let metadata = SpecMetadata {
364 author: Some("Test Author".to_string()),
365 created_at: now,
366 updated_at: now,
367 phase: SpecPhase::Requirements,
368 status: SpecStatus::Draft,
369 };
370
371 assert_eq!(metadata.author, Some("Test Author".to_string()));
372 assert_eq!(metadata.phase, SpecPhase::Requirements);
373 assert_eq!(metadata.status, SpecStatus::Draft);
374 assert_eq!(metadata.created_at, now);
375 assert_eq!(metadata.updated_at, now);
376 }
377
378 #[test]
379 fn test_spec_metadata_no_author() {
380 let now = Utc::now();
381 let metadata = SpecMetadata {
382 author: None,
383 created_at: now,
384 updated_at: now,
385 phase: SpecPhase::Discovery,
386 status: SpecStatus::Draft,
387 };
388
389 assert!(metadata.author.is_none());
390 }
391
392 #[test]
397 fn test_spec_phase_serialization() {
398 let phases = vec![
399 SpecPhase::Discovery,
400 SpecPhase::Requirements,
401 SpecPhase::Design,
402 SpecPhase::Tasks,
403 SpecPhase::Execution,
404 ];
405
406 for phase in phases {
407 let json = serde_json::to_string(&phase).unwrap();
408 let deserialized: SpecPhase = serde_json::from_str(&json).unwrap();
409 assert_eq!(phase, deserialized);
410 }
411 }
412
413 #[test]
414 fn test_spec_status_serialization() {
415 let statuses = vec![
416 SpecStatus::Draft,
417 SpecStatus::InReview,
418 SpecStatus::Approved,
419 SpecStatus::Archived,
420 ];
421
422 for status in statuses {
423 let json = serde_json::to_string(&status).unwrap();
424 let deserialized: SpecStatus = serde_json::from_str(&json).unwrap();
425 assert_eq!(status, deserialized);
426 }
427 }
428
429 #[test]
430 fn test_priority_serialization() {
431 let priorities = vec![Priority::Must, Priority::Should, Priority::Could];
432
433 for priority in priorities {
434 let json = serde_json::to_string(&priority).unwrap();
435 let deserialized: Priority = serde_json::from_str(&json).unwrap();
436 assert_eq!(priority, deserialized);
437 }
438 }
439
440 #[test]
441 fn test_task_status_serialization() {
442 let statuses = vec![
443 TaskStatus::NotStarted,
444 TaskStatus::InProgress,
445 TaskStatus::Complete,
446 ];
447
448 for status in statuses {
449 let json = serde_json::to_string(&status).unwrap();
450 let deserialized: TaskStatus = serde_json::from_str(&json).unwrap();
451 assert_eq!(status, deserialized);
452 }
453 }
454
455 #[test]
460 fn test_acceptance_criterion_serialization() {
461 let criterion = AcceptanceCriterion {
462 id: "AC-1.1".to_string(),
463 when: "user clicks button".to_string(),
464 then: "dialog opens".to_string(),
465 };
466
467 let json = serde_json::to_string(&criterion).unwrap();
468 let deserialized: AcceptanceCriterion = serde_json::from_str(&json).unwrap();
469
470 assert_eq!(criterion.id, deserialized.id);
471 assert_eq!(criterion.when, deserialized.when);
472 assert_eq!(criterion.then, deserialized.then);
473 }
474
475 #[test]
476 fn test_requirement_serialization() {
477 let requirement = Requirement {
478 id: "REQ-1".to_string(),
479 user_story: "As a user, I want to create tasks".to_string(),
480 acceptance_criteria: vec![
481 AcceptanceCriterion {
482 id: "AC-1.1".to_string(),
483 when: "user enters task".to_string(),
484 then: "task is added".to_string(),
485 },
486 AcceptanceCriterion {
487 id: "AC-1.2".to_string(),
488 when: "user submits empty task".to_string(),
489 then: "error is shown".to_string(),
490 },
491 ],
492 priority: Priority::Must,
493 };
494
495 let json = serde_json::to_string(&requirement).unwrap();
496 let deserialized: Requirement = serde_json::from_str(&json).unwrap();
497
498 assert_eq!(requirement.id, deserialized.id);
499 assert_eq!(requirement.user_story, deserialized.user_story);
500 assert_eq!(requirement.priority, deserialized.priority);
501 assert_eq!(
502 requirement.acceptance_criteria.len(),
503 deserialized.acceptance_criteria.len()
504 );
505 }
506
507 #[test]
512 fn test_component_serialization() {
513 let component = Component {
514 name: "TaskManager".to_string(),
515 description: "Manages task lifecycle".to_string(),
516 };
517
518 let json = serde_json::to_string(&component).unwrap();
519 let deserialized: Component = serde_json::from_str(&json).unwrap();
520
521 assert_eq!(component.name, deserialized.name);
522 assert_eq!(component.description, deserialized.description);
523 }
524
525 #[test]
526 fn test_data_model_serialization() {
527 let model = DataModel {
528 name: "Task".to_string(),
529 description: "Represents a task in the system".to_string(),
530 };
531
532 let json = serde_json::to_string(&model).unwrap();
533 let deserialized: DataModel = serde_json::from_str(&json).unwrap();
534
535 assert_eq!(model.name, deserialized.name);
536 assert_eq!(model.description, deserialized.description);
537 }
538
539 #[test]
540 fn test_property_serialization() {
541 let property = Property {
542 id: "PROP-1".to_string(),
543 description: "For any task list, adding a task increases length by 1".to_string(),
544 validates: vec!["REQ-1.1".to_string(), "REQ-1.2".to_string()],
545 };
546
547 let json = serde_json::to_string(&property).unwrap();
548 let deserialized: Property = serde_json::from_str(&json).unwrap();
549
550 assert_eq!(property.id, deserialized.id);
551 assert_eq!(property.description, deserialized.description);
552 assert_eq!(property.validates, deserialized.validates);
553 }
554
555 #[test]
556 fn test_design_serialization() {
557 let design = Design {
558 overview: "Task management system".to_string(),
559 architecture: "Layered architecture".to_string(),
560 components: vec![Component {
561 name: "TaskManager".to_string(),
562 description: "Manages tasks".to_string(),
563 }],
564 data_models: vec![DataModel {
565 name: "Task".to_string(),
566 description: "Task entity".to_string(),
567 }],
568 correctness_properties: vec![Property {
569 id: "PROP-1".to_string(),
570 description: "Task addition property".to_string(),
571 validates: vec!["REQ-1".to_string()],
572 }],
573 };
574
575 let json = serde_json::to_string(&design).unwrap();
576 let deserialized: Design = serde_json::from_str(&json).unwrap();
577
578 assert_eq!(design.overview, deserialized.overview);
579 assert_eq!(design.architecture, deserialized.architecture);
580 assert_eq!(design.components.len(), deserialized.components.len());
581 assert_eq!(design.data_models.len(), deserialized.data_models.len());
582 assert_eq!(
583 design.correctness_properties.len(),
584 deserialized.correctness_properties.len()
585 );
586 }
587
588 #[test]
593 fn test_task_serialization() {
594 let task = Task {
595 id: "1".to_string(),
596 description: "Implement task manager".to_string(),
597 subtasks: vec![Task {
598 id: "1.1".to_string(),
599 description: "Create data model".to_string(),
600 subtasks: vec![],
601 requirements: vec!["REQ-1".to_string()],
602 status: TaskStatus::NotStarted,
603 optional: false,
604 }],
605 requirements: vec!["REQ-1".to_string()],
606 status: TaskStatus::InProgress,
607 optional: false,
608 };
609
610 let json = serde_json::to_string(&task).unwrap();
611 let deserialized: Task = serde_json::from_str(&json).unwrap();
612
613 assert_eq!(task.id, deserialized.id);
614 assert_eq!(task.description, deserialized.description);
615 assert_eq!(task.status, deserialized.status);
616 assert_eq!(task.optional, deserialized.optional);
617 assert_eq!(task.subtasks.len(), deserialized.subtasks.len());
618 }
619
620 #[test]
625 fn test_spec_inheritance_serialization() {
626 let inheritance = SpecInheritance {
627 parent_id: Some("parent-spec".to_string()),
628 precedence_level: 1,
629 merged_from: vec!["spec-a".to_string(), "spec-b".to_string()],
630 };
631
632 let json = serde_json::to_string(&inheritance).unwrap();
633 let deserialized: SpecInheritance = serde_json::from_str(&json).unwrap();
634
635 assert_eq!(inheritance.parent_id, deserialized.parent_id);
636 assert_eq!(inheritance.precedence_level, deserialized.precedence_level);
637 assert_eq!(inheritance.merged_from, deserialized.merged_from);
638 }
639
640 #[test]
641 fn test_spec_inheritance_no_parent() {
642 let inheritance = SpecInheritance {
643 parent_id: None,
644 precedence_level: 0,
645 merged_from: vec![],
646 };
647
648 let json = serde_json::to_string(&inheritance).unwrap();
649 let deserialized: SpecInheritance = serde_json::from_str(&json).unwrap();
650
651 assert!(deserialized.parent_id.is_none());
652 assert_eq!(deserialized.precedence_level, 0);
653 assert!(deserialized.merged_from.is_empty());
654 }
655
656 #[test]
657 fn test_spec_inheritance_precedence_levels() {
658 let levels = vec![0, 1, 2];
659
660 for level in levels {
661 let inheritance = SpecInheritance {
662 parent_id: Some("parent".to_string()),
663 precedence_level: level,
664 merged_from: vec![],
665 };
666
667 let json = serde_json::to_string(&inheritance).unwrap();
668 let deserialized: SpecInheritance = serde_json::from_str(&json).unwrap();
669
670 assert_eq!(deserialized.precedence_level, level);
671 }
672 }
673
674 #[test]
679 fn test_change_detail_serialization() {
680 let change = ChangeDetail {
681 field: "status".to_string(),
682 old_value: Some("Draft".to_string()),
683 new_value: Some("Approved".to_string()),
684 };
685
686 let json = serde_json::to_string(&change).unwrap();
687 let deserialized: ChangeDetail = serde_json::from_str(&json).unwrap();
688
689 assert_eq!(change.field, deserialized.field);
690 assert_eq!(change.old_value, deserialized.old_value);
691 assert_eq!(change.new_value, deserialized.new_value);
692 }
693
694 #[test]
695 fn test_change_detail_with_none_values() {
696 let change = ChangeDetail {
697 field: "new_field".to_string(),
698 old_value: None,
699 new_value: Some("value".to_string()),
700 };
701
702 let json = serde_json::to_string(&change).unwrap();
703 let deserialized: ChangeDetail = serde_json::from_str(&json).unwrap();
704
705 assert!(deserialized.old_value.is_none());
706 assert_eq!(deserialized.new_value, Some("value".to_string()));
707 }
708
709 #[test]
710 fn test_spec_change_serialization() {
711 let now = Utc::now();
712 let spec_change = SpecChange {
713 id: "change-1".to_string(),
714 spec_id: "spec-1".to_string(),
715 timestamp: now,
716 author: Some("John Doe".to_string()),
717 rationale: "Updated requirements".to_string(),
718 changes: vec![
719 ChangeDetail {
720 field: "status".to_string(),
721 old_value: Some("Draft".to_string()),
722 new_value: Some("Approved".to_string()),
723 },
724 ChangeDetail {
725 field: "phase".to_string(),
726 old_value: Some("Requirements".to_string()),
727 new_value: Some("Design".to_string()),
728 },
729 ],
730 };
731
732 let json = serde_json::to_string(&spec_change).unwrap();
733 let deserialized: SpecChange = serde_json::from_str(&json).unwrap();
734
735 assert_eq!(spec_change.id, deserialized.id);
736 assert_eq!(spec_change.spec_id, deserialized.spec_id);
737 assert_eq!(spec_change.author, deserialized.author);
738 assert_eq!(spec_change.rationale, deserialized.rationale);
739 assert_eq!(spec_change.changes.len(), deserialized.changes.len());
740 }
741
742 #[test]
747 fn test_steering_rule_serialization() {
748 let rule = SteeringRule {
749 id: "rule-1".to_string(),
750 description: "Use snake_case for variables".to_string(),
751 pattern: "^[a-z_]+$".to_string(),
752 action: "enforce".to_string(),
753 };
754
755 let json = serde_json::to_string(&rule).unwrap();
756 let deserialized: SteeringRule = serde_json::from_str(&json).unwrap();
757
758 assert_eq!(rule.id, deserialized.id);
759 assert_eq!(rule.description, deserialized.description);
760 assert_eq!(rule.pattern, deserialized.pattern);
761 assert_eq!(rule.action, deserialized.action);
762 }
763
764 #[test]
765 fn test_standard_serialization() {
766 let standard = Standard {
767 id: "std-1".to_string(),
768 description: "All public APIs must have tests".to_string(),
769 };
770
771 let json = serde_json::to_string(&standard).unwrap();
772 let deserialized: Standard = serde_json::from_str(&json).unwrap();
773
774 assert_eq!(standard.id, deserialized.id);
775 assert_eq!(standard.description, deserialized.description);
776 }
777
778 #[test]
779 fn test_template_ref_serialization() {
780 let template = TemplateRef {
781 id: "tpl-1".to_string(),
782 path: "templates/rust-entity.rs".to_string(),
783 };
784
785 let json = serde_json::to_string(&template).unwrap();
786 let deserialized: TemplateRef = serde_json::from_str(&json).unwrap();
787
788 assert_eq!(template.id, deserialized.id);
789 assert_eq!(template.path, deserialized.path);
790 }
791
792 #[test]
793 fn test_steering_serialization() {
794 let steering = Steering {
795 rules: vec![SteeringRule {
796 id: "rule-1".to_string(),
797 description: "Use snake_case".to_string(),
798 pattern: "^[a-z_]+$".to_string(),
799 action: "enforce".to_string(),
800 }],
801 standards: vec![Standard {
802 id: "std-1".to_string(),
803 description: "Test all public APIs".to_string(),
804 }],
805 templates: vec![TemplateRef {
806 id: "tpl-1".to_string(),
807 path: "templates/entity.rs".to_string(),
808 }],
809 };
810
811 let json = serde_json::to_string(&steering).unwrap();
812 let deserialized: Steering = serde_json::from_str(&json).unwrap();
813
814 assert_eq!(steering.rules.len(), deserialized.rules.len());
815 assert_eq!(steering.standards.len(), deserialized.standards.len());
816 assert_eq!(steering.templates.len(), deserialized.templates.len());
817 }
818
819 #[test]
824 fn test_spec_serialization_complete() {
825 let now = Utc::now();
826 let spec = Spec {
827 id: "feature-1".to_string(),
828 name: "Task Management".to_string(),
829 version: "1.0.0".to_string(),
830 requirements: vec![Requirement {
831 id: "REQ-1".to_string(),
832 user_story: "As a user, I want to create tasks".to_string(),
833 acceptance_criteria: vec![AcceptanceCriterion {
834 id: "AC-1.1".to_string(),
835 when: "user enters task".to_string(),
836 then: "task is added".to_string(),
837 }],
838 priority: Priority::Must,
839 }],
840 design: Some(Design {
841 overview: "Task management system".to_string(),
842 architecture: "Layered".to_string(),
843 components: vec![],
844 data_models: vec![],
845 correctness_properties: vec![],
846 }),
847 tasks: vec![Task {
848 id: "1".to_string(),
849 description: "Implement task manager".to_string(),
850 subtasks: vec![],
851 requirements: vec!["REQ-1".to_string()],
852 status: TaskStatus::NotStarted,
853 optional: false,
854 }],
855 metadata: SpecMetadata {
856 author: Some("Developer".to_string()),
857 created_at: now,
858 updated_at: now,
859 phase: SpecPhase::Requirements,
860 status: SpecStatus::Draft,
861 },
862 inheritance: Some(SpecInheritance {
863 parent_id: None,
864 precedence_level: 0,
865 merged_from: vec![],
866 }),
867 };
868
869 let json = serde_json::to_string(&spec).unwrap();
870 let deserialized: Spec = serde_json::from_str(&json).unwrap();
871
872 assert_eq!(spec.id, deserialized.id);
873 assert_eq!(spec.name, deserialized.name);
874 assert_eq!(spec.version, deserialized.version);
875 assert_eq!(spec.requirements.len(), deserialized.requirements.len());
876 assert!(deserialized.design.is_some());
877 assert_eq!(spec.tasks.len(), deserialized.tasks.len());
878 assert_eq!(spec.metadata.author, deserialized.metadata.author);
879 assert!(deserialized.inheritance.is_some());
880 }
881
882 #[test]
883 fn test_spec_serialization_minimal() {
884 let now = Utc::now();
885 let spec = Spec {
886 id: "minimal".to_string(),
887 name: "Minimal Spec".to_string(),
888 version: "0.1.0".to_string(),
889 requirements: vec![],
890 design: None,
891 tasks: vec![],
892 metadata: SpecMetadata {
893 author: None,
894 created_at: now,
895 updated_at: now,
896 phase: SpecPhase::Discovery,
897 status: SpecStatus::Draft,
898 },
899 inheritance: None,
900 };
901
902 let json = serde_json::to_string(&spec).unwrap();
903 let deserialized: Spec = serde_json::from_str(&json).unwrap();
904
905 assert_eq!(spec.id, deserialized.id);
906 assert!(deserialized.design.is_none());
907 assert!(deserialized.inheritance.is_none());
908 assert!(deserialized.metadata.author.is_none());
909 }
910
911 #[test]
916 fn test_spec_yaml_serialization() {
917 let now = Utc::now();
918 let spec = Spec {
919 id: "yaml-test".to_string(),
920 name: "YAML Test Spec".to_string(),
921 version: "1.0.0".to_string(),
922 requirements: vec![],
923 design: None,
924 tasks: vec![],
925 metadata: SpecMetadata {
926 author: Some("Test Author".to_string()),
927 created_at: now,
928 updated_at: now,
929 phase: SpecPhase::Requirements,
930 status: SpecStatus::Draft,
931 },
932 inheritance: None,
933 };
934
935 let yaml = serde_yaml::to_string(&spec).unwrap();
936 let deserialized: Spec = serde_yaml::from_str(&yaml).unwrap();
937
938 assert_eq!(spec.id, deserialized.id);
939 assert_eq!(spec.name, deserialized.name);
940 assert_eq!(spec.metadata.author, deserialized.metadata.author);
941 }
942
943 #[test]
944 fn test_requirement_yaml_serialization() {
945 let requirement = Requirement {
946 id: "REQ-1".to_string(),
947 user_story: "As a user, I want to manage tasks".to_string(),
948 acceptance_criteria: vec![AcceptanceCriterion {
949 id: "AC-1.1".to_string(),
950 when: "user clicks add".to_string(),
951 then: "task is created".to_string(),
952 }],
953 priority: Priority::Must,
954 };
955
956 let yaml = serde_yaml::to_string(&requirement).unwrap();
957 let deserialized: Requirement = serde_yaml::from_str(&yaml).unwrap();
958
959 assert_eq!(requirement.id, deserialized.id);
960 assert_eq!(requirement.priority, deserialized.priority);
961 }
962
963 #[test]
968 fn test_spec_query_default() {
969 let query = SpecQuery::default();
970
971 assert!(query.name.is_none());
972 assert!(query.spec_type.is_none());
973 assert!(query.status.is_none());
974 assert!(query.priority.is_none());
975 assert!(query.phase.is_none());
976 assert!(query.custom_filters.is_empty());
977 }
978
979 #[test]
980 fn test_spec_query_with_filters() {
981 let query = SpecQuery {
982 name: Some("task-management".to_string()),
983 spec_type: Some(SpecType::Feature),
984 status: Some(SpecStatus::Approved),
985 priority: Some(Priority::Must),
986 phase: Some(SpecPhase::Design),
987 custom_filters: vec![("author".to_string(), "John".to_string())],
988 };
989
990 assert_eq!(query.name, Some("task-management".to_string()));
991 assert_eq!(query.spec_type, Some(SpecType::Feature));
992 assert_eq!(query.status, Some(SpecStatus::Approved));
993 assert_eq!(query.priority, Some(Priority::Must));
994 assert_eq!(query.phase, Some(SpecPhase::Design));
995 assert_eq!(query.custom_filters.len(), 1);
996 }
997
998 #[test]
1003 fn test_approval_gate_creation() {
1004 let gate = ApprovalGate {
1005 phase: SpecPhase::Requirements,
1006 approved: false,
1007 approved_at: None,
1008 approved_by: None,
1009 feedback: None,
1010 };
1011
1012 assert_eq!(gate.phase, SpecPhase::Requirements);
1013 assert!(!gate.approved);
1014 assert!(gate.approved_at.is_none());
1015 assert!(gate.approved_by.is_none());
1016 assert!(gate.feedback.is_none());
1017 }
1018
1019 #[test]
1020 fn test_approval_gate_approved() {
1021 let now = Utc::now();
1022 let gate = ApprovalGate {
1023 phase: SpecPhase::Design,
1024 approved: true,
1025 approved_at: Some(now),
1026 approved_by: Some("reviewer".to_string()),
1027 feedback: Some("Looks good".to_string()),
1028 };
1029
1030 assert_eq!(gate.phase, SpecPhase::Design);
1031 assert!(gate.approved);
1032 assert_eq!(gate.approved_at, Some(now));
1033 assert_eq!(gate.approved_by, Some("reviewer".to_string()));
1034 assert_eq!(gate.feedback, Some("Looks good".to_string()));
1035 }
1036
1037 #[test]
1038 fn test_approval_gate_serialization() {
1039 let now = Utc::now();
1040 let gate = ApprovalGate {
1041 phase: SpecPhase::Tasks,
1042 approved: true,
1043 approved_at: Some(now),
1044 approved_by: Some("john".to_string()),
1045 feedback: Some("Ready to implement".to_string()),
1046 };
1047
1048 let json = serde_json::to_string(&gate).unwrap();
1049 let deserialized: ApprovalGate = serde_json::from_str(&json).unwrap();
1050
1051 assert_eq!(gate.phase, deserialized.phase);
1052 assert_eq!(gate.approved, deserialized.approved);
1053 assert_eq!(gate.approved_by, deserialized.approved_by);
1054 assert_eq!(gate.feedback, deserialized.feedback);
1055 }
1056
1057 #[test]
1062 fn test_spec_writing_session_creation() {
1063 let now = Utc::now();
1064 let session = SpecWritingSession {
1065 id: "session-1".to_string(),
1066 spec_id: "spec-1".to_string(),
1067 phase: SpecPhase::Requirements,
1068 conversation_history: vec![],
1069 approval_gates: vec![],
1070 created_at: now,
1071 updated_at: now,
1072 };
1073
1074 assert_eq!(session.id, "session-1");
1075 assert_eq!(session.spec_id, "spec-1");
1076 assert_eq!(session.phase, SpecPhase::Requirements);
1077 assert!(session.conversation_history.is_empty());
1078 assert!(session.approval_gates.is_empty());
1079 }
1080
1081 #[test]
1082 fn test_spec_writing_session_with_messages() {
1083 let now = Utc::now();
1084 let messages = vec![
1085 ConversationMessage {
1086 id: "msg-1".to_string(),
1087 spec_id: "spec-1".to_string(),
1088 role: MessageRole::User,
1089 content: "Create a task management system".to_string(),
1090 timestamp: now,
1091 },
1092 ConversationMessage {
1093 id: "msg-2".to_string(),
1094 spec_id: "spec-1".to_string(),
1095 role: MessageRole::Assistant,
1096 content: "I'll help you create a task management system".to_string(),
1097 timestamp: now,
1098 },
1099 ];
1100
1101 let session = SpecWritingSession {
1102 id: "session-1".to_string(),
1103 spec_id: "spec-1".to_string(),
1104 phase: SpecPhase::Requirements,
1105 conversation_history: messages.clone(),
1106 approval_gates: vec![],
1107 created_at: now,
1108 updated_at: now,
1109 };
1110
1111 assert_eq!(session.conversation_history.len(), 2);
1112 assert_eq!(session.conversation_history[0].role, MessageRole::User);
1113 assert_eq!(session.conversation_history[1].role, MessageRole::Assistant);
1114 }
1115
1116 #[test]
1117 fn test_spec_writing_session_with_approval_gates() {
1118 let now = Utc::now();
1119 let gates = vec![
1120 ApprovalGate {
1121 phase: SpecPhase::Requirements,
1122 approved: true,
1123 approved_at: Some(now),
1124 approved_by: Some("reviewer".to_string()),
1125 feedback: None,
1126 },
1127 ApprovalGate {
1128 phase: SpecPhase::Design,
1129 approved: false,
1130 approved_at: None,
1131 approved_by: None,
1132 feedback: None,
1133 },
1134 ];
1135
1136 let session = SpecWritingSession {
1137 id: "session-1".to_string(),
1138 spec_id: "spec-1".to_string(),
1139 phase: SpecPhase::Design,
1140 conversation_history: vec![],
1141 approval_gates: gates.clone(),
1142 created_at: now,
1143 updated_at: now,
1144 };
1145
1146 assert_eq!(session.approval_gates.len(), 2);
1147 assert!(session.approval_gates[0].approved);
1148 assert!(!session.approval_gates[1].approved);
1149 }
1150
1151 #[test]
1152 fn test_spec_writing_session_serialization() {
1153 let now = Utc::now();
1154 let session = SpecWritingSession {
1155 id: "session-1".to_string(),
1156 spec_id: "spec-1".to_string(),
1157 phase: SpecPhase::Requirements,
1158 conversation_history: vec![ConversationMessage {
1159 id: "msg-1".to_string(),
1160 spec_id: "spec-1".to_string(),
1161 role: MessageRole::User,
1162 content: "Create a system".to_string(),
1163 timestamp: now,
1164 }],
1165 approval_gates: vec![ApprovalGate {
1166 phase: SpecPhase::Requirements,
1167 approved: false,
1168 approved_at: None,
1169 approved_by: None,
1170 feedback: None,
1171 }],
1172 created_at: now,
1173 updated_at: now,
1174 };
1175
1176 let json = serde_json::to_string(&session).unwrap();
1177 let deserialized: SpecWritingSession = serde_json::from_str(&json).unwrap();
1178
1179 assert_eq!(session.id, deserialized.id);
1180 assert_eq!(session.spec_id, deserialized.spec_id);
1181 assert_eq!(session.phase, deserialized.phase);
1182 assert_eq!(
1183 session.conversation_history.len(),
1184 deserialized.conversation_history.len()
1185 );
1186 assert_eq!(
1187 session.approval_gates.len(),
1188 deserialized.approval_gates.len()
1189 );
1190 }
1191
1192 #[test]
1193 fn test_spec_writing_session_yaml_serialization() {
1194 let now = Utc::now();
1195 let session = SpecWritingSession {
1196 id: "session-1".to_string(),
1197 spec_id: "spec-1".to_string(),
1198 phase: SpecPhase::Design,
1199 conversation_history: vec![],
1200 approval_gates: vec![],
1201 created_at: now,
1202 updated_at: now,
1203 };
1204
1205 let yaml = serde_yaml::to_string(&session).unwrap();
1206 let deserialized: SpecWritingSession = serde_yaml::from_str(&yaml).unwrap();
1207
1208 assert_eq!(session.id, deserialized.id);
1209 assert_eq!(session.spec_id, deserialized.spec_id);
1210 assert_eq!(session.phase, deserialized.phase);
1211 }
1212
1213 #[test]
1214 fn test_spec_writing_session_phase_progression() {
1215 let now = Utc::now();
1216 let phases = vec![
1217 SpecPhase::Discovery,
1218 SpecPhase::Requirements,
1219 SpecPhase::Design,
1220 SpecPhase::Tasks,
1221 SpecPhase::Execution,
1222 ];
1223
1224 for phase in phases {
1225 let session = SpecWritingSession {
1226 id: "session-1".to_string(),
1227 spec_id: "spec-1".to_string(),
1228 phase,
1229 conversation_history: vec![],
1230 approval_gates: vec![],
1231 created_at: now,
1232 updated_at: now,
1233 };
1234
1235 assert_eq!(session.phase, phase);
1236 }
1237 }
1238}