A materialized copy of an M5 group stateful template (cron/queue/email) now reads as `materialized = true` — inherited, read-only — distinct from a hand-authored trigger. Threads a derived `materialized` bool (= `materialized_from IS NOT NULL`) row → domain → API → CLI, mirroring the `sealed`/`shared` columns; `list_for_app` SELECTs `materialized_from`. No migration. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
228 lines
6.1 KiB
Rust
228 lines
6.1 KiB
Rust
//! `pic triggers` smoke tests. Covers the create-cron / create-kv /
|
|
//! create-dead-letter happy paths plus a generic create-from-json
|
|
//! escape hatch.
|
|
|
|
use predicates::prelude::*;
|
|
|
|
use crate::common;
|
|
use crate::common::cleanup::AppGuard;
|
|
|
|
#[ignore = "needs DATABASE_URL pointing at a running Postgres"]
|
|
#[test]
|
|
fn create_kv_and_show_in_ls() {
|
|
let Some(fx) = common::fixture_or_skip() else {
|
|
return;
|
|
};
|
|
let env = common::admin_env(fx);
|
|
let (script_id, guard) = common::deploy_fixture(&env, "trg-kv", "hello.rhai");
|
|
let app = guard.slug().to_string();
|
|
|
|
let out = common::pic_as(&env)
|
|
.args([
|
|
"triggers",
|
|
"create-kv",
|
|
"--app",
|
|
&app,
|
|
"--script",
|
|
&script_id,
|
|
"--collection",
|
|
"users",
|
|
"--op",
|
|
"insert",
|
|
"--op",
|
|
"update",
|
|
])
|
|
.output()
|
|
.expect("create-kv");
|
|
assert!(
|
|
out.status.success(),
|
|
"create-kv failed: {out:?}\n{}",
|
|
String::from_utf8_lossy(&out.stderr)
|
|
);
|
|
let stdout = String::from_utf8(out.stdout).unwrap();
|
|
assert!(stdout.contains("kind") && stdout.contains("kv"));
|
|
|
|
let out = common::pic_as(&env)
|
|
.args(["triggers", "ls", "--app", &app])
|
|
.output()
|
|
.expect("triggers ls");
|
|
let stdout = String::from_utf8(out.stdout).unwrap();
|
|
let header = stdout.lines().next().expect("header");
|
|
assert_eq!(
|
|
common::cells(header),
|
|
vec![
|
|
"id",
|
|
"kind",
|
|
"script_id",
|
|
"enabled",
|
|
"dispatch",
|
|
"retry_max",
|
|
"materialized",
|
|
"created_at"
|
|
]
|
|
);
|
|
let row = stdout
|
|
.lines()
|
|
.skip(1)
|
|
.map(common::cells)
|
|
.find(|c| c.get(1).copied() == Some("kv"))
|
|
.unwrap_or_else(|| panic!("kv trigger missing from ls: {stdout}"));
|
|
assert_eq!(row[2], script_id);
|
|
// D1: a hand-authored trigger is not materialized.
|
|
assert_eq!(
|
|
row[6], "false",
|
|
"an app-authored trigger shows materialized = false"
|
|
);
|
|
}
|
|
|
|
#[ignore = "needs DATABASE_URL pointing at a running Postgres"]
|
|
#[test]
|
|
fn create_cron_persists_schedule() {
|
|
let Some(fx) = common::fixture_or_skip() else {
|
|
return;
|
|
};
|
|
let env = common::admin_env(fx);
|
|
let (script_id, guard) = common::deploy_fixture(&env, "trg-cron", "hello.rhai");
|
|
let app = guard.slug().to_string();
|
|
|
|
common::pic_as(&env)
|
|
.args([
|
|
"triggers",
|
|
"create-cron",
|
|
"--app",
|
|
&app,
|
|
"--script",
|
|
&script_id,
|
|
"--schedule",
|
|
"0 0 * * * *",
|
|
])
|
|
.assert()
|
|
.success();
|
|
|
|
let out = common::pic_as(&env)
|
|
.args(["triggers", "ls", "--app", &app])
|
|
.output()
|
|
.expect("triggers ls");
|
|
let stdout = String::from_utf8(out.stdout).unwrap();
|
|
assert!(
|
|
stdout.lines().skip(1).any(|l| l.contains("cron")),
|
|
"cron trigger missing: {stdout}"
|
|
);
|
|
}
|
|
|
|
#[ignore = "needs DATABASE_URL pointing at a running Postgres"]
|
|
#[test]
|
|
fn create_dead_letter_with_filter() {
|
|
let Some(fx) = common::fixture_or_skip() else {
|
|
return;
|
|
};
|
|
let env = common::admin_env(fx);
|
|
let (script_id, guard) = common::deploy_fixture(&env, "trg-dl", "hello.rhai");
|
|
let app = guard.slug().to_string();
|
|
|
|
common::pic_as(&env)
|
|
.args([
|
|
"triggers",
|
|
"create-dead-letter",
|
|
"--app",
|
|
&app,
|
|
"--script",
|
|
&script_id,
|
|
"--source-filter",
|
|
"queue",
|
|
])
|
|
.assert()
|
|
.success();
|
|
}
|
|
|
|
#[ignore = "needs DATABASE_URL pointing at a running Postgres"]
|
|
#[test]
|
|
fn create_from_json_inline_body_succeeds() {
|
|
let Some(fx) = common::fixture_or_skip() else {
|
|
return;
|
|
};
|
|
let env = common::admin_env(fx);
|
|
let (script_id, guard) = common::deploy_fixture(&env, "trg-json", "hello.rhai");
|
|
let app = guard.slug().to_string();
|
|
|
|
let body =
|
|
format!(r#"{{"script_id":"{script_id}","collection_glob":"*","dispatch_mode":"async"}}"#);
|
|
common::pic_as(&env)
|
|
.args([
|
|
"triggers",
|
|
"create-from-json",
|
|
"--app",
|
|
&app,
|
|
"--kind",
|
|
"kv",
|
|
"--body",
|
|
&body,
|
|
])
|
|
.assert()
|
|
.success();
|
|
}
|
|
|
|
#[ignore = "needs DATABASE_URL pointing at a running Postgres"]
|
|
#[test]
|
|
fn rm_drops_trigger_from_ls() {
|
|
let Some(fx) = common::fixture_or_skip() else {
|
|
return;
|
|
};
|
|
let env = common::admin_env(fx);
|
|
let (script_id, guard) = common::deploy_fixture(&env, "trg-rm", "hello.rhai");
|
|
let app = guard.slug().to_string();
|
|
|
|
// Create + capture id from ls
|
|
common::pic_as(&env)
|
|
.args([
|
|
"triggers",
|
|
"create-cron",
|
|
"--app",
|
|
&app,
|
|
"--script",
|
|
&script_id,
|
|
"--schedule",
|
|
"0 0 * * * *",
|
|
])
|
|
.assert()
|
|
.success();
|
|
|
|
let out = common::pic_as(&env)
|
|
.args(["triggers", "ls", "--app", &app])
|
|
.output()
|
|
.expect("triggers ls");
|
|
let stdout = String::from_utf8(out.stdout).unwrap();
|
|
let row = stdout
|
|
.lines()
|
|
.skip(1)
|
|
.map(common::cells)
|
|
.find(|c| c.get(1).copied() == Some("cron"))
|
|
.unwrap_or_else(|| panic!("cron trigger missing: {stdout}"));
|
|
let id = row[0].to_string();
|
|
|
|
common::pic_as(&env)
|
|
.args(["triggers", "rm", "--app", &app, &id])
|
|
.assert()
|
|
.success()
|
|
.stdout(predicate::str::contains(format!("Deleted trigger {id}")));
|
|
|
|
let out = common::pic_as(&env)
|
|
.args(["triggers", "ls", "--app", &app])
|
|
.output()
|
|
.expect("triggers ls after rm");
|
|
let stdout = String::from_utf8(out.stdout).unwrap();
|
|
assert!(
|
|
!stdout.lines().any(|l| l.contains(&id)),
|
|
"deleted trigger still in ls: {stdout}"
|
|
);
|
|
|
|
// Belt-and-braces so guard ordering is explicit.
|
|
drop(guard);
|
|
}
|
|
|
|
/// Belt-and-braces: explicit binding so AppGuard isn't unused-warned.
|
|
#[allow(dead_code)]
|
|
fn _force_guard_in_scope() {
|
|
let _: Option<AppGuard> = None;
|
|
}
|