| 1 | //! Actual CLI consumers share the pinned speech adapter with ToolSpecs. |
| 2 | use crate::extension_host::TestManagerGuard; |
| 3 | use crate::extension_host::tests::node_for_tests; |
| 4 | use crate::plugins::activation::TestPolicyGuard; |
| 5 | use crate::tools::speech::speech_host_tests::{config, manager, provider}; |
| 6 | use std::sync::Arc; |
| 7 | use wiremock::MockServer; |
| 8 | |
| 9 | #[tokio::test(flavor = "current_thread")] |
| 10 | async fn real_cli_speech_and_tts_alias_share_host_plan_and_write_same_audio() { |
| 11 | use clap::Parser; |
| 12 | let _home = crate::test_support::SealedHome::new(); |
| 13 | let _policy = TestPolicyGuard::extension_host(false); |
| 14 | let Some(node) = node_for_tests("real_cli_speech_host") else { |
| 15 | return; |
| 16 | }; |
| 17 | let home = tempfile::tempdir().unwrap(); |
| 18 | let manager = manager(node, home.path()); |
| 19 | let _manager = TestManagerGuard::install(Arc::clone(&manager)); |
| 20 | let server = MockServer::start().await; |
| 21 | provider(&server, 2).await; |
| 22 | let parsed = crate::Cli::try_parse_from(["codewhale", "tts", "hello"]).unwrap(); |
| 23 | assert!(matches!(parsed.command, Some(crate::Commands::Speech(_)))); |
| 24 | let args = || crate::SpeechArgs { |
| 25 | text: "hello".into(), |
| 26 | output: None, |
| 27 | output_dir: Some(home.path().join("audio")), |
| 28 | model: Some("mimo-tts".into()), |
| 29 | voice: Some("Mia".into()), |
| 30 | instruction: Some(" warm ".into()), |
| 31 | voice_prompt: None, |
| 32 | clone_voice: None, |
| 33 | format: "pcm".into(), |
| 34 | json: true, |
| 35 | }; |
| 36 | crate::run_speech(&config(&server.uri(), false), args()) |
| 37 | .await |
| 38 | .unwrap(); |
| 39 | crate::run_speech(&config(&server.uri(), true), args()) |
| 40 | .await |
| 41 | .unwrap(); |
| 42 | let requests = server.received_requests().await.unwrap(); |
| 43 | assert_eq!(requests.len(), 2); |
| 44 | assert_eq!(requests[0].body, requests[1].body); |
| 45 | assert_eq!( |
| 46 | std::fs::read(home.path().join("audio/speech.pcm16")).unwrap(), |
| 47 | b"hi" |
| 48 | ); |
| 49 | manager.shutdown().await; |
| 50 | } |
| 51 | |
| 52 | #[tokio::test(flavor = "current_thread")] |
| 53 | async fn selected_speech_host_refusal_never_falls_back_or_writes_cli_audio() { |
| 54 | let _home = crate::test_support::SealedHome::new(); |
| 55 | let _policy = TestPolicyGuard::extension_host(false); |
| 56 | let home = tempfile::tempdir().unwrap(); |
| 57 | let server = MockServer::start().await; |
| 58 | let manager = manager(home.path().join("missing-node"), home.path()); |
| 59 | let _manager = TestManagerGuard::install(Arc::clone(&manager)); |
| 60 | let args = crate::SpeechArgs { |
| 61 | text: "hello".into(), |
| 62 | output: Some(home.path().join("cli.wav")), |
| 63 | output_dir: None, |
| 64 | model: None, |
| 65 | voice: None, |
| 66 | instruction: None, |
| 67 | voice_prompt: None, |
| 68 | clone_voice: Some(home.path().join("missing.wav")), |
| 69 | format: "wav".into(), |
| 70 | json: true, |
| 71 | }; |
| 72 | assert!( |
| 73 | crate::run_speech(&config(&server.uri(), true), args) |
| 74 | .await |
| 75 | .is_err() |
| 76 | ); |
| 77 | assert!(server.received_requests().await.unwrap().is_empty()); |
| 78 | assert!(!home.path().join("cli.wav").exists()); |
| 79 | manager.shutdown().await; |
| 80 | } |
| 81 | |
| 82 | #[tokio::test(flavor = "current_thread")] |
| 83 | async fn cli_speech_preserves_sample_file_error_before_format_validation() { |
| 84 | let _home = crate::test_support::SealedHome::new(); |
| 85 | let _policy = TestPolicyGuard::extension_host(false); |
| 86 | let home = tempfile::tempdir().unwrap(); |
| 87 | let server = MockServer::start().await; |
| 88 | let Some(node) = node_for_tests("cli_speech_error_order") else { |
| 89 | return; |
| 90 | }; |
| 91 | let manager = manager(node, home.path()); |
| 92 | let _manager = TestManagerGuard::install(Arc::clone(&manager)); |
| 93 | // CLI's sample-read error still takes precedence over unsupported format. |
| 94 | let args = || crate::SpeechArgs { |
| 95 | text: "hello".into(), |
| 96 | output: None, |
| 97 | output_dir: Some(home.path().into()), |
| 98 | model: None, |
| 99 | voice: None, |
| 100 | instruction: None, |
| 101 | voice_prompt: None, |
| 102 | clone_voice: Some(home.path().join("missing.wav")), |
| 103 | format: "flac".into(), |
| 104 | json: true, |
| 105 | }; |
| 106 | let rust = crate::run_speech(&config(&server.uri(), false), args()) |
| 107 | .await |
| 108 | .unwrap_err(); |
| 109 | let host = crate::run_speech(&config(&server.uri(), true), args()) |
| 110 | .await |
| 111 | .unwrap_err(); |
| 112 | assert_eq!(host.to_string(), rust.to_string()); |
| 113 | assert!(server.received_requests().await.unwrap().is_empty()); |
| 114 | manager.shutdown().await; |
| 115 | } |
| 116 |