mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-09-16 14:51:37 +02:00
Fix MCP send_http_request ignoring environmentId (#654)
This commit is contained in:
@@ -33,6 +33,9 @@ pub enum Error {
|
||||
#[error("No base environment for {0}")]
|
||||
MissingBaseEnvironment(String),
|
||||
|
||||
#[error("Invalid environment selection: {0}")]
|
||||
InvalidEnvironment(String),
|
||||
|
||||
#[error("Multiple base environments for {0}. Delete duplicates before continuing.")]
|
||||
MultipleBaseEnvironments(String),
|
||||
|
||||
|
||||
@@ -11,6 +11,27 @@ impl<'a> ClientDb<'a> {
|
||||
self.find_one(EnvironmentIden::Id, id)
|
||||
}
|
||||
|
||||
/// Resolve an explicit execution environment, rejecting foreign and folder environments.
|
||||
/// Folder variables are inherited from the request's folder, never selected globally.
|
||||
pub fn get_environment_for_workspace(
|
||||
&self,
|
||||
workspace_id: &str,
|
||||
environment_id: &str,
|
||||
) -> Result<Environment> {
|
||||
let environment = self.get_environment(environment_id)?;
|
||||
if environment.workspace_id != workspace_id {
|
||||
return Err(crate::error::Error::InvalidEnvironment(format!(
|
||||
"Environment {environment_id} does not belong to workspace {workspace_id}"
|
||||
)));
|
||||
}
|
||||
if !matches!(environment.parent_model.as_str(), "workspace" | "environment") {
|
||||
return Err(crate::error::Error::InvalidEnvironment(format!(
|
||||
"Environment {environment_id} is not a workspace environment"
|
||||
)));
|
||||
}
|
||||
Ok(environment)
|
||||
}
|
||||
|
||||
pub fn get_environment_by_folder_id(&self, folder_id: &str) -> Result<Option<Environment>> {
|
||||
let mut environments: Vec<Environment> =
|
||||
self.find_many(EnvironmentIden::ParentId, folder_id, None)?;
|
||||
@@ -196,3 +217,142 @@ impl<'a> WriteDb<'a> {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::error::Error;
|
||||
use crate::init_in_memory;
|
||||
use crate::models::{Environment, EnvironmentVariable, Folder, Workspace};
|
||||
use crate::query_manager::QueryManager;
|
||||
use crate::render::make_vars_hashmap;
|
||||
use crate::util::UpdateSource;
|
||||
|
||||
fn fixture() -> (QueryManager, Environment, Environment, Environment) {
|
||||
let (manager, _blobs, _rx) = init_in_memory().unwrap();
|
||||
let source = &UpdateSource::Background;
|
||||
let (base, staging, folder) = manager
|
||||
.with_tx(|db| {
|
||||
let workspace = db.list_workspaces()?.remove(0);
|
||||
let variable = |name: &str, value: &str| EnvironmentVariable {
|
||||
enabled: true,
|
||||
name: name.into(),
|
||||
value: value.into(),
|
||||
..Default::default()
|
||||
};
|
||||
let base = db.ensure_base_environment(&workspace.id)?;
|
||||
let base = db.upsert_environment(
|
||||
&Environment {
|
||||
variables: vec![
|
||||
variable("marker", "global"),
|
||||
variable("global_only", "inherited"),
|
||||
],
|
||||
..base
|
||||
},
|
||||
source,
|
||||
)?;
|
||||
let staging = db.upsert_environment(
|
||||
&Environment {
|
||||
workspace_id: workspace.id.clone(),
|
||||
parent_model: "environment".into(),
|
||||
parent_id: Some(base.id.clone()),
|
||||
name: "Staging".into(),
|
||||
variables: vec![variable("marker", "staging")],
|
||||
..Default::default()
|
||||
},
|
||||
source,
|
||||
)?;
|
||||
let folder = db.upsert_folder(
|
||||
&Folder {
|
||||
workspace_id: workspace.id.clone(),
|
||||
name: "Folder".into(),
|
||||
..Default::default()
|
||||
},
|
||||
source,
|
||||
)?;
|
||||
let folder = db.upsert_environment(
|
||||
&Environment {
|
||||
workspace_id: workspace.id,
|
||||
parent_model: "folder".into(),
|
||||
parent_id: Some(folder.id),
|
||||
variables: vec![variable("marker", "folder")],
|
||||
..Default::default()
|
||||
},
|
||||
source,
|
||||
)?;
|
||||
Ok::<_, Error>((base, staging, folder))
|
||||
})
|
||||
.unwrap();
|
||||
(manager, base, staging, folder)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn explicit_environment_preserves_global_and_folder_inheritance() {
|
||||
let (manager, base, staging, folder) = fixture();
|
||||
let db = manager.connect();
|
||||
let selected = db.get_environment_for_workspace(&base.workspace_id, &staging.id).unwrap();
|
||||
let vars = make_vars_hashmap(
|
||||
db.resolve_environments(&base.workspace_id, None, Some(&selected.id)).unwrap(),
|
||||
);
|
||||
assert_eq!(vars["marker"], "staging");
|
||||
assert_eq!(vars["global_only"], "inherited");
|
||||
|
||||
let vars = make_vars_hashmap(
|
||||
db.resolve_environments(
|
||||
&base.workspace_id,
|
||||
folder.parent_id.as_deref(),
|
||||
Some(&selected.id),
|
||||
)
|
||||
.unwrap(),
|
||||
);
|
||||
assert_eq!(vars["marker"], "folder");
|
||||
assert_eq!(vars["global_only"], "inherited");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn base_environment_can_be_selected_explicitly() {
|
||||
let (manager, base, _staging, _folder) = fixture();
|
||||
let db = manager.connect();
|
||||
let selected = db.get_environment_for_workspace(&base.workspace_id, &base.id).unwrap();
|
||||
let vars = make_vars_hashmap(
|
||||
db.resolve_environments(&base.workspace_id, None, Some(&selected.id)).unwrap(),
|
||||
);
|
||||
assert_eq!(vars["marker"], "global");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn explicit_environment_rejects_empty_and_missing_ids() {
|
||||
let (manager, base, _staging, _folder) = fixture();
|
||||
for id in ["", "ev_missing"] {
|
||||
assert!(matches!(
|
||||
manager.connect().get_environment_for_workspace(&base.workspace_id, id),
|
||||
Err(Error::ModelNotFound(_))
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn explicit_environment_rejects_another_workspace() {
|
||||
let (manager, _base, staging, _folder) = fixture();
|
||||
let other = manager
|
||||
.with_tx(|db| {
|
||||
db.upsert_workspace(
|
||||
&Workspace { name: "Other".into(), ..Default::default() },
|
||||
&UpdateSource::Background,
|
||||
)
|
||||
})
|
||||
.unwrap();
|
||||
assert!(matches!(
|
||||
manager.connect().get_environment_for_workspace(&other.id, &staging.id),
|
||||
Err(Error::InvalidEnvironment(_))
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn explicit_environment_rejects_folder_variables() {
|
||||
let (manager, base, _staging, folder) = fixture();
|
||||
assert!(matches!(
|
||||
manager.connect().get_environment_for_workspace(&base.workspace_id, &folder.id),
|
||||
Err(Error::InvalidEnvironment(_))
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
+6
-1
@@ -572,7 +572,12 @@ export type RenderHttpRequestResponse = { httpRequest: HttpRequest, };
|
||||
|
||||
export type RenderPurpose = "send" | "preview";
|
||||
|
||||
export type SendHttpRequestRequest = { httpRequest: Partial<HttpRequest>, };
|
||||
export type SendHttpRequestRequest = { httpRequest: Partial<HttpRequest>,
|
||||
/**
|
||||
* Override the environment for this send without changing the active selection.
|
||||
* When omitted, use the host's active environment.
|
||||
*/
|
||||
environmentId?: string, };
|
||||
|
||||
export type SendHttpRequestResponse = { httpResponse: HttpResponse,
|
||||
/**
|
||||
|
||||
@@ -296,6 +296,36 @@ pub struct ExportHttpRequestResponse {
|
||||
pub struct SendHttpRequestRequest {
|
||||
#[ts(type = "Partial<HttpRequest>")]
|
||||
pub http_request: HttpRequest,
|
||||
/// Override the environment for this send without changing the active selection.
|
||||
/// When omitted, use the host's active environment.
|
||||
#[ts(optional)]
|
||||
pub environment_id: Option<String>,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod send_http_request_tests {
|
||||
use super::SendHttpRequestRequest;
|
||||
use serde_json::json;
|
||||
|
||||
#[test]
|
||||
fn environment_override_survives_the_plugin_wire_format() {
|
||||
let request: SendHttpRequestRequest = serde_json::from_value(json!({
|
||||
"httpRequest": { "id": "rq_test" }, "environmentId": "ev_staging"
|
||||
}))
|
||||
.unwrap();
|
||||
assert_eq!(request.environment_id.as_deref(), Some("ev_staging"));
|
||||
assert_eq!(serde_json::to_value(request).unwrap()["environmentId"], "ev_staging");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn older_plugins_can_omit_the_environment_override() {
|
||||
let request: SendHttpRequestRequest = serde_json::from_value(json!({
|
||||
"httpRequest": { "id": "rq_test" }
|
||||
}))
|
||||
.unwrap();
|
||||
assert_eq!(request.environment_id, None);
|
||||
assert_eq!(request.http_request.id, "rq_test");
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
||||
|
||||
Reference in New Issue
Block a user