@@ -26,6 +26,8 @@ fn test_cli_help() {
2626 . stdout ( predicate:: str:: contains ( "graph" ) )
2727 . stdout ( predicate:: str:: contains ( "replace" ) )
2828 . stdout ( predicate:: str:: contains ( "find" ) )
29+ . stdout ( predicate:: str:: contains ( "extract" ) )
30+ . stdout ( predicate:: str:: contains ( "coverage" ) )
2931 . stdout ( predicate:: str:: contains ( "thesaurus" ) )
3032 . stdout ( predicate:: str:: contains ( "completions" ) ) ;
3133}
@@ -568,4 +570,218 @@ mod integration {
568570 // This test mainly verifies the flag is accepted
569571 assert ! ( output. status. success( ) || stderr. len( ) < 1000 ) ;
570572 }
573+
574+ #[ test]
575+ fn test_extract_help ( ) {
576+ cli_command ( )
577+ . args ( [ "extract" , "--help" ] )
578+ . assert ( )
579+ . success ( )
580+ . stdout ( predicate:: str:: contains ( "text" ) )
581+ . stdout ( predicate:: str:: contains ( "--role" ) )
582+ . stdout ( predicate:: str:: contains ( "--json" ) )
583+ . stdout ( predicate:: str:: contains ( "--schema" ) ) ;
584+ }
585+
586+ #[ test]
587+ fn test_coverage_help ( ) {
588+ cli_command ( )
589+ . args ( [ "coverage" , "--help" ] )
590+ . assert ( )
591+ . success ( )
592+ . stdout ( predicate:: str:: contains ( "text" ) )
593+ . stdout ( predicate:: str:: contains ( "--schema" ) )
594+ . stdout ( predicate:: str:: contains ( "--threshold" ) )
595+ . stdout ( predicate:: str:: contains ( "--json" ) ) ;
596+ }
597+
598+ #[ test]
599+ #[ serial]
600+ fn test_extract_with_schema ( ) {
601+ let manifest_dir = std:: env:: var ( "CARGO_MANIFEST_DIR" ) . unwrap_or_else ( |_| "." . to_string ( ) ) ;
602+ let schema_path = std:: path:: PathBuf :: from ( & manifest_dir)
603+ . parent ( )
604+ . and_then ( |p| p. parent ( ) )
605+ . unwrap ( )
606+ . join ( "crates/terraphim_types/test-fixtures/sample_ontology_schema.json" ) ;
607+
608+ let output = cli_command ( )
609+ . args ( [
610+ "extract" ,
611+ "This chapter covers the concept of knowledge graphs" ,
612+ "--schema" ,
613+ schema_path. to_str ( ) . unwrap ( ) ,
614+ ] )
615+ . output ( )
616+ . expect ( "Failed to execute command" ) ;
617+
618+ if output. status . success ( ) {
619+ let stdout = String :: from_utf8_lossy ( & output. stdout ) ;
620+ let parsed: Result < serde_json:: Value , _ > = serde_json:: from_str ( & stdout) ;
621+ assert ! (
622+ parsed. is_ok( ) ,
623+ "Extract --schema output should be valid JSON: {}" ,
624+ stdout
625+ ) ;
626+
627+ if let Ok ( json) = parsed {
628+ // SchemaSignal has entities, relationships, confidence
629+ assert ! ( json. get( "entities" ) . is_some( ) , "Should have entities field" ) ;
630+ assert ! (
631+ json. get( "confidence" ) . is_some( ) ,
632+ "Should have confidence field"
633+ ) ;
634+ }
635+ }
636+ }
637+
638+ #[ test]
639+ #[ serial]
640+ fn test_coverage_with_full_coverage ( ) {
641+ let manifest_dir = std:: env:: var ( "CARGO_MANIFEST_DIR" ) . unwrap_or_else ( |_| "." . to_string ( ) ) ;
642+ let schema_path = std:: path:: PathBuf :: from ( & manifest_dir)
643+ . parent ( )
644+ . and_then ( |p| p. parent ( ) )
645+ . unwrap ( )
646+ . join ( "crates/terraphim_types/test-fixtures/sample_ontology_schema.json" ) ;
647+
648+ // Text that contains all 3 entity types: chapter, concept, knowledge graph
649+ let output = cli_command ( )
650+ . args ( [
651+ "coverage" ,
652+ "This chapter covers the concept and the knowledge graph" ,
653+ "--schema" ,
654+ schema_path. to_str ( ) . unwrap ( ) ,
655+ "--threshold" ,
656+ "0.7" ,
657+ ] )
658+ . output ( )
659+ . expect ( "Failed to execute command" ) ;
660+
661+ // Full coverage should exit 0
662+ let stdout = String :: from_utf8_lossy ( & output. stdout ) ;
663+ if !stdout. is_empty ( ) {
664+ let parsed: Result < serde_json:: Value , _ > = serde_json:: from_str ( & stdout) ;
665+ assert ! (
666+ parsed. is_ok( ) ,
667+ "Coverage output should be valid JSON: {}" ,
668+ stdout
669+ ) ;
670+
671+ if let Ok ( json) = parsed {
672+ assert ! ( json. get( "signal" ) . is_some( ) , "Should have signal field" ) ;
673+ assert ! (
674+ json. get( "matched_categories" ) . is_some( ) ,
675+ "Should have matched_categories field"
676+ ) ;
677+ assert ! (
678+ json. get( "missing_categories" ) . is_some( ) ,
679+ "Should have missing_categories field"
680+ ) ;
681+ assert ! (
682+ json. get( "schema_name" ) . is_some( ) ,
683+ "Should have schema_name field"
684+ ) ;
685+ }
686+ }
687+ }
688+
689+ #[ test]
690+ #[ serial]
691+ fn test_coverage_below_threshold_exits_1 ( ) {
692+ let manifest_dir = std:: env:: var ( "CARGO_MANIFEST_DIR" ) . unwrap_or_else ( |_| "." . to_string ( ) ) ;
693+ let schema_path = std:: path:: PathBuf :: from ( & manifest_dir)
694+ . parent ( )
695+ . and_then ( |p| p. parent ( ) )
696+ . unwrap ( )
697+ . join ( "crates/terraphim_types/test-fixtures/sample_ontology_schema.json" ) ;
698+
699+ // Text that matches NONE of the entity types
700+ let output = cli_command ( )
701+ . args ( [
702+ "coverage" ,
703+ "completely unrelated text about cooking recipes" ,
704+ "--schema" ,
705+ schema_path. to_str ( ) . unwrap ( ) ,
706+ "--threshold" ,
707+ "0.7" ,
708+ ] )
709+ . output ( )
710+ . expect ( "Failed to execute command" ) ;
711+
712+ // Zero coverage should exit with code 1
713+ assert ! (
714+ !output. status. success( ) ,
715+ "Coverage below threshold should exit with non-zero code"
716+ ) ;
717+
718+ // But output should still be valid JSON
719+ let stdout = String :: from_utf8_lossy ( & output. stdout ) ;
720+ if !stdout. is_empty ( ) {
721+ let parsed: Result < serde_json:: Value , _ > = serde_json:: from_str ( & stdout) ;
722+ assert ! (
723+ parsed. is_ok( ) ,
724+ "Coverage output should be valid JSON even on exit 1: {}" ,
725+ stdout
726+ ) ;
727+
728+ if let Ok ( json) = parsed {
729+ let needs_review = json
730+ . get ( "signal" )
731+ . and_then ( |s| s. get ( "needs_review" ) )
732+ . and_then ( |v| v. as_bool ( ) ) ;
733+ assert_eq ! (
734+ needs_review,
735+ Some ( true ) ,
736+ "needs_review should be true when below threshold"
737+ ) ;
738+ }
739+ }
740+ }
741+
742+ #[ test]
743+ #[ serial]
744+ fn test_extract_command_json_mode ( ) {
745+ let output = cli_command ( )
746+ . args ( [ "extract" , "rust async tokio" , "--json" ] )
747+ . output ( )
748+ . expect ( "Failed to execute command" ) ;
749+
750+ if output. status . success ( ) {
751+ let stdout = String :: from_utf8_lossy ( & output. stdout ) ;
752+ let parsed: Result < serde_json:: Value , _ > = serde_json:: from_str ( & stdout) ;
753+ assert ! (
754+ parsed. is_ok( ) ,
755+ "Extract --json output should be valid JSON: {}" ,
756+ stdout
757+ ) ;
758+
759+ // Should be an array of ExtractedEntity
760+ if let Ok ( json) = parsed {
761+ assert ! ( json. is_array( ) , "Extract --json should return an array" ) ;
762+ }
763+ }
764+ }
765+
766+ #[ test]
767+ fn test_coverage_missing_schema_arg ( ) {
768+ // coverage requires --schema
769+ cli_command ( )
770+ . args ( [ "coverage" , "some text" ] )
771+ . assert ( )
772+ . failure ( ) ;
773+ }
774+
775+ #[ test]
776+ fn test_extract_with_nonexistent_schema ( ) {
777+ cli_command ( )
778+ . args ( [
779+ "extract" ,
780+ "some text" ,
781+ "--schema" ,
782+ "/nonexistent/schema.json" ,
783+ ] )
784+ . assert ( )
785+ . failure ( ) ;
786+ }
571787}
0 commit comments