moadim 3.2.5

Loop engine for AI agents — routines over REST, MCP, and a built-in web UI

#[test]
fn svc_update_rejects_renaming_into_existing_slug() {
    let _home = TempHome::set();
    // Covers the slug-conflict branch in `svc_update`: renaming one routine to a
    // title that another routine already owns yields a `Conflict`.
    let title_keep = "Svc Update Keep ZZZ";
    let title_other = "Svc Update Other ZZZ";
    // Build a store directly so both routines coexist before the rename attempt.
    let store = new_store();
    let routine_keep = make_routine("keep-id", title_keep, 1, 1);
    let routine_other = make_routine("other-id", title_other, 2, 2);
    crate::routine_storage::write_routine(&routine_keep).unwrap();
    crate::routine_storage::write_routine(&routine_other).unwrap();
    store.lock().unwrap().insert("keep-id".into(), routine_keep);
    store
        .lock()
        .unwrap()
        .insert("other-id".into(), routine_other);

    // Wrapped defensively: the rename short-circuits on `Conflict` before the
    // sync, but `with_empty_path` guarantees no real crontab write either way (#175).
    with_empty_path(|| {
        let conflict = svc_update(
            &store,
            "other-id",
            UpdateRoutineRequest {
        disabled_reason: None,
                model: None,
                schedule: None,
                schedules: None,
                // Rename "other" into the slug already owned by "keep".
                title: Some(title_keep.into()),
                agent: None,
                prompt: None,
                goal: None,
                repositories: None,
                machines: None,
                enabled: None,
                ttl_secs: None,
                max_runtime_secs: None,
                power_saving_exempt: None,
                tags: None,
                env: None,
                failure_threshold: None,
        notifications: Default::default(),
            },
        );
        assert!(matches!(conflict, Err(AppError::Conflict(_))));
    });
}

#[test]
fn svc_update_title_keeps_existing_workbench_slug() {
    let _home = TempHome::set();
    let old_title = "Svc Update Rename Old ZZZ";
    let new_title = "Svc Update Rename New ZZZ";
    let old_slug = slugify(old_title);
    let new_slug = slugify(new_title);
    let routine = make_routine("rename-id", old_title, 1, 1);
    crate::routine_storage::write_routine(&routine).unwrap();
    let store = store_with(vec![routine]);

    let workbenches = crate::paths::workbenches_dir();
    let old_dir = workbenches.join(format!("{old_slug}-1000"));
    std::fs::create_dir_all(&old_dir).unwrap();
    std::fs::write(old_dir.join("agent.log"), "prior run log").unwrap();

    with_empty_path(|| {
        svc_update(
            &store,
            "rename-id",
            UpdateRoutineRequest {
        disabled_reason: None,
                title: Some(new_title.into()),
                ..empty_update_request()
            },
        )
        .unwrap();
    });

    assert!(old_dir.exists());
    assert!(!workbenches.join(format!("{new_slug}-1000")).exists());
    let logs = svc_logs(&store, "rename-id").unwrap();
    assert_eq!(logs.content, "prior run log");
}