diff --git a/Cargo.lock b/Cargo.lock index 5f17717..9a40033 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3778,7 +3778,7 @@ dependencies = [ [[package]] name = "schematic" version = "0.18.12" -source = "git+https://github.com/JeanMertz/schematic?branch=merged#f072ccf7402d00c853b23712910f22a32cc6bfe3" +source = "git+https://github.com/JeanMertz/schematic?branch=merged#533dc9ada5af11a74f44adfe19d7c056a174db32" dependencies = [ "indexmap", "miette", @@ -3797,7 +3797,7 @@ dependencies = [ [[package]] name = "schematic_macros" version = "0.18.11" -source = "git+https://github.com/JeanMertz/schematic?branch=merged#f072ccf7402d00c853b23712910f22a32cc6bfe3" +source = "git+https://github.com/JeanMertz/schematic?branch=merged#533dc9ada5af11a74f44adfe19d7c056a174db32" dependencies = [ "convert_case 0.8.0", "darling 0.21.3", @@ -3809,7 +3809,7 @@ dependencies = [ [[package]] name = "schematic_types" version = "0.10.6" -source = "git+https://github.com/JeanMertz/schematic?branch=merged#f072ccf7402d00c853b23712910f22a32cc6bfe3" +source = "git+https://github.com/JeanMertz/schematic?branch=merged#533dc9ada5af11a74f44adfe19d7c056a174db32" dependencies = [ "indexmap", "relative-path", diff --git a/crates/jp_cli/src/cmd/attachment/rm.rs b/crates/jp_cli/src/cmd/attachment/rm.rs index 3b95488..d6381ed 100644 --- a/crates/jp_cli/src/cmd/attachment/rm.rs +++ b/crates/jp_cli/src/cmd/attachment/rm.rs @@ -33,7 +33,7 @@ impl IntoPartialAppConfig for Rm { .collect::, _>>()?; for attachment in partial.conversation.attachments { - let url = AttachmentConfig::from_partial(attachment.clone())?.to_url()?; + let url = AttachmentConfig::from_partial(attachment.clone(), vec![])?.to_url()?; if !to_remove_attachments.contains(&url) { attachments.push(attachment); } diff --git a/crates/jp_cli/src/cmd/conversation/fork.rs b/crates/jp_cli/src/cmd/conversation/fork.rs index d6a4762..24abd0c 100644 --- a/crates/jp_cli/src/cmd/conversation/fork.rs +++ b/crates/jp_cli/src/cmd/conversation/fork.rs @@ -88,11 +88,7 @@ mod tests { use std::panic::{AssertUnwindSafe, catch_unwind, resume_unwind}; use assert_matches::assert_matches; - use jp_config::{ - AppConfig, Config as _, PartialAppConfig, - conversation::tool::RunMode, - model::id::{PartialModelIdConfig, ProviderId}, - }; + use jp_config::AppConfig; use jp_conversation::{ Conversation, ConversationEvent, ConversationStream, event::{ChatRequest, ChatResponse}, @@ -406,15 +402,7 @@ mod tests { for (name, case) in cases { let tmp = tempdir().unwrap(); - let mut partial = PartialAppConfig::empty(); - partial.conversation.tools.defaults.run = Some(RunMode::Ask); - partial.assistant.model.id = PartialModelIdConfig { - provider: Some(ProviderId::Anthropic), - name: Some("test".parse().unwrap()), - } - .into(); - - let config = AppConfig::from_partial(partial).unwrap(); + let config = AppConfig::new_test(); let workspace = Workspace::new(tmp.path()); let mut ctx = Ctx::new( workspace, diff --git a/crates/jp_config/src/conversation/attachment.rs b/crates/jp_config/src/conversation/attachment.rs index acf4d34..95bc77d 100644 --- a/crates/jp_config/src/conversation/attachment.rs +++ b/crates/jp_config/src/conversation/attachment.rs @@ -74,7 +74,7 @@ impl FromStr for AttachmentConfig { fn from_str(s: &str) -> Result { let partial = PartialAttachmentConfig::from_str(s)?; - Self::from_partial(partial).map_err(Into::into) + Self::from_partial(partial, vec![]).map_err(Into::into) } } diff --git a/crates/jp_config/src/conversation/tool.rs b/crates/jp_config/src/conversation/tool.rs index 246c053..83b2650 100644 --- a/crates/jp_config/src/conversation/tool.rs +++ b/crates/jp_config/src/conversation/tool.rs @@ -1049,7 +1049,7 @@ mod tests { )) ); - let cfg = CommandConfigOrString::from_partial(p.command.clone().unwrap()).unwrap(); + let cfg = CommandConfigOrString::from_partial(p.command.clone().unwrap(), vec![]).unwrap(); assert_eq!(cfg.command(), ToolCommandConfig { program: "cargo".to_owned(), args: vec!["check".to_owned()], @@ -1073,7 +1073,7 @@ mod tests { )) ); - let cfg = CommandConfigOrString::from_partial(p.command.unwrap()).unwrap(); + let cfg = CommandConfigOrString::from_partial(p.command.unwrap(), vec![]).unwrap(); assert_eq!(cfg.command(), ToolCommandConfig { program: "cargo".to_owned(), args: vec!["check".to_owned(), "--verbose".to_owned()], diff --git a/crates/jp_config/src/lib.rs b/crates/jp_config/src/lib.rs index 4606a97..7ef0dbd 100644 --- a/crates/jp_config/src/lib.rs +++ b/crates/jp_config/src/lib.rs @@ -237,6 +237,31 @@ impl ToPartial for AppConfig { } impl AppConfig { + /// Return a default configuration for testing purposes. + /// + /// This CANNOT be used in release mode. + #[cfg(debug_assertions)] + #[doc(hidden)] + #[must_use] + pub fn new_test() -> Self { + use crate::{ + conversation::tool::RunMode, + model::id::{Name, PartialModelIdConfig, ProviderId}, + }; + + let mut partial = PartialAppConfig::empty(); + + partial.conversation.title.generate.auto = Some(false); + partial.conversation.tools.defaults.run = Some(RunMode::Ask); + partial.assistant.model.id = PartialModelIdConfig { + provider: Some(ProviderId::Anthropic), + name: Some(Name("test".to_owned())), + } + .into(); + + Self::from_partial(partial, vec![]).expect("valid config") + } + /// Return a list of all fields in the configuration. /// /// The fields are returned in alphabetical order, with nested fields diff --git a/crates/jp_config/src/model/parameters.rs b/crates/jp_config/src/model/parameters.rs index ce3dd16..322fc23 100644 --- a/crates/jp_config/src/model/parameters.rs +++ b/crates/jp_config/src/model/parameters.rs @@ -207,7 +207,7 @@ impl FromStr for ReasoningConfig { fn from_str(s: &str) -> Result { let partial = PartialReasoningConfig::from_str(s)?; - Self::from_partial(partial).map_err(Into::into) + Self::from_partial(partial, vec![]).map_err(Into::into) } } diff --git a/crates/jp_config/src/util.rs b/crates/jp_config/src/util.rs index 2e3059b..d4b5478 100644 --- a/crates/jp_config/src/util.rs +++ b/crates/jp_config/src/util.rs @@ -182,7 +182,7 @@ pub fn build(mut partial: PartialAppConfig) -> Result { "Configuration details." ); - let mut config: AppConfig = Config::from_partial(partial)?; + let mut config: AppConfig = Config::from_partial(partial, vec![])?; // Sort instructions by position. config @@ -510,7 +510,7 @@ mod tests { let error = build(PartialAppConfig::default_values(&()).unwrap().unwrap()).unwrap_err(); assert_matches!( error, - Error::Schematic(schematic::ConfigError::MissingRequired(_)) + Error::Schematic(schematic::ConfigError::MissingRequired { .. }) ); let mut partial = PartialAppConfig::default_values(&()).unwrap().unwrap(); @@ -536,7 +536,7 @@ mod tests { let mut partial = PartialAppConfig::default_values(&()).unwrap().unwrap(); let error = build(partial.clone()).unwrap_err(); - assert_matches!(error, Error::Schematic(MissingRequired(v)) if v == "provider"); + assert_matches!(error, Error::Schematic(MissingRequired { fields }) if fields == vec!["assistant", "model", "id", "provider"]); partial.assistant.model.id = PartialModelIdConfig { provider: Some(ProviderId::Openrouter), name: Some("foo".parse().unwrap()), @@ -544,7 +544,7 @@ mod tests { .into(); let error = build(partial.clone()).unwrap_err(); - assert_matches!(error, Error::Schematic(MissingRequired(v)) if v == "run"); + assert_matches!(error, Error::Schematic(MissingRequired{ fields }) if fields == vec!["conversation", "tools", "defaults", "run"]); partial.conversation.tools.defaults.run = Some(RunMode::Unattended); build(partial).unwrap(); diff --git a/crates/jp_conversation/src/stream.rs b/crates/jp_conversation/src/stream.rs index 303b1f0..dd07581 100644 --- a/crates/jp_conversation/src/stream.rs +++ b/crates/jp_conversation/src/stream.rs @@ -209,7 +209,7 @@ impl ConversationStream { partial.merge(&(), delta.into())?; } - AppConfig::from_partial(partial).map_err(Into::into) + AppConfig::from_partial(partial, vec![]).map_err(Into::into) } /// Removes all events from the end of the stream, until a [`ChatRequest`] @@ -470,6 +470,22 @@ impl ConversationStream { front_config: self.base_config.to_partial(), } } + + /// Return a default conversation stream for testing purposes. + /// + /// This CANNOT be used in release mode. + #[cfg(debug_assertions)] + #[doc(hidden)] + #[must_use] + pub fn new_test() -> Self { + use time::macros::utc_datetime; + + Self { + base_config: AppConfig::new_test().into(), + events: vec![], + created_at: utc_datetime!(2020-01-01 0:00), + } + } } impl Extend for ConversationStream { @@ -748,7 +764,7 @@ impl FromIterator for Result Deserialize<'de> for ConversationStream { match events.remove(0) { InternalEvent::ConfigDelta(base_config) => Ok(Self { created_at: base_config.timestamp, - base_config: AppConfig::from_partial(base_config.into()) + base_config: AppConfig::from_partial(base_config.into(), vec![]) .map_err(Error::custom)? .into(), events, @@ -988,30 +1004,13 @@ impl<'de> Deserialize<'de> for InternalEvent { #[cfg(test)] mod tests { - use jp_config::{ - conversation::tool::RunMode, - model::id::{PartialModelIdConfig, ProviderId}, - }; use time::macros::utc_datetime; use super::*; #[test] fn test_conversation_stream_serialization_roundtrip() { - let mut base_config = PartialAppConfig::empty(); - base_config.conversation.title.generate.auto = Some(false); - base_config.conversation.tools.defaults.run = Some(RunMode::Ask); - base_config.assistant.model.id = PartialModelIdConfig { - provider: Some(ProviderId::Anthropic), - name: Some("test".parse().unwrap()), - } - .into(); - - let mut stream = ConversationStream { - base_config: AppConfig::from_partial(base_config).unwrap().into(), - events: vec![], - created_at: utc_datetime!(2020-01-01 0:00), - }; + let mut stream = ConversationStream::new_test(); insta::assert_json_snapshot!(&stream); diff --git a/crates/jp_llm/src/test.rs b/crates/jp_llm/src/test.rs index f0d8f1d..2c60ef5 100644 --- a/crates/jp_llm/src/test.rs +++ b/crates/jp_llm/src/test.rs @@ -97,7 +97,7 @@ impl TestRequest { } .into(); - AppConfig::from_partial(cfg).unwrap().into() + AppConfig::from_partial(cfg, vec![]).unwrap().into() }) .with_created_at(utc_datetime!(2020-01-01 0:00)), ) @@ -128,7 +128,7 @@ impl TestRequest { } .into(); - AppConfig::from_partial(cfg).unwrap().into() + AppConfig::from_partial(cfg, vec![]).unwrap().into() }) .with_created_at(datetime!(2020-01-01 0:00 utc)), ) diff --git a/crates/jp_workspace/src/lib.rs b/crates/jp_workspace/src/lib.rs index ed64a89..d2c117d 100644 --- a/crates/jp_workspace/src/lib.rs +++ b/crates/jp_workspace/src/lib.rs @@ -627,11 +627,6 @@ pub fn user_data_dir() -> Result { mod tests { use std::{collections::HashMap, fs, time::Duration}; - use jp_config::{ - Config as _, PartialAppConfig, - conversation::tool::RunMode, - model::id::{PartialModelIdConfig, ProviderId}, - }; use jp_storage::{CONVERSATIONS_DIR, METADATA_FILE, value::read_json}; use tempfile::tempdir; use test_log::test; @@ -738,19 +733,9 @@ mod tests { let storage = root.join("storage"); let mut workspace = Workspace::new(&root); + let config = AppConfig::new_test(); - let mut partial = PartialAppConfig::empty(); - partial.conversation.tools.defaults.run = Some(RunMode::Ask); - partial.assistant.model.id = PartialModelIdConfig { - provider: Some(ProviderId::Anthropic), - name: Some("test".parse().unwrap()), - } - .into(); - - let id = workspace.create_conversation( - Conversation::default(), - AppConfig::from_partial(partial).unwrap().into(), - ); + let id = workspace.create_conversation(Conversation::default(), config.into()); workspace .set_active_conversation_id(id, UtcDateTime::UNIX_EPOCH) .unwrap(); @@ -818,18 +803,9 @@ mod tests { assert!(workspace.state.local.conversations.is_empty()); let conversation = Conversation::default(); - let mut partial = PartialAppConfig::empty(); - partial.conversation.tools.defaults.run = Some(RunMode::Ask); - partial.assistant.model.id = PartialModelIdConfig { - provider: Some(ProviderId::Anthropic), - name: Some("test".parse().unwrap()), - } - .into(); + let config = AppConfig::new_test(); + let id = workspace.create_conversation(conversation.clone(), config.into()); - let id = workspace.create_conversation( - conversation.clone(), - AppConfig::from_partial(partial).unwrap().into(), - ); assert_eq!( workspace .state