ricecoder_teams/
models.rs

1/// Data models for the team collaboration system
2use chrono::{DateTime, Utc};
3use serde::{Deserialize, Serialize};
4
5/// Represents a team in the organization
6#[derive(Debug, Clone, Serialize, Deserialize)]
7pub struct Team {
8    pub id: String,
9    pub name: String,
10    pub organization_id: Option<String>,
11    pub members: Vec<TeamMember>,
12    pub created_at: DateTime<Utc>,
13    pub updated_at: DateTime<Utc>,
14}
15
16/// Represents a member of a team
17#[derive(Debug, Clone, Serialize, Deserialize)]
18pub struct TeamMember {
19    pub id: String,
20    pub name: String,
21    pub email: String,
22    pub role: TeamRole,
23    pub joined_at: DateTime<Utc>,
24}
25
26/// Role of a team member
27#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
28pub enum TeamRole {
29    Admin,
30    Member,
31    Viewer,
32}
33
34impl TeamRole {
35    pub fn as_str(&self) -> &'static str {
36        match self {
37            TeamRole::Admin => "admin",
38            TeamRole::Member => "member",
39            TeamRole::Viewer => "viewer",
40        }
41    }
42}
43
44impl std::str::FromStr for TeamRole {
45    type Err = String;
46
47    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
48        match s {
49            "admin" => Ok(TeamRole::Admin),
50            "member" => Ok(TeamRole::Member),
51            "viewer" => Ok(TeamRole::Viewer),
52            _ => Err(format!("Invalid team role: {}", s)),
53        }
54    }
55}
56
57/// Team standards and governance
58#[derive(Debug, Clone, Serialize, Deserialize)]
59pub struct TeamStandards {
60    pub id: String,
61    pub team_id: String,
62    pub code_review_rules: Vec<CodeReviewRule>,
63    pub templates: Vec<Template>,
64    pub steering_docs: Vec<SteeringDoc>,
65    pub compliance_requirements: Vec<ComplianceRequirement>,
66    pub version: u32,
67    pub created_at: DateTime<Utc>,
68    pub updated_at: DateTime<Utc>,
69}
70
71/// Code review rule
72#[derive(Debug, Clone, Serialize, Deserialize)]
73pub struct CodeReviewRule {
74    pub id: String,
75    pub name: String,
76    pub description: String,
77    pub enabled: bool,
78}
79
80/// Project template
81#[derive(Debug, Clone, Serialize, Deserialize)]
82pub struct Template {
83    pub id: String,
84    pub name: String,
85    pub description: String,
86    pub content: String,
87}
88
89/// Steering document
90#[derive(Debug, Clone, Serialize, Deserialize)]
91pub struct SteeringDoc {
92    pub id: String,
93    pub name: String,
94    pub content: String,
95}
96
97/// Compliance requirement
98#[derive(Debug, Clone, Serialize, Deserialize)]
99pub struct ComplianceRequirement {
100    pub id: String,
101    pub name: String,
102    pub description: String,
103}
104
105/// Shared rule that can be promoted across scopes
106#[derive(Debug, Clone, Serialize, Deserialize)]
107pub struct SharedRule {
108    pub id: String,
109    pub name: String,
110    pub description: String,
111    pub scope: RuleScope,
112    pub enforced: bool,
113    pub promoted_by: String,
114    pub promoted_at: DateTime<Utc>,
115    pub version: u32,
116}
117
118/// Scope of a rule
119#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
120pub enum RuleScope {
121    Project,
122    Team,
123    Organization,
124}
125
126impl RuleScope {
127    pub fn as_str(&self) -> &'static str {
128        match self {
129            RuleScope::Project => "project",
130            RuleScope::Team => "team",
131            RuleScope::Organization => "organization",
132        }
133    }
134}
135
136impl std::str::FromStr for RuleScope {
137    type Err = String;
138
139    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
140        match s {
141            "project" => Ok(RuleScope::Project),
142            "team" => Ok(RuleScope::Team),
143            "organization" => Ok(RuleScope::Organization),
144            _ => Err(format!("Invalid rule scope: {}", s)),
145        }
146    }
147}
148
149/// Adoption metrics for a rule
150#[derive(Debug, Clone, Serialize, Deserialize)]
151pub struct AdoptionMetrics {
152    pub rule_id: String,
153    pub total_members: u32,
154    pub adopting_members: u32,
155    pub adoption_percentage: f64,
156    pub adoption_trend: Vec<(DateTime<Utc>, f64)>,
157}
158
159/// Effectiveness metrics for a rule
160#[derive(Debug, Clone, Serialize, Deserialize)]
161pub struct EffectivenessMetrics {
162    pub rule_id: String,
163    pub positive_outcomes: u32,
164    pub negative_outcomes: u32,
165    pub effectiveness_score: f64,
166    pub impact_trend: Vec<(DateTime<Utc>, f64)>,
167}
168
169/// Standards override for project-level customization
170#[derive(Debug, Clone, Serialize, Deserialize)]
171pub struct StandardsOverride {
172    pub project_id: String,
173    pub overridden_standards: Vec<String>,
174    pub created_at: DateTime<Utc>,
175}
176
177/// Merged standards from hierarchy
178#[derive(Debug, Clone, Serialize, Deserialize)]
179pub struct MergedStandards {
180    pub organization_standards: Option<TeamStandards>,
181    pub team_standards: Option<TeamStandards>,
182    pub project_standards: Option<TeamStandards>,
183    pub final_standards: TeamStandards,
184}
185
186/// Audit log entry for permission changes
187#[derive(Debug, Clone, Serialize, Deserialize)]
188pub struct AuditLogEntry {
189    pub id: String,
190    pub team_id: String,
191    pub user_id: String,
192    pub action: String,
193    pub resource: String,
194    pub result: String,
195    pub timestamp: DateTime<Utc>,
196}
197
198/// Team analytics report
199#[derive(Debug, Clone, Serialize, Deserialize)]
200pub struct TeamAnalyticsReport {
201    pub team_id: String,
202    pub total_members: u32,
203    pub adoption_metrics: Vec<AdoptionMetrics>,
204    pub effectiveness_metrics: Vec<EffectivenessMetrics>,
205    pub generated_at: DateTime<Utc>,
206}
207
208#[cfg(test)]
209mod tests {
210    use super::*;
211
212    // Helper function to create a test team
213    fn create_test_team() -> Team {
214        Team {
215            id: "team-1".to_string(),
216            name: "Test Team".to_string(),
217            organization_id: Some("org-1".to_string()),
218            members: vec![],
219            created_at: Utc::now(),
220            updated_at: Utc::now(),
221        }
222    }
223
224    // Helper function to create a test team member
225    fn create_test_member() -> TeamMember {
226        TeamMember {
227            id: "member-1".to_string(),
228            name: "John Doe".to_string(),
229            email: "john@example.com".to_string(),
230            role: TeamRole::Member,
231            joined_at: Utc::now(),
232        }
233    }
234
235    // Helper function to create test standards
236    fn create_test_standards() -> TeamStandards {
237        TeamStandards {
238            id: "standards-1".to_string(),
239            team_id: "team-1".to_string(),
240            code_review_rules: vec![CodeReviewRule {
241                id: "rule-1".to_string(),
242                name: "Test Rule".to_string(),
243                description: "A test rule".to_string(),
244                enabled: true,
245            }],
246            templates: vec![Template {
247                id: "template-1".to_string(),
248                name: "Test Template".to_string(),
249                description: "A test template".to_string(),
250                content: "template content".to_string(),
251            }],
252            steering_docs: vec![SteeringDoc {
253                id: "doc-1".to_string(),
254                name: "Test Doc".to_string(),
255                content: "doc content".to_string(),
256            }],
257            compliance_requirements: vec![ComplianceRequirement {
258                id: "compliance-1".to_string(),
259                name: "Test Compliance".to_string(),
260                description: "A test compliance requirement".to_string(),
261            }],
262            version: 1,
263            created_at: Utc::now(),
264            updated_at: Utc::now(),
265        }
266    }
267
268    #[test]
269    fn test_team_serialization_to_json() {
270        let team = create_test_team();
271        let json = serde_json::to_string(&team).expect("Failed to serialize to JSON");
272        assert!(json.contains("\"id\":\"team-1\""));
273        assert!(json.contains("\"name\":\"Test Team\""));
274    }
275
276    #[test]
277    fn test_team_deserialization_from_json() {
278        let json = r#"{"id":"team-1","name":"Test Team","organization_id":"org-1","members":[],"created_at":"2024-01-01T00:00:00Z","updated_at":"2024-01-01T00:00:00Z"}"#;
279        let team: Team = serde_json::from_str(json).expect("Failed to deserialize from JSON");
280        assert_eq!(team.id, "team-1");
281        assert_eq!(team.name, "Test Team");
282        assert_eq!(team.organization_id, Some("org-1".to_string()));
283    }
284
285    #[test]
286    fn test_team_serialization_to_yaml() {
287        let team = create_test_team();
288        let yaml = serde_yaml::to_string(&team).expect("Failed to serialize to YAML");
289        assert!(yaml.contains("id: team-1"));
290        assert!(yaml.contains("name: Test Team"));
291    }
292
293    #[test]
294    fn test_team_deserialization_from_yaml() {
295        let yaml = r#"
296id: team-1
297name: Test Team
298organization_id: org-1
299members: []
300created_at: 2024-01-01T00:00:00Z
301updated_at: 2024-01-01T00:00:00Z
302"#;
303        let team: Team = serde_yaml::from_str(yaml).expect("Failed to deserialize from YAML");
304        assert_eq!(team.id, "team-1");
305        assert_eq!(team.name, "Test Team");
306    }
307
308    #[test]
309    fn test_team_member_serialization_to_json() {
310        let member = create_test_member();
311        let json = serde_json::to_string(&member).expect("Failed to serialize to JSON");
312        assert!(json.contains("\"id\":\"member-1\""));
313        assert!(json.contains("\"email\":\"john@example.com\""));
314    }
315
316    #[test]
317    fn test_team_member_deserialization_from_json() {
318        let json = r#"{"id":"member-1","name":"John Doe","email":"john@example.com","role":"Member","joined_at":"2024-01-01T00:00:00Z"}"#;
319        let member: TeamMember =
320            serde_json::from_str(json).expect("Failed to deserialize from JSON");
321        assert_eq!(member.id, "member-1");
322        assert_eq!(member.name, "John Doe");
323        assert_eq!(member.role, TeamRole::Member);
324    }
325
326    #[test]
327    fn test_team_role_as_str() {
328        assert_eq!(TeamRole::Admin.as_str(), "admin");
329        assert_eq!(TeamRole::Member.as_str(), "member");
330        assert_eq!(TeamRole::Viewer.as_str(), "viewer");
331    }
332
333    #[test]
334    fn test_team_role_from_str() {
335        use std::str::FromStr;
336        assert_eq!(TeamRole::from_str("admin"), Ok(TeamRole::Admin));
337        assert_eq!(TeamRole::from_str("member"), Ok(TeamRole::Member));
338        assert_eq!(TeamRole::from_str("viewer"), Ok(TeamRole::Viewer));
339        assert!(TeamRole::from_str("invalid").is_err());
340    }
341
342    #[test]
343    fn test_team_standards_serialization_to_json() {
344        let standards = create_test_standards();
345        let json = serde_json::to_string(&standards).expect("Failed to serialize to JSON");
346        assert!(json.contains("\"id\":\"standards-1\""));
347        assert!(json.contains("\"team_id\":\"team-1\""));
348        assert!(json.contains("\"version\":1"));
349    }
350
351    #[test]
352    fn test_team_standards_deserialization_from_json() {
353        let standards = create_test_standards();
354        let json = serde_json::to_string(&standards).expect("Failed to serialize");
355        let deserialized: TeamStandards =
356            serde_json::from_str(&json).expect("Failed to deserialize");
357        assert_eq!(deserialized.id, standards.id);
358        assert_eq!(deserialized.team_id, standards.team_id);
359        assert_eq!(deserialized.version, standards.version);
360        assert_eq!(deserialized.code_review_rules.len(), 1);
361        assert_eq!(deserialized.templates.len(), 1);
362        assert_eq!(deserialized.steering_docs.len(), 1);
363        assert_eq!(deserialized.compliance_requirements.len(), 1);
364    }
365
366    #[test]
367    fn test_team_standards_serialization_to_yaml() {
368        let standards = create_test_standards();
369        let yaml = serde_yaml::to_string(&standards).expect("Failed to serialize to YAML");
370        assert!(yaml.contains("id: standards-1"));
371        assert!(yaml.contains("team_id: team-1"));
372        assert!(yaml.contains("version: 1"));
373    }
374
375    #[test]
376    fn test_team_standards_deserialization_from_yaml() {
377        let standards = create_test_standards();
378        let yaml = serde_yaml::to_string(&standards).expect("Failed to serialize");
379        let deserialized: TeamStandards =
380            serde_yaml::from_str(&yaml).expect("Failed to deserialize");
381        assert_eq!(deserialized.id, standards.id);
382        assert_eq!(deserialized.team_id, standards.team_id);
383        assert_eq!(deserialized.version, standards.version);
384    }
385
386    #[test]
387    fn test_shared_rule_serialization_to_json() {
388        let rule = SharedRule {
389            id: "rule-1".to_string(),
390            name: "Test Rule".to_string(),
391            description: "A test rule".to_string(),
392            scope: RuleScope::Team,
393            enforced: true,
394            promoted_by: "admin-1".to_string(),
395            promoted_at: Utc::now(),
396            version: 1,
397        };
398        let json = serde_json::to_string(&rule).expect("Failed to serialize to JSON");
399        assert!(json.contains("\"id\":\"rule-1\""));
400        assert!(json.contains("\"scope\":\"Team\""));
401    }
402
403    #[test]
404    fn test_shared_rule_deserialization_from_json() {
405        let json = r#"{"id":"rule-1","name":"Test Rule","description":"A test rule","scope":"Team","enforced":true,"promoted_by":"admin-1","promoted_at":"2024-01-01T00:00:00Z","version":1}"#;
406        let rule: SharedRule = serde_json::from_str(json).expect("Failed to deserialize from JSON");
407        assert_eq!(rule.id, "rule-1");
408        assert_eq!(rule.scope, RuleScope::Team);
409        assert_eq!(rule.version, 1);
410    }
411
412    #[test]
413    fn test_rule_scope_as_str() {
414        assert_eq!(RuleScope::Project.as_str(), "project");
415        assert_eq!(RuleScope::Team.as_str(), "team");
416        assert_eq!(RuleScope::Organization.as_str(), "organization");
417    }
418
419    #[test]
420    fn test_rule_scope_from_str() {
421        use std::str::FromStr;
422        assert_eq!(RuleScope::from_str("project"), Ok(RuleScope::Project));
423        assert_eq!(RuleScope::from_str("team"), Ok(RuleScope::Team));
424        assert_eq!(
425            RuleScope::from_str("organization"),
426            Ok(RuleScope::Organization)
427        );
428        assert!(RuleScope::from_str("invalid").is_err());
429    }
430
431    #[test]
432    fn test_adoption_metrics_serialization() {
433        let metrics = AdoptionMetrics {
434            rule_id: "rule-1".to_string(),
435            total_members: 10,
436            adopting_members: 8,
437            adoption_percentage: 80.0,
438            adoption_trend: vec![(Utc::now(), 75.0), (Utc::now(), 80.0)],
439        };
440        let json = serde_json::to_string(&metrics).expect("Failed to serialize to JSON");
441        assert!(json.contains("\"rule_id\":\"rule-1\""));
442        assert!(json.contains("\"total_members\":10"));
443        assert!(json.contains("\"adopting_members\":8"));
444    }
445
446    #[test]
447    fn test_effectiveness_metrics_serialization() {
448        let metrics = EffectivenessMetrics {
449            rule_id: "rule-1".to_string(),
450            positive_outcomes: 15,
451            negative_outcomes: 2,
452            effectiveness_score: 0.88,
453            impact_trend: vec![(Utc::now(), 0.85), (Utc::now(), 0.88)],
454        };
455        let json = serde_json::to_string(&metrics).expect("Failed to serialize to JSON");
456        assert!(json.contains("\"rule_id\":\"rule-1\""));
457        assert!(json.contains("\"positive_outcomes\":15"));
458    }
459
460    #[test]
461    fn test_audit_log_entry_serialization() {
462        let entry = AuditLogEntry {
463            id: "log-1".to_string(),
464            team_id: "team-1".to_string(),
465            user_id: "user-1".to_string(),
466            action: "create_rule".to_string(),
467            resource: "rule-1".to_string(),
468            result: "success".to_string(),
469            timestamp: Utc::now(),
470        };
471        let json = serde_json::to_string(&entry).expect("Failed to serialize to JSON");
472        assert!(json.contains("\"id\":\"log-1\""));
473        assert!(json.contains("\"action\":\"create_rule\""));
474    }
475
476    #[test]
477    fn test_team_analytics_report_serialization() {
478        let report = TeamAnalyticsReport {
479            team_id: "team-1".to_string(),
480            total_members: 10,
481            adoption_metrics: vec![],
482            effectiveness_metrics: vec![],
483            generated_at: Utc::now(),
484        };
485        let json = serde_json::to_string(&report).expect("Failed to serialize to JSON");
486        assert!(json.contains("\"team_id\":\"team-1\""));
487        assert!(json.contains("\"total_members\":10"));
488    }
489
490    #[test]
491    fn test_round_trip_team_json() {
492        let original = create_test_team();
493        let json = serde_json::to_string(&original).expect("Failed to serialize");
494        let deserialized: Team = serde_json::from_str(&json).expect("Failed to deserialize");
495        assert_eq!(original.id, deserialized.id);
496        assert_eq!(original.name, deserialized.name);
497        assert_eq!(original.organization_id, deserialized.organization_id);
498    }
499
500    #[test]
501    fn test_round_trip_team_yaml() {
502        let original = create_test_team();
503        let yaml = serde_yaml::to_string(&original).expect("Failed to serialize");
504        let deserialized: Team = serde_yaml::from_str(&yaml).expect("Failed to deserialize");
505        assert_eq!(original.id, deserialized.id);
506        assert_eq!(original.name, deserialized.name);
507        assert_eq!(original.organization_id, deserialized.organization_id);
508    }
509
510    #[test]
511    fn test_round_trip_standards_json() {
512        let original = create_test_standards();
513        let json = serde_json::to_string(&original).expect("Failed to serialize");
514        let deserialized: TeamStandards =
515            serde_json::from_str(&json).expect("Failed to deserialize");
516        assert_eq!(original.id, deserialized.id);
517        assert_eq!(original.team_id, deserialized.team_id);
518        assert_eq!(original.version, deserialized.version);
519        assert_eq!(
520            original.code_review_rules.len(),
521            deserialized.code_review_rules.len()
522        );
523    }
524
525    #[test]
526    fn test_round_trip_standards_yaml() {
527        let original = create_test_standards();
528        let yaml = serde_yaml::to_string(&original).expect("Failed to serialize");
529        let deserialized: TeamStandards =
530            serde_yaml::from_str(&yaml).expect("Failed to deserialize");
531        assert_eq!(original.id, deserialized.id);
532        assert_eq!(original.team_id, deserialized.team_id);
533        assert_eq!(original.version, deserialized.version);
534    }
535
536    #[test]
537    fn test_team_member_with_different_roles() {
538        let admin = TeamMember {
539            id: "admin-1".to_string(),
540            name: "Admin User".to_string(),
541            email: "admin@example.com".to_string(),
542            role: TeamRole::Admin,
543            joined_at: Utc::now(),
544        };
545
546        let member = TeamMember {
547            id: "member-1".to_string(),
548            name: "Regular Member".to_string(),
549            email: "member@example.com".to_string(),
550            role: TeamRole::Member,
551            joined_at: Utc::now(),
552        };
553
554        let viewer = TeamMember {
555            id: "viewer-1".to_string(),
556            name: "Viewer User".to_string(),
557            email: "viewer@example.com".to_string(),
558            role: TeamRole::Viewer,
559            joined_at: Utc::now(),
560        };
561
562        assert_eq!(admin.role, TeamRole::Admin);
563        assert_eq!(member.role, TeamRole::Member);
564        assert_eq!(viewer.role, TeamRole::Viewer);
565    }
566
567    #[test]
568    fn test_standards_override_serialization() {
569        let override_data = StandardsOverride {
570            project_id: "project-1".to_string(),
571            overridden_standards: vec!["rule-1".to_string(), "rule-2".to_string()],
572            created_at: Utc::now(),
573        };
574        let json = serde_json::to_string(&override_data).expect("Failed to serialize to JSON");
575        assert!(json.contains("\"project_id\":\"project-1\""));
576        assert!(json.contains("\"overridden_standards\""));
577    }
578
579    #[test]
580    fn test_merged_standards_serialization() {
581        let merged = MergedStandards {
582            organization_standards: Some(create_test_standards()),
583            team_standards: Some(create_test_standards()),
584            project_standards: None,
585            final_standards: create_test_standards(),
586        };
587        let json = serde_json::to_string(&merged).expect("Failed to serialize to JSON");
588        assert!(json.contains("\"organization_standards\""));
589        assert!(json.contains("\"team_standards\""));
590        assert!(json.contains("\"final_standards\""));
591    }
592}