Skip to content

Commit ffdb83c

Browse files
committed
test(stream): prove recovery receipts and exact argv
1 parent 11f5c1c commit ffdb83c

3 files changed

Lines changed: 168 additions & 5 deletions

File tree

src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -860,7 +860,7 @@ enum StreamCmd {
860860
/// Adapter command run under `sh -c`; omit both launch forms for external ingress.
861861
#[arg(long, conflicts_with = "adapter_argv")]
862862
command: Option<String>,
863-
/// Direct adapter argv. Element 0 is the program.
863+
/// Direct adapter argv after `--`. Element 0 is the program; values are preserved exactly.
864864
#[arg(trailing_var_arg = true, allow_hyphen_values = true)]
865865
adapter_argv: Vec<String>,
866866
#[arg(long)]

tests/event_e2e.rs

Lines changed: 90 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -451,10 +451,10 @@ fn initial_supersession_authenticates_predecessor_immediately_before_archive() {
451451
"hetz",
452452
"hetz.worker",
453453
"gh-ci",
454-
"passed",
454+
"initial-passed",
455455
Some("pr-1"),
456-
Some("passed"),
457-
"passed",
456+
Some("initial passed"),
457+
"initial passed",
458458
true,
459459
)
460460
.unwrap_err();
@@ -470,7 +470,7 @@ fn initial_supersession_authenticates_predecessor_immediately_before_archive() {
470470
message::list_inbox(&inbox)
471471
.unwrap()
472472
.iter()
473-
.any(|message| { message.event_id.as_deref() == Some("passed") })
473+
.any(|message| { message.event_id.as_deref() == Some("initial-passed") })
474474
);
475475
}
476476

@@ -869,6 +869,92 @@ fn pending_supersession_authenticates_its_predecessor_before_archive() {
869869
);
870870
}
871871

872+
#[test]
873+
fn pending_supersession_accepts_an_authenticated_archive_only_predecessor() {
874+
let _fail_env = EVENT_FAIL_ENV.lock().unwrap();
875+
let catalog = tempfile::tempdir().unwrap();
876+
let agent = declare_agent(catalog.path(), "\"running\"", " stream \"gh-ci\" {}\n");
877+
let predecessor = emit(catalog.path(), "running", Some("pr-1"), false);
878+
unsafe { std::env::set_var("ST2_TEST_EVENT_FAIL_AT", "passed:materialized") };
879+
let _ = event::emit(
880+
catalog.path(),
881+
"hetz",
882+
"hetz.worker",
883+
"gh-ci",
884+
"passed",
885+
Some("pr-1"),
886+
Some("passed"),
887+
"passed",
888+
true,
889+
)
890+
.unwrap_err();
891+
unsafe { std::env::remove_var("ST2_TEST_EVENT_FAIL_AT") };
892+
let inbox = message::inbox_dir(&agent);
893+
let archive = message::archive_dir(&agent);
894+
message::archive_msg(&inbox, &archive, &predecessor.filename).unwrap();
895+
896+
let next = emit(catalog.path(), "unrelated", Some("pr-2"), false);
897+
898+
assert_eq!(next.status, EventReceiptStatus::Created);
899+
assert!(archive.join(&predecessor.filename).is_file());
900+
assert!(
901+
message::list_inbox(&inbox)
902+
.unwrap()
903+
.iter()
904+
.any(|message| { message.event_id.as_deref() == Some("passed") })
905+
);
906+
}
907+
908+
#[test]
909+
fn pending_supersession_fails_closed_when_predecessor_has_no_receipt() {
910+
let _fail_env = EVENT_FAIL_ENV.lock().unwrap();
911+
let catalog = tempfile::tempdir().unwrap();
912+
let agent = declare_agent(catalog.path(), "\"running\"", " stream \"gh-ci\" {}\n");
913+
let predecessor = emit(catalog.path(), "running", Some("pr-1"), false);
914+
unsafe { std::env::set_var("ST2_TEST_EVENT_FAIL_AT", "passed:materialized") };
915+
let _ = event::emit(
916+
catalog.path(),
917+
"hetz",
918+
"hetz.worker",
919+
"gh-ci",
920+
"passed",
921+
Some("pr-1"),
922+
Some("passed"),
923+
"passed",
924+
true,
925+
)
926+
.unwrap_err();
927+
unsafe { std::env::remove_var("ST2_TEST_EVENT_FAIL_AT") };
928+
let inbox = message::inbox_dir(&agent);
929+
fs::remove_file(inbox.join(&predecessor.filename)).unwrap();
930+
931+
let error = event::emit(
932+
catalog.path(),
933+
"hetz",
934+
"hetz.worker",
935+
"gh-ci",
936+
"unrelated",
937+
None,
938+
None,
939+
"unrelated",
940+
false,
941+
)
942+
.unwrap_err();
943+
944+
assert!(
945+
error
946+
.to_string()
947+
.contains("has no inbox file or archive receipt"),
948+
"{error:#}"
949+
);
950+
assert!(
951+
message::list_inbox(&inbox)
952+
.unwrap()
953+
.iter()
954+
.all(|message| { message.event_id.as_deref() != Some("unrelated") })
955+
);
956+
}
957+
872958
#[test]
873959
fn keyless_supersede_replaces_the_stream_wide_head() {
874960
let catalog = tempfile::tempdir().unwrap();

tests/stream_authoring_cli.rs

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,83 @@ fn a_direct_adapter_launch_executes_the_exact_event_cli_contract() {
165165
);
166166
}
167167

168+
#[test]
169+
fn direct_adapter_argv_preserves_spaces_and_metacharacters_exactly() {
170+
let catalog = tempfile::tempdir().unwrap();
171+
write_agent(catalog.path());
172+
let expected = [
173+
"/bin/example adapter",
174+
"argument with spaces",
175+
"$HOME",
176+
"$(never-executed)",
177+
"semi;colon",
178+
"quote\"and\\slash",
179+
"--looks-like-a-flag",
180+
];
181+
let mut args = vec![
182+
"stream",
183+
"add",
184+
"exact-argv",
185+
"--agent",
186+
"hetz.worker",
187+
"--host",
188+
"hetz",
189+
"--",
190+
];
191+
args.extend(expected);
192+
193+
let add = st2(catalog.path(), &args);
194+
195+
assert!(
196+
add.status.success(),
197+
"{}",
198+
String::from_utf8_lossy(&add.stderr)
199+
);
200+
let spec = st2::discover(catalog.path()).specs.remove(0);
201+
let stream = spec
202+
.streams
203+
.iter()
204+
.find(|stream| stream.name == "exact-argv")
205+
.unwrap();
206+
assert_eq!(
207+
stream.launch,
208+
Some(st2::spec::StreamLaunch::Argv(
209+
expected.iter().map(|value| (*value).to_owned()).collect()
210+
))
211+
);
212+
}
213+
214+
#[test]
215+
fn command_and_direct_argv_are_mutually_exclusive() {
216+
let catalog = tempfile::tempdir().unwrap();
217+
write_agent(catalog.path());
218+
219+
let add = st2(
220+
catalog.path(),
221+
&[
222+
"stream",
223+
"add",
224+
"ambiguous",
225+
"--agent",
226+
"hetz.worker",
227+
"--host",
228+
"hetz",
229+
"--command",
230+
"echo shell",
231+
"--",
232+
"/bin/echo",
233+
"direct",
234+
],
235+
);
236+
237+
assert!(!add.status.success());
238+
assert!(
239+
String::from_utf8_lossy(&add.stderr).contains("cannot be used with"),
240+
"{}",
241+
String::from_utf8_lossy(&add.stderr)
242+
);
243+
}
244+
168245
#[test]
169246
fn a_bare_actor_can_self_author_on_the_selected_host() {
170247
let catalog = tempfile::tempdir().unwrap();

0 commit comments

Comments
 (0)