1use serde::{Deserialize, Serialize};
4
5use crate::enums::{LabelName, LinkType, ParameterMode, Severity, Stage, Status};
6
7#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
9#[serde(rename_all = "camelCase")]
10pub struct TestResult {
11 pub uuid: String,
13
14 #[serde(skip_serializing_if = "Option::is_none")]
16 pub history_id: Option<String>,
17
18 #[serde(skip_serializing_if = "Option::is_none")]
20 pub test_case_id: Option<String>,
21
22 pub name: String,
24
25 #[serde(skip_serializing_if = "Option::is_none")]
27 pub full_name: Option<String>,
28
29 #[serde(skip_serializing_if = "Option::is_none")]
31 pub description: Option<String>,
32
33 #[serde(skip_serializing_if = "Option::is_none")]
35 pub description_html: Option<String>,
36
37 pub status: Status,
39
40 #[serde(skip_serializing_if = "Option::is_none")]
42 pub status_details: Option<StatusDetails>,
43
44 pub stage: Stage,
46
47 #[serde(default, skip_serializing_if = "Vec::is_empty")]
49 pub steps: Vec<StepResult>,
50
51 #[serde(default, skip_serializing_if = "Vec::is_empty")]
53 pub attachments: Vec<Attachment>,
54
55 #[serde(default, skip_serializing_if = "Vec::is_empty")]
57 pub parameters: Vec<Parameter>,
58
59 #[serde(default, skip_serializing_if = "Vec::is_empty")]
61 pub labels: Vec<Label>,
62
63 #[serde(default, skip_serializing_if = "Vec::is_empty")]
65 pub links: Vec<Link>,
66
67 pub start: i64,
69
70 pub stop: i64,
72}
73
74impl TestResult {
75 pub fn new(uuid: String, name: String) -> Self {
77 let now = current_time_ms();
78 Self {
79 uuid,
80 history_id: None,
81 test_case_id: None,
82 name,
83 full_name: None,
84 description: None,
85 description_html: None,
86 status: Status::Unknown,
87 status_details: None,
88 stage: Stage::Running,
89 steps: Vec::new(),
90 attachments: Vec::new(),
91 parameters: Vec::new(),
92 labels: Vec::new(),
93 links: Vec::new(),
94 start: now,
95 stop: now,
96 }
97 }
98
99 pub fn add_label(&mut self, name: impl Into<String>, value: impl Into<String>) {
101 self.labels.push(Label {
102 name: name.into(),
103 value: value.into(),
104 });
105 }
106
107 pub fn add_label_name(&mut self, name: LabelName, value: impl Into<String>) {
109 self.add_label(name.as_str(), value);
110 }
111
112 pub fn add_link(&mut self, url: impl Into<String>, name: Option<String>, link_type: LinkType) {
114 self.links.push(Link {
115 name,
116 url: url.into(),
117 r#type: Some(link_type),
118 });
119 }
120
121 pub fn add_parameter(&mut self, name: impl Into<String>, value: impl Into<String>) {
123 self.parameters.push(Parameter {
124 name: name.into(),
125 value: value.into(),
126 excluded: None,
127 mode: None,
128 });
129 }
130
131 pub fn add_attachment(&mut self, attachment: Attachment) {
133 self.attachments.push(attachment);
134 }
135
136 pub fn add_step(&mut self, step: StepResult) {
138 self.steps.push(step);
139 }
140
141 pub fn set_status(&mut self, status: Status) {
143 self.status = status;
144 }
145
146 pub fn finish(&mut self) {
148 self.stop = current_time_ms();
149 self.stage = Stage::Finished;
150 }
151
152 pub fn pass(&mut self) {
154 self.status = Status::Passed;
155 self.finish();
156 }
157
158 pub fn fail(&mut self, message: Option<String>, trace: Option<String>) {
160 self.status = Status::Failed;
161 if message.is_some() || trace.is_some() {
162 self.status_details = Some(StatusDetails {
163 message,
164 trace,
165 ..Default::default()
166 });
167 }
168 self.finish();
169 }
170
171 pub fn broken(&mut self, message: Option<String>, trace: Option<String>) {
173 self.status = Status::Broken;
174 if message.is_some() || trace.is_some() {
175 self.status_details = Some(StatusDetails {
176 message,
177 trace,
178 ..Default::default()
179 });
180 }
181 self.finish();
182 }
183}
184
185#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
187#[serde(rename_all = "camelCase")]
188pub struct StepResult {
189 #[serde(skip_serializing_if = "Option::is_none")]
191 pub uuid: Option<String>,
192
193 pub name: String,
195
196 pub status: Status,
198
199 #[serde(skip_serializing_if = "Option::is_none")]
201 pub status_details: Option<StatusDetails>,
202
203 pub stage: Stage,
205
206 #[serde(default, skip_serializing_if = "Vec::is_empty")]
208 pub steps: Vec<StepResult>,
209
210 #[serde(default, skip_serializing_if = "Vec::is_empty")]
212 pub attachments: Vec<Attachment>,
213
214 #[serde(default, skip_serializing_if = "Vec::is_empty")]
216 pub parameters: Vec<Parameter>,
217
218 pub start: i64,
220
221 pub stop: i64,
223}
224
225impl StepResult {
226 pub fn new(name: impl Into<String>) -> Self {
228 let now = current_time_ms();
229 Self {
230 uuid: None,
231 name: name.into(),
232 status: Status::Unknown,
233 status_details: None,
234 stage: Stage::Running,
235 steps: Vec::new(),
236 attachments: Vec::new(),
237 parameters: Vec::new(),
238 start: now,
239 stop: now,
240 }
241 }
242
243 pub fn add_step(&mut self, step: StepResult) {
245 self.steps.push(step);
246 }
247
248 pub fn add_attachment(&mut self, attachment: Attachment) {
250 self.attachments.push(attachment);
251 }
252
253 pub fn add_parameter(&mut self, name: impl Into<String>, value: impl Into<String>) {
255 self.parameters.push(Parameter {
256 name: name.into(),
257 value: value.into(),
258 excluded: None,
259 mode: None,
260 });
261 }
262
263 pub fn pass(&mut self) {
265 self.status = Status::Passed;
266 self.stage = Stage::Finished;
267 self.stop = current_time_ms();
268 }
269
270 pub fn fail(&mut self, message: Option<String>, trace: Option<String>) {
272 self.status = Status::Failed;
273 self.stage = Stage::Finished;
274 self.stop = current_time_ms();
275 if message.is_some() || trace.is_some() {
276 self.status_details = Some(StatusDetails {
277 message,
278 trace,
279 ..Default::default()
280 });
281 }
282 }
283
284 pub fn broken(&mut self, message: Option<String>, trace: Option<String>) {
286 self.status = Status::Broken;
287 self.stage = Stage::Finished;
288 self.stop = current_time_ms();
289 if message.is_some() || trace.is_some() {
290 self.status_details = Some(StatusDetails {
291 message,
292 trace,
293 ..Default::default()
294 });
295 }
296 }
297}
298
299#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
301#[serde(rename_all = "camelCase")]
302pub struct StatusDetails {
303 #[serde(skip_serializing_if = "Option::is_none")]
305 pub known: Option<bool>,
306
307 #[serde(skip_serializing_if = "Option::is_none")]
309 pub muted: Option<bool>,
310
311 #[serde(skip_serializing_if = "Option::is_none")]
313 pub flaky: Option<bool>,
314
315 #[serde(skip_serializing_if = "Option::is_none")]
317 pub message: Option<String>,
318
319 #[serde(skip_serializing_if = "Option::is_none")]
321 pub trace: Option<String>,
322}
323
324#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
326pub struct Label {
327 pub name: String,
329
330 pub value: String,
332}
333
334impl Label {
335 pub fn new(name: impl Into<String>, value: impl Into<String>) -> Self {
337 Self {
338 name: name.into(),
339 value: value.into(),
340 }
341 }
342
343 pub fn from_name(name: LabelName, value: impl Into<String>) -> Self {
345 Self::new(name.as_str(), value)
346 }
347
348 pub fn epic(value: impl Into<String>) -> Self {
350 Self::from_name(LabelName::Epic, value)
351 }
352
353 pub fn feature(value: impl Into<String>) -> Self {
355 Self::from_name(LabelName::Feature, value)
356 }
357
358 pub fn story(value: impl Into<String>) -> Self {
360 Self::from_name(LabelName::Story, value)
361 }
362
363 pub fn suite(value: impl Into<String>) -> Self {
365 Self::from_name(LabelName::Suite, value)
366 }
367
368 pub fn parent_suite(value: impl Into<String>) -> Self {
370 Self::from_name(LabelName::ParentSuite, value)
371 }
372
373 pub fn sub_suite(value: impl Into<String>) -> Self {
375 Self::from_name(LabelName::SubSuite, value)
376 }
377
378 pub fn severity(severity: Severity) -> Self {
380 Self::from_name(LabelName::Severity, severity.as_str())
381 }
382
383 pub fn owner(value: impl Into<String>) -> Self {
385 Self::from_name(LabelName::Owner, value)
386 }
387
388 pub fn tag(value: impl Into<String>) -> Self {
390 Self::from_name(LabelName::Tag, value)
391 }
392
393 pub fn allure_id(value: impl Into<String>) -> Self {
395 Self::from_name(LabelName::AllureId, value)
396 }
397
398 pub fn host(value: impl Into<String>) -> Self {
400 Self::from_name(LabelName::Host, value)
401 }
402
403 pub fn thread(value: impl Into<String>) -> Self {
405 Self::from_name(LabelName::Thread, value)
406 }
407
408 pub fn framework(value: impl Into<String>) -> Self {
410 Self::from_name(LabelName::Framework, value)
411 }
412
413 pub fn language(value: impl Into<String>) -> Self {
415 Self::from_name(LabelName::Language, value)
416 }
417
418 pub fn package(value: impl Into<String>) -> Self {
420 Self::from_name(LabelName::Package, value)
421 }
422
423 pub fn test_class(value: impl Into<String>) -> Self {
425 Self::from_name(LabelName::TestClass, value)
426 }
427
428 pub fn test_method(value: impl Into<String>) -> Self {
430 Self::from_name(LabelName::TestMethod, value)
431 }
432}
433
434#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
436pub struct Link {
437 #[serde(skip_serializing_if = "Option::is_none")]
439 pub name: Option<String>,
440
441 pub url: String,
443
444 #[serde(skip_serializing_if = "Option::is_none")]
446 pub r#type: Option<LinkType>,
447}
448
449impl Link {
450 pub fn new(url: impl Into<String>) -> Self {
452 Self {
453 name: None,
454 url: url.into(),
455 r#type: None,
456 }
457 }
458
459 pub fn with_name(url: impl Into<String>, name: impl Into<String>) -> Self {
461 Self {
462 name: Some(name.into()),
463 url: url.into(),
464 r#type: None,
465 }
466 }
467
468 pub fn issue(url: impl Into<String>, name: Option<String>) -> Self {
470 Self {
471 name,
472 url: url.into(),
473 r#type: Some(LinkType::Issue),
474 }
475 }
476
477 pub fn tms(url: impl Into<String>, name: Option<String>) -> Self {
479 Self {
480 name,
481 url: url.into(),
482 r#type: Some(LinkType::Tms),
483 }
484 }
485}
486
487#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
489pub struct Parameter {
490 pub name: String,
492
493 pub value: String,
495
496 #[serde(skip_serializing_if = "Option::is_none")]
498 pub excluded: Option<bool>,
499
500 #[serde(skip_serializing_if = "Option::is_none")]
502 pub mode: Option<ParameterMode>,
503}
504
505impl Parameter {
506 pub fn new(name: impl Into<String>, value: impl Into<String>) -> Self {
508 Self {
509 name: name.into(),
510 value: value.into(),
511 excluded: None,
512 mode: None,
513 }
514 }
515
516 pub fn excluded(name: impl Into<String>, value: impl Into<String>) -> Self {
518 Self {
519 name: name.into(),
520 value: value.into(),
521 excluded: Some(true),
522 mode: None,
523 }
524 }
525
526 pub fn hidden(name: impl Into<String>, value: impl Into<String>) -> Self {
528 Self {
529 name: name.into(),
530 value: value.into(),
531 excluded: None,
532 mode: Some(ParameterMode::Hidden),
533 }
534 }
535
536 pub fn masked(name: impl Into<String>, value: impl Into<String>) -> Self {
538 Self {
539 name: name.into(),
540 value: value.into(),
541 excluded: None,
542 mode: Some(ParameterMode::Masked),
543 }
544 }
545}
546
547#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
549pub struct Attachment {
550 pub name: String,
552
553 pub source: String,
555
556 #[serde(skip_serializing_if = "Option::is_none")]
558 pub r#type: Option<String>,
559}
560
561impl Attachment {
562 pub fn new(
564 name: impl Into<String>,
565 source: impl Into<String>,
566 mime_type: Option<String>,
567 ) -> Self {
568 Self {
569 name: name.into(),
570 source: source.into(),
571 r#type: mime_type,
572 }
573 }
574}
575
576#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
579#[serde(rename_all = "camelCase")]
580pub struct TestResultContainer {
581 pub uuid: String,
583
584 #[serde(skip_serializing_if = "Option::is_none")]
586 pub name: Option<String>,
587
588 #[serde(default, skip_serializing_if = "Vec::is_empty")]
590 pub children: Vec<String>,
591
592 #[serde(default, skip_serializing_if = "Vec::is_empty")]
594 pub befores: Vec<FixtureResult>,
595
596 #[serde(default, skip_serializing_if = "Vec::is_empty")]
598 pub afters: Vec<FixtureResult>,
599
600 #[serde(skip_serializing_if = "Option::is_none")]
602 pub start: Option<i64>,
603
604 #[serde(skip_serializing_if = "Option::is_none")]
606 pub stop: Option<i64>,
607}
608
609impl TestResultContainer {
610 pub fn new(uuid: String) -> Self {
612 Self {
613 uuid,
614 name: None,
615 children: Vec::new(),
616 befores: Vec::new(),
617 afters: Vec::new(),
618 start: None,
619 stop: None,
620 }
621 }
622
623 pub fn add_child(&mut self, test_uuid: String) {
625 self.children.push(test_uuid);
626 }
627
628 pub fn add_before(&mut self, fixture: FixtureResult) {
630 self.befores.push(fixture);
631 }
632
633 pub fn add_after(&mut self, fixture: FixtureResult) {
635 self.afters.push(fixture);
636 }
637}
638
639#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
641#[serde(rename_all = "camelCase")]
642pub struct FixtureResult {
643 pub name: String,
645
646 pub status: Status,
648
649 #[serde(skip_serializing_if = "Option::is_none")]
651 pub status_details: Option<StatusDetails>,
652
653 pub stage: Stage,
655
656 #[serde(default, skip_serializing_if = "Vec::is_empty")]
658 pub steps: Vec<StepResult>,
659
660 #[serde(default, skip_serializing_if = "Vec::is_empty")]
662 pub attachments: Vec<Attachment>,
663
664 #[serde(default, skip_serializing_if = "Vec::is_empty")]
666 pub parameters: Vec<Parameter>,
667
668 pub start: i64,
670
671 pub stop: i64,
673}
674
675impl FixtureResult {
676 pub fn new(name: impl Into<String>) -> Self {
678 let now = current_time_ms();
679 Self {
680 name: name.into(),
681 status: Status::Unknown,
682 status_details: None,
683 stage: Stage::Running,
684 steps: Vec::new(),
685 attachments: Vec::new(),
686 parameters: Vec::new(),
687 start: now,
688 stop: now,
689 }
690 }
691
692 pub fn pass(&mut self) {
694 self.status = Status::Passed;
695 self.stage = Stage::Finished;
696 self.stop = current_time_ms();
697 }
698
699 pub fn fail(&mut self, message: Option<String>, trace: Option<String>) {
701 self.status = Status::Failed;
702 self.stage = Stage::Finished;
703 self.stop = current_time_ms();
704 if message.is_some() || trace.is_some() {
705 self.status_details = Some(StatusDetails {
706 message,
707 trace,
708 ..Default::default()
709 });
710 }
711 }
712}
713
714#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
716#[serde(rename_all = "camelCase")]
717pub struct Category {
718 pub name: String,
720
721 #[serde(default, skip_serializing_if = "Vec::is_empty")]
723 pub matched_statuses: Vec<Status>,
724
725 #[serde(skip_serializing_if = "Option::is_none")]
727 pub message_regex: Option<String>,
728
729 #[serde(skip_serializing_if = "Option::is_none")]
731 pub trace_regex: Option<String>,
732
733 #[serde(skip_serializing_if = "Option::is_none")]
735 pub flaky: Option<bool>,
736}
737
738impl Category {
739 pub fn new(name: impl Into<String>) -> Self {
741 Self {
742 name: name.into(),
743 matched_statuses: Vec::new(),
744 message_regex: None,
745 trace_regex: None,
746 flaky: None,
747 }
748 }
749
750 pub fn with_status(mut self, status: Status) -> Self {
752 self.matched_statuses.push(status);
753 self
754 }
755
756 pub fn with_message_regex(mut self, regex: impl Into<String>) -> Self {
758 self.message_regex = Some(regex.into());
759 self
760 }
761
762 pub fn with_trace_regex(mut self, regex: impl Into<String>) -> Self {
764 self.trace_regex = Some(regex.into());
765 self
766 }
767
768 pub fn as_flaky(mut self) -> Self {
770 self.flaky = Some(true);
771 self
772 }
773}
774
775pub fn current_time_ms() -> i64 {
777 std::time::SystemTime::now()
778 .duration_since(std::time::UNIX_EPOCH)
779 .map(|d| d.as_millis() as i64)
780 .unwrap_or(0)
781}
782
783#[cfg(test)]
784mod tests {
785 use super::*;
786
787 #[test]
788 fn test_test_result_new() {
789 let result = TestResult::new("test-uuid".to_string(), "Test Name".to_string());
790 assert_eq!(result.uuid, "test-uuid");
791 assert_eq!(result.name, "Test Name");
792 assert_eq!(result.status, Status::Unknown);
793 assert_eq!(result.stage, Stage::Running);
794 }
795
796 #[test]
797 fn test_test_result_serialization() {
798 let mut result = TestResult::new("uuid-123".to_string(), "My Test".to_string());
799 result.add_label_name(LabelName::Epic, "Identity");
800 result.add_label_name(LabelName::Severity, "critical");
801 result.pass();
802
803 let json = serde_json::to_string_pretty(&result).unwrap();
804 assert!(json.contains("\"uuid\": \"uuid-123\""));
805 assert!(json.contains("\"name\": \"My Test\""));
806 assert!(json.contains("\"status\": \"passed\""));
807 assert!(json.contains("\"epic\""));
808 }
809
810 #[test]
811 fn test_step_result() {
812 let mut step = StepResult::new("Step 1");
813 step.add_parameter("input", "value");
814 step.pass();
815
816 assert_eq!(step.status, Status::Passed);
817 assert_eq!(step.stage, Stage::Finished);
818 assert_eq!(step.parameters.len(), 1);
819 }
820
821 #[test]
822 fn test_label_constructors() {
823 let epic = Label::epic("My Epic");
824 assert_eq!(epic.name, "epic");
825 assert_eq!(epic.value, "My Epic");
826
827 let severity = Label::severity(Severity::Critical);
828 assert_eq!(severity.name, "severity");
829 assert_eq!(severity.value, "critical");
830 }
831
832 #[test]
833 fn test_link_constructors() {
834 let issue = Link::issue("https://jira.com/PROJ-123", Some("PROJ-123".to_string()));
835 assert_eq!(issue.r#type, Some(LinkType::Issue));
836 assert_eq!(issue.url, "https://jira.com/PROJ-123");
837 }
838
839 #[test]
840 fn test_parameter_modes() {
841 let masked = Parameter::masked("password", "secret123");
842 assert_eq!(masked.mode, Some(ParameterMode::Masked));
843
844 let excluded = Parameter::excluded("timestamp", "123456");
845 assert_eq!(excluded.excluded, Some(true));
846 }
847
848 #[test]
849 fn test_container() {
850 let mut container = TestResultContainer::new("container-uuid".to_string());
851 container.add_child("test-1".to_string());
852 container.add_child("test-2".to_string());
853
854 let mut before = FixtureResult::new("setup");
855 before.pass();
856 container.add_before(before);
857
858 assert_eq!(container.children.len(), 2);
859 assert_eq!(container.befores.len(), 1);
860 }
861
862 #[test]
863 fn test_category() {
864 let category = Category::new("Infrastructure Issues")
865 .with_status(Status::Broken)
866 .with_message_regex(".*timeout.*")
867 .as_flaky();
868
869 assert_eq!(category.name, "Infrastructure Issues");
870 assert_eq!(category.matched_statuses, vec![Status::Broken]);
871 assert_eq!(category.message_regex, Some(".*timeout.*".to_string()));
872 assert_eq!(category.flaky, Some(true));
873 }
874
875 #[test]
876 fn test_test_result_fail_and_broken_details() {
877 let mut result = TestResult::new("u1".to_string(), "Name".to_string());
878 result.fail(Some("boom".into()), Some("trace".into()));
879 assert_eq!(result.status, Status::Failed);
880 let details = result.status_details.unwrap();
881 assert_eq!(details.message.as_deref(), Some("boom"));
882 assert_eq!(details.trace.as_deref(), Some("trace"));
883 assert_eq!(result.stage, Stage::Finished);
884
885 let mut broken = TestResult::new("u2".to_string(), "Name2".to_string());
886 broken.broken(None, None);
887 assert_eq!(broken.status, Status::Broken);
888 assert!(broken.status_details.is_none());
889 assert_eq!(broken.stage, Stage::Finished);
890 }
891
892 #[test]
893 fn test_step_result_fail_and_broken_details() {
894 let mut step = StepResult::new("fail-step");
895 step.fail(Some("oops".into()), None);
896 assert_eq!(step.status, Status::Failed);
897 assert_eq!(
898 step.status_details.unwrap().message.as_deref(),
899 Some("oops")
900 );
901
902 let mut broken = StepResult::new("broken-step");
903 broken.broken(None, Some("trace".into()));
904 assert_eq!(broken.status, Status::Broken);
905 assert_eq!(
906 broken.status_details.unwrap().trace.as_deref(),
907 Some("trace")
908 );
909 }
910
911 #[test]
912 fn test_fixture_result_fail_sets_details() {
913 let mut fixture = FixtureResult::new("setup");
914 fixture.fail(Some("failed".into()), Some("trace".into()));
915 assert_eq!(fixture.status, Status::Failed);
916 assert_eq!(fixture.stage, Stage::Finished);
917 let details = fixture.status_details.unwrap();
918 assert_eq!(details.message.as_deref(), Some("failed"));
919 assert_eq!(details.trace.as_deref(), Some("trace"));
920 }
921
922 #[test]
923 fn test_parameter_hidden_flag() {
924 let hidden = Parameter::hidden("secret", "value");
925 assert_eq!(hidden.mode, Some(ParameterMode::Hidden));
926 assert_eq!(hidden.excluded, None);
927 }
928
929 #[test]
930 fn test_link_with_name_and_default() {
931 let named = Link::with_name("https://example.test", "Example");
932 assert_eq!(named.name.as_deref(), Some("Example"));
933 assert_eq!(named.r#type, None);
934
935 let plain = Link::new("https://example.test");
936 assert_eq!(plain.r#type, None);
937 }
938
939 #[test]
940 fn test_test_result_set_status_and_finish() {
941 let mut result = TestResult::new("u3".to_string(), "Name3".to_string());
942 result.set_status(Status::Skipped);
943 result.finish();
944 assert_eq!(result.status, Status::Skipped);
945 assert_eq!(result.stage, Stage::Finished);
946 assert!(result.stop >= result.start);
947 }
948
949 #[test]
950 fn test_label_constructors_cover_all_variants() {
951 let labels = vec![
952 Label::story("story"),
953 Label::suite("suite"),
954 Label::parent_suite("parent"),
955 Label::sub_suite("sub"),
956 Label::owner("owner"),
957 Label::tag("tag"),
958 Label::allure_id("123"),
959 Label::host("localhost"),
960 Label::thread("thread-1"),
961 Label::framework("framework"),
962 Label::language("rust"),
963 Label::package("pkg"),
964 Label::test_class("cls"),
965 Label::test_method("meth"),
966 ];
967 assert_eq!(labels.len(), 14);
968 assert!(labels.iter().any(|l| l.name == "testMethod"));
969 assert!(labels.iter().any(|l| l.name == "package"));
970 }
971
972 #[test]
973 fn test_parameter_new_and_link_tms() {
974 let param = Parameter::new("key", "val");
975 assert_eq!(param.name, "key");
976 assert!(param.mode.is_none());
977 assert!(param.excluded.is_none());
978
979 let tms = Link::tms("https://tms", Some("TMS-1".into()));
980 assert_eq!(tms.r#type, Some(LinkType::Tms));
981 assert_eq!(tms.name.as_deref(), Some("TMS-1"));
982 }
983}