//! Deterministic seed data for end-to-end tests. //! //! Active only when `CLAWMATES_MODE=e2e`. Idempotent: a fixed workspace, //! owner, agent, and credit balance that Playwright journeys assert against. //! This is real production code driving the real registration paths — the //! only test-specific part is the fixture data itself. use cm_auth::AuthService; use cm_domain::{ AccessPolicy, Agent, AgentId, AgentStatus, Role, User, UserId, Workspace, WorkspaceId, }; use sqlx::PgPool; /// In e2e mode the server hosts a Slack-shaped sink so journeys can assert /// exactly what the broker posted, without external infrastructure. pub fn slack_sink_router() -> axum::Router { use std::sync::Arc; use tokio::sync::Mutex; type Posts = Arc>>; let posts: Posts = Arc::new(Mutex::new(Vec::new())); axum::Router::new() .route( "/__slack/chat.postMessage", axum::routing::post( |axum::extract::State(posts): axum::extract::State, axum::Json(body): axum::Json| async move { posts.lock().await.push(body); axum::Json(serde_json::json!({"ok": true})) }, ), ) .route( "/__slack/posts", axum::routing::get( |axum::extract::State(posts): axum::extract::State| async move { axum::Json(posts.lock().await.clone()) }, ), ) .with_state(posts) } pub const E2E_OWNER_EMAIL: &str = "owner@acme.test"; pub const E2E_OWNER_PASSWORD: &str = "e2e-password"; pub fn enabled() -> bool { std::env::var("CLAWMATES_MODE").as_deref() == Ok("e2e") } pub async fn seed(pool: &PgPool) -> Result<(), String> { if cm_db::repo::users::find_by_email(pool, E2E_OWNER_EMAIL) .await .is_ok() { return Ok(()); } let workspace = Workspace { id: WorkspaceId::new(), name: "Acme".into(), plan: "team".into(), }; cm_db::repo::workspaces::insert(pool, &workspace) .await .map_err(|e| format!("seed workspace: {e}"))?; let owner = User { id: UserId::new(), workspace_id: workspace.id, email: E2E_OWNER_EMAIL.into(), role: Role::Owner, display_name: "Avery Owner".into(), created_at: time::OffsetDateTime::UNIX_EPOCH, }; cm_db::repo::users::insert(pool, &owner) .await .map_err(|e| format!("seed owner: {e}"))?; AuthService::new(pool.clone()) .set_password(owner.id, E2E_OWNER_PASSWORD) .await .map_err(|e| format!("seed password: {e}"))?; let agent = Agent { id: AgentId::new(), workspace_id: workspace.id, name: "Scout".into(), job_title: "Research Analyst".into(), system_prompt: "You research things carefully.".into(), avatar: "scout-1".into(), accent: "#f96565".into(), wallpaper: "dunes".into(), managed_by: owner.id, status: AgentStatus::Online, }; cm_db::repo::agents::insert(pool, &agent, &AccessPolicy::default()) .await .map_err(|e| format!("seed agent: {e}"))?; cm_db::repo::credits::add_lot(pool, workspace.id, 1250, "e2e-seed") .await .map_err(|e| format!("seed credits: {e}"))?; cm_db::repo::skills::create( pool, None, "Daily briefing", "Clawmates", "Summarize the day's priorities each morning.", "Each morning, compile a short briefing of priorities and blockers.", ) .await .map_err(|e| format!("seed skill: {e}"))?; sqlx::query("INSERT INTO promo_codes (code, credits) VALUES ('WELCOME500', 500)") .execute(pool) .await .map_err(|e| format!("seed promo: {e}"))?; println!("clawmates-server: e2e seed applied ({E2E_OWNER_EMAIL})"); Ok(()) }