Files
storkit/server/src/http/context.rs
Dave 1433115f9b fix: add missing closing brace and #[test] attribute in context.rs
The permission_decision_equality test was missing its closing brace,
causing it to swallow the not_found_returns_404_status test function.
This was likely caused by a bad merge conflict resolution.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 11:22:42 +00:00

117 lines
4.3 KiB
Rust

use crate::agents::{AgentPool, ReconciliationEvent};
use crate::io::watcher::WatcherEvent;
use crate::state::SessionState;
use crate::store::JsonFileStore;
use crate::workflow::WorkflowState;
use poem::http::StatusCode;
use std::sync::Arc;
use tokio::sync::{broadcast, mpsc, oneshot};
/// The user's decision when responding to a permission dialog.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PermissionDecision {
/// One-time denial.
Deny,
/// One-time approval.
Approve,
/// Approve and persist the rule to `.claude/settings.json` so Claude Code's
/// built-in permission system handles future checks without prompting.
AlwaysAllow,
}
/// A permission request forwarded from the MCP `prompt_permission` tool to the
/// active WebSocket session. The MCP handler blocks on `response_tx` until the
/// user approves or denies via the frontend dialog.
pub struct PermissionForward {
pub request_id: String,
pub tool_name: String,
pub tool_input: serde_json::Value,
pub response_tx: oneshot::Sender<PermissionDecision>,
}
#[derive(Clone)]
pub struct AppContext {
pub state: Arc<SessionState>,
pub store: Arc<JsonFileStore>,
pub workflow: Arc<std::sync::Mutex<WorkflowState>>,
pub agents: Arc<AgentPool>,
/// Broadcast channel for filesystem watcher events. WebSocket handlers
/// subscribe to this to push lifecycle notifications to connected clients.
pub watcher_tx: broadcast::Sender<WatcherEvent>,
/// Broadcast channel for startup reconciliation progress events.
/// WebSocket handlers subscribe to this to push real-time reconciliation
/// updates to connected clients.
pub reconciliation_tx: broadcast::Sender<ReconciliationEvent>,
/// Sender for permission requests originating from the MCP
/// `prompt_permission` tool. The MCP handler sends a [`PermissionForward`]
/// and awaits the oneshot response.
pub perm_tx: mpsc::UnboundedSender<PermissionForward>,
/// Receiver for permission requests. The active WebSocket handler locks
/// this and polls for incoming permission forwards.
pub perm_rx: Arc<tokio::sync::Mutex<mpsc::UnboundedReceiver<PermissionForward>>>,
}
#[cfg(test)]
impl AppContext {
pub fn new_test(project_root: std::path::PathBuf) -> Self {
let state = SessionState::default();
*state.project_root.lock().unwrap() = Some(project_root.clone());
let store_path = project_root.join(".story_kit_store.json");
let (watcher_tx, _) = broadcast::channel(64);
let (reconciliation_tx, _) = broadcast::channel(64);
let (perm_tx, perm_rx) = mpsc::unbounded_channel();
Self {
state: Arc::new(state),
store: Arc::new(JsonFileStore::new(store_path).unwrap()),
workflow: Arc::new(std::sync::Mutex::new(WorkflowState::default())),
agents: Arc::new(AgentPool::new(3001, watcher_tx.clone())),
watcher_tx,
reconciliation_tx,
perm_tx,
perm_rx: Arc::new(tokio::sync::Mutex::new(perm_rx)),
}
}
}
pub type OpenApiResult<T> = poem::Result<T>;
pub fn bad_request(message: String) -> poem::Error {
poem::Error::from_string(message, StatusCode::BAD_REQUEST)
}
pub fn not_found(message: String) -> poem::Error {
poem::Error::from_string(message, StatusCode::NOT_FOUND)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn bad_request_returns_400_status() {
let err = bad_request("something went wrong".to_string());
assert_eq!(err.status(), StatusCode::BAD_REQUEST);
}
#[test]
fn bad_request_accepts_empty_message() {
let err = bad_request(String::new());
assert_eq!(err.status(), StatusCode::BAD_REQUEST);
}
#[test]
fn permission_decision_equality() {
assert_eq!(PermissionDecision::Deny, PermissionDecision::Deny);
assert_eq!(PermissionDecision::Approve, PermissionDecision::Approve);
assert_eq!(PermissionDecision::AlwaysAllow, PermissionDecision::AlwaysAllow);
assert_ne!(PermissionDecision::Deny, PermissionDecision::Approve);
assert_ne!(PermissionDecision::Approve, PermissionDecision::AlwaysAllow);
}
#[test]
fn not_found_returns_404_status() {
let err = not_found("item not found".to_string());
assert_eq!(err.status(), StatusCode::NOT_FOUND);
}
}