Files
alkssh/tests/client_ops.rs
T
glm-5.3-flash 2371c2e169 feat(client): land the op rows — exec/pty, direct-tcpip, git exec, sftp placeholder (w4-op-rows)
The family op → vanilla translation surfaces on SshConnection, per
client.md §op rows:

- session: exec (pipe) / pty_exec / shell → RemoteSession — the
  alktty chunk plane both directions, the exit consumed in-band as
  the family exit chunk (exit-chunk-is-last); the POC-3 §D.3
  mechanics pinned (exec() + wait()-drain, split-halves pump for
  exit-consuming rows, typed-open-then-straight-into_stream for the
  exit-less tunnel rows)
- tunnel: tunnel_direct / tunnel_direct_unix → TunnelChannel — typed
  opens, rejections mirrored to OpError::OpenDeclined
  (OpenDeclineReason), the connection stays usable after every
  decline (row-pinned per class); the OQ-11 runtime-Unix gate mirrored
- git: git_service(Service, repo_id) → GitPackSession — the shared
  grammar's construction arm in dispatch.rs (parse round-trips
  unit-pinned), the raw pack duplex pumped, the exit consumed
- sftp: the every-build OQ-13 placeholder (OpError::SftpNotServed,
  Infallible Ok type), before any wire op
- shared exit-currency module src/exit_code.rs (extracted from
  tty_map.rs): SIGNAL_NAMES + derive_exit + sig maps two-direction,
  new signal_code_from_name client arm
- OpError taxonomy (closed, family-mirrored) in error.rs; the fake
  sshd double grown with the op-row script + observations;
  tests/client_ops.rs — 18 rows + the alkssh↔alkssh exec smoke
  (real-vanilla-sshd smoke named wave-5 e2e territory)

Verification: cargo test (default + per-feature + all-features) all
green; clippy ×2 -D warnings clean; fmt clean; cargo doc --no-deps
warning-free.
2026-10-29 16:21:25 +00:00

839 lines
33 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! The op rows' contract rows (`w4-op-rows`) — driven through the
//! in-process fake `sshd` grown with the op-row script
//! ([`sshd_double`](common::sshd_double), the client-direction
//! vehicle): featured + working rows (exec exit-status, pty
//! window-change riding the live pump, the direct-tcpip pump, the git
//! exec round trip), the decline rows (server-rejected opens, the
//! sftp placeholder, the off-Unix streamlocal twin — OQ-11), and the
//! connection-stays-usable row after each decline class. The
//! row/stamp discipline (`tests/common/mod.rs`'s doc) applies
//! verbatim.
//!
//! **Smoke posture** (the task's decided + recorded item): the session
//! rows' cheap real-stack smoke is the alkssh-client-op-row ↔
//! alkssh-server-direction loopback (both russh stacks; the client
//! exec row driven against the server direction's *real* session rows
//! — the chunk bridge's two arms meeting over one live wire), gated
//! on tooling like the wave-3 smokes — skipped-with-reason when
//! absent, never silently green. A real-vanilla-`sshd` dial is wave-5
//! e2e territory explicitly (see the task's Notes).
mod common;
use std::sync::Arc;
use std::time::Duration;
use alkssh::{DialConfig, Limits, OpError, SshConnection, dial};
#[cfg(any(feature = "session", feature = "tunnel"))]
use alkssh::OpenDeclineReason;
#[cfg(feature = "session")]
use alkssh::fingerprint;
#[cfg(feature = "tunnel")]
use common::sshd_double::DblDirect;
#[cfg(any(feature = "session", feature = "git"))]
use common::sshd_double::DblExit;
use common::sshd_double::{SshdDouble, SshdObserved, SshdScript, spawn_scripted};
use common::{MemHostKeyPolicy, TestVault, fixture};
/// A vault-backed key source over the alpha fixture — the dial
/// posture (the fixture PEM sealed, decrypt-at-dial).
fn alpha_key_source() -> Arc<dyn alkssh::KeySource> {
let vault = TestVault::unlocked();
let blob = vault.seal(fixture("alpha"));
Arc::new(alkssh::VaultKeySource::new(vault.handle(), Some(blob)))
}
/// Dials a fresh double under the given script — one dial per row per
/// double. Returns the connection, the double (its outcome channel
/// for the clean-close rows) and the double's observations.
async fn dial_scripted(script: SshdScript) -> (SshConnection, SshdDouble, SshdObserved) {
dial_scripted_limits(script, Limits::default()).await
}
/// [`dial_scripted`] tuned with an explicit budget (the client-side
/// caps are [`Limits`], carried by the dial).
async fn dial_scripted_limits(
script: SshdScript,
limits: Limits,
) -> (SshConnection, SshdDouble, SshdObserved) {
let (double, accepts, observed) =
spawn_scripted(vec![fixture("bravo").private_key()], true, script).await;
accepts.register(fixture("alpha").fingerprint);
let policy = Arc::new(MemHostKeyPolicy::new());
policy.pin("127.0.0.1", fixture("bravo").fingerprint);
let mut config = DialConfig::new(
double.address.clone(),
"runner",
alpha_key_source(),
Arc::clone(&policy) as Arc<dyn alkssh::HostKeyPolicy>,
);
config.limits = limits;
let connection = dial(config).await.expect("the dial succeeds");
(connection, double, observed)
}
/// The clean-close observable: the explicit close leaves the double's
/// session `Ok` — the decline rows' "the connection stays usable"
/// final act.
async fn expect_clean_close(connection: SshConnection, mut double: SshdDouble) {
assert!(!connection.is_closed());
connection.close().await;
let outcome = double.next_session_outcome().await;
assert!(
matches!(outcome, Some(Ok(()))),
"the clean close is the double's clean session end, got {outcome:?}"
);
double.shutdown();
}
/// `next_opened_session` bounded for the "no channel ever opened"
/// assertions (an unbounded `recv` would hang a wrong row).
async fn no_open_observed(double: &mut SshdDouble, within: &str) {
let deadline = tokio::time::Instant::now() + Duration::from_millis(400);
let opened = tokio::time::timeout_at(deadline, double.next_opened_session()).await;
assert!(
matches!(opened, Ok(None) | Err(_)),
"no session channel may open before {within}: got {opened:?}"
);
}
/// OQ-13's client mirror: the sftp op declines cleanly in **every**
/// build (the `sftp` feature ships in the build string but gates no
/// route — the featured-build variant below pins that half too,
/// `--all-features` running both).
///
/// - *Contract stamp:* OQ-13 (the decline placeholder is the shipped
/// behavior, both directions) + `client.md` §op rows (row 5) + §What
/// the crate does *not* include — before any wire op: no session
/// channel ever opens at the peer, no subsystem request ever rides,
/// and the connection stays usable (the clean close is the double's
/// clean session end).
#[tokio::test]
async fn sftp_placeholder_declines_in_every_build_before_any_wire_op() {
let (connection, mut double, observed) = dial_scripted(SshdScript::default()).await;
let err = connection
.sftp()
.await
.expect_err("the OQ-13 placeholder declines the sftp op");
assert!(matches!(err, OpError::SftpNotServed), "got {err:?}");
assert!(
observed.execs().is_empty(),
"the placeholder consulted no wire op"
);
no_open_observed(&mut double, "the sftp placeholder's decline").await;
expect_clean_close(connection, double).await;
}
#[cfg(feature = "session")]
#[tokio::test]
async fn sftp_feature_on_the_placeholder_still_declines() {
let (connection, mut double, observed) = dial_scripted(SshdScript::default()).await;
let err = connection
.sftp()
.await
.expect_err("the feature never serves the route (OQ-13 — the placeholder ships)");
assert!(matches!(err, OpError::SftpNotServed), "got {err:?}");
assert!(observed.execs().is_empty());
no_open_observed(&mut double, "the featured placeholder's decline").await;
expect_clean_close(connection, double).await;
}
#[cfg(feature = "session")]
mod session_rows {
use super::*;
use alktty::wire::{ChunkReader, STREAM_CTRL_IN, STREAM_STDIN};
use alktty::{ControlMessage, negotiation::TerminalParamsWire};
use tokio::io::AsyncWriteExt;
/// Frames one chunk by hand (`stream_type` byte + the big-endian
/// `u32` length + payload — the published header format, read
/// back only through alktty's own [`ChunkReader`]).
fn frame(stream_type: u8, payload: &[u8]) -> Vec<u8> {
let mut out = vec![stream_type];
out.extend_from_slice(&(payload.len() as u32).to_be_bytes());
out.extend_from_slice(payload);
out
}
/// Frames one control chunk — the family currency's own
/// serializer builds the payload.
async fn control_frame(message: &ControlMessage) -> Vec<u8> {
let payload = message.to_json().expect("the control serializes");
frame(STREAM_CTRL_IN, &payload)
}
/// Drains the hub's chunk plane until the exit chunk or the end —
/// returns (stdout, stderr, the exit code in-band, if any).
async fn read_to_exit(mut session: alkssh::RemoteSession) -> (Vec<u8>, Vec<u8>, Option<i32>) {
let mut stdout = Vec::new();
let mut stderr = Vec::new();
let mut exit = None;
let mut reader = ChunkReader::new(&mut session.output);
let deadline = tokio::time::Instant::now() + Duration::from_secs(10);
loop {
let chunk = match tokio::time::timeout_at(deadline, reader.read_chunk()).await {
Ok(Ok(chunk)) => chunk,
_ => break,
};
match chunk.stream_type {
alktty::wire::STREAM_STDOUT => stdout.extend_from_slice(&chunk.bytes),
alktty::wire::STREAM_STDERR => stderr.extend_from_slice(&chunk.bytes),
alktty::wire::STREAM_CTRL_OUT => {
if let Ok(ControlMessage::Exit { code }) =
ControlMessage::from_slice(&chunk.bytes)
{
exit = Some(code);
}
break;
}
_ => {}
}
}
(stdout, stderr, exit)
}
/// The exit row: the sentinel rides, the pump delivers.
async fn send_stdin_eof(session: &mut alkssh::RemoteSession) {
session
.input
.write_all(&frame(STREAM_STDIN, &[]))
.await
.expect("the stdin sentinel writes");
}
/// - *Contract stamp:* `client.md` §op rows (row 1, the exec
/// translation + the family plane) — the wire carries the
/// family command verbatim (`exec()`, never `request_exec`);
/// stdout/stderr come back as distinct chunks and the exit
/// status rides the family chunk in-band, last.
#[tokio::test]
async fn exec_row_pumps_the_program_and_the_exit_through_the_family_plane() {
let script = SshdScript {
stdout: b"out-ok",
stderr: b"err-why",
exit: Some(DblExit::Status(0)),
..SshdScript::default()
};
let (connection, double, observed) = dial_scripted(script).await;
let mut session = connection
.exec("echo out-ok")
.await
.expect("the exec row launches");
assert_eq!(
observed.execs(),
vec![b"echo out-ok".to_vec()],
"the wire carries the family command, never altered"
);
send_stdin_eof(&mut session).await;
let (stdout, stderr, exit) = read_to_exit(session).await;
assert_eq!(stdout, b"out-ok");
assert_eq!(stderr, b"err-why");
assert_eq!(
exit,
Some(0),
"exit-status(0) rode the wire; the family chunk mirrors it in-band"
);
expect_clean_close(connection, double).await;
}
/// - *Contract stamp:* POC-3 §D.3 (the Success/Failure semantics —
/// **the Failure reply surfaces on the channel queue's drain,
/// not as `Err` from the request call**) →
/// [`OpError::RequestRefused`]; the refused row closes its
/// channel behind the refusal and the connection stays usable
/// (the clean-close observable).
#[tokio::test]
async fn refused_exec_reply_surfaces_the_request_refusal() {
let script = SshdScript {
request_success: false,
..SshdScript::default()
};
let (connection, double, observed) = dial_scripted(script).await;
let err = connection
.exec("echo refused")
.await
.expect_err("the scripted refusal surfaces");
assert!(matches!(err, OpError::RequestRefused), "got {err:?}");
assert_eq!(
observed.execs(),
vec![b"echo refused".to_vec()],
"the wire carried the request (its Failure reply is the class)"
);
expect_clean_close(connection, double).await;
}
/// - *Contract stamp:* the command-carrying rows' length budget
/// (the server direction's pre-parse cap, `Limits::
/// max_exec_command_len`, mirrored at the command-construction
/// gate): a beyond-budget command declines **before any wire
/// op** — no channel, no request, bounded by construction.
#[tokio::test]
async fn oversized_exec_declines_before_any_wire_op() {
let limits = Limits {
max_exec_command_len: 4,
..Limits::default()
};
let script = SshdScript {
stdout: b"never",
exit: Some(DblExit::Status(0)),
..SshdScript::default()
};
let (connection, mut double, observed) = dial_scripted_limits(script, limits).await;
let err = connection
.exec("echo too long")
.await
.expect_err("the budget gate declines before the wire");
assert!(matches!(err, OpError::CommandTooLong), "got {err:?}");
assert!(observed.execs().is_empty());
no_open_observed(&mut double, "the budget gate's decline").await;
expect_clean_close(connection, double).await;
}
/// - *Contract stamp:* `client.md` §op rows (row 1, the pty/shell
/// translation) — the family [`TerminalParamsWire`] rides the
/// wire `pty_request` verbatim (the reserved modes never ride:
/// the double sees the params, and the wire request carries the
/// empty terminal-modes set), and the shell request launches
/// the session.
#[tokio::test]
async fn pty_shell_translates_the_family_params_onto_the_wire() {
let (connection, double, observed) = dial_scripted(SshdScript::default()).await;
let params = TerminalParamsWire {
term: Some("xterm-256color".to_owned()),
cols: 80,
rows: 24,
pixel_width: 10,
pixel_height: 20,
modes: serde_json::json!({}),
};
let session = connection
.shell(Some(params))
.await
.expect("the pty shell row launches");
assert_eq!(
observed.ptys(),
vec![("xterm-256color".to_owned(), 80, 24, 10, 20)],
"the family params ride the wire pty request verbatim"
);
assert_eq!(observed.shells().len(), 1, "the shell request rides");
drop(session);
expect_clean_close(connection, double).await;
}
/// - *Contract stamp:* the pipe-mode shell parity (mirroring the
/// server direction's decided row — a pty-less shell is
/// legitimate vanilla and served, never declined): the
/// `params: None` shell carries **no** pty request.
#[tokio::test]
async fn pipe_shell_launches_without_a_pty_request() {
let (connection, double, observed) = dial_scripted(SshdScript::default()).await;
let session = connection
.shell(None)
.await
.expect("the pipe shell launches");
assert!(
observed.ptys().is_empty(),
"no pty request rides the pipe-mode shell"
);
assert_eq!(observed.shells().len(), 1);
drop(session);
expect_clean_close(connection, double).await;
}
/// - *Contract stamp:* the pty control rows riding the **live
/// pump** (the POC-3 §D.3 window-change/signal mechanics',
/// client arm): the session never `into_stream()`s, so the
/// fire-and-forget rows ride the write half mid-session — the
/// `CTRL_IN` `Resize` translates to the wire `window_change`
/// and `Signal` to the wire `signal` (the double's
/// observations are the witnesses).
#[tokio::test]
async fn pty_control_rows_ride_the_live_session_pump() {
let (connection, double, observed) = dial_scripted(SshdScript::default()).await;
let params = TerminalParamsWire {
term: Some("xterm-256color".to_owned()),
cols: 80,
rows: 24,
pixel_width: 0,
pixel_height: 0,
modes: serde_json::json!({}),
};
let mut session = connection
.shell(Some(params))
.await
.expect("the pty shell row launches");
session
.input
.write_all(
&control_frame(&ControlMessage::Resize {
cols: 120,
rows: 40,
pixel_width: 0,
pixel_height: 0,
})
.await,
)
.await
.expect("the resize writes");
session
.input
.write_all(
&control_frame(&ControlMessage::Signal {
name: "TERM".to_owned(),
})
.await,
)
.await
.expect("the signal writes");
let deadline = tokio::time::Instant::now() + Duration::from_secs(10);
let witnessed = || {
observed.windows() == vec![(120, 40, 0, 0)]
&& observed.signals().iter().any(|s| s.contains("TERM"))
};
while !witnessed() {
if tokio::time::Instant::now() >= deadline {
panic!("the control translations never landed: {observed:?}");
}
tokio::time::sleep(Duration::from_millis(20)).await;
}
assert_eq!(
observed.windows(),
vec![(120, 40, 0, 0)],
"the CTRL_IN Resize translated to the wire window_change"
);
assert!(
observed.signals().iter().any(|s| s.contains("TERM")),
"the CTRL_IN Signal translated to the wire signal, name verbatim"
);
drop(session);
expect_clean_close(connection, double).await;
}
/// - *Contract stamp:* the family exit-currency's signal mirror
/// (mapping.md's derivation contract, the client arm — consumed
/// not re-derived): a wire `exit-signal("TERM")` reports the
/// family code `−15` in-band (rule 2's value, the table read
/// backwards; the derivation never invents).
#[tokio::test]
async fn exit_signal_report_mirrors_the_family_code_in_band() {
let script = SshdScript {
stdout: b"killed",
exit: Some(DblExit::Signal("TERM")),
..SshdScript::default()
};
let (connection, double, observed) = dial_scripted(script).await;
let mut session = connection
.exec("start a session")
.await
.expect("the exec row launches");
assert_eq!(observed.execs(), vec![b"start a session".to_vec()]);
send_stdin_eof(&mut session).await;
let (stdout, _, exit) = read_to_exit(session).await;
assert_eq!(stdout, b"killed");
assert_eq!(
exit,
Some(-15),
"the −signum mirror, table-read, no invention"
);
expect_clean_close(connection, double).await;
}
/// - *Contract stamp:* the never-half-capability posture (ADR-003
/// decision 3, client mirror) — a **refused pty refuses the
/// whole row**: the exec behind it never rides the wire (the
/// double's exec observations stay empty).
#[tokio::test]
async fn refused_pty_refuses_the_whole_row_never_half_serves() {
let script = SshdScript {
request_success: false,
..SshdScript::default()
};
let (connection, double, observed) = dial_scripted(script).await;
let params = TerminalParamsWire {
term: Some("xterm".to_owned()),
cols: 80,
rows: 24,
pixel_width: 0,
pixel_height: 0,
modes: serde_json::json!({}),
};
let err = connection
.pty_exec(params, "echo never")
.await
.expect_err("the refused pty refuses the row");
assert!(matches!(err, OpError::RequestRefused), "got {err:?}");
assert!(
observed.execs().is_empty() && observed.shells().is_empty(),
"the requests behind the refused pty never rode the wire (the row's channel closed behind the refusal)"
);
expect_clean_close(connection, double).await;
}
/// - *Contract stamp:* the server-rejected **open** row (POC-1 §6,
/// client arm) + the connection-stays-usable row (per decline
/// class, `client.md` §Correctness facts): the session open
/// refused (the scripted count) → `OpenDeclined(Prohibited)`,
/// then a later launch on the *same connection* succeeds and
/// pumps — the usable-after-decline posture — and everything
/// closes clean.
#[tokio::test]
async fn server_rejected_open_declines_and_the_connection_stays_usable() {
let script = SshdScript {
reject_first_session_opens: 1,
stdout: b"still-usable",
exit: Some(DblExit::Status(0)),
..SshdScript::default()
};
let (connection, double, observed) = dial_scripted(script).await;
let err = connection
.exec("echo declined")
.await
.expect_err("the server-rejected open declines");
assert!(
matches!(err, OpError::OpenDeclined(OpenDeclineReason::Prohibited)),
"got {err:?}"
);
assert!(
!connection.is_closed(),
"the decline never poisons the connection"
);
let mut session = connection
.exec("echo still-usable")
.await
.expect("the same connection serves the next launch");
send_stdin_eof(&mut session).await;
let (stdout, _, exit) = read_to_exit(session).await;
assert_eq!(stdout, b"still-usable");
assert_eq!(exit, Some(0));
assert_eq!(
observed.execs(),
vec![b"echo still-usable".to_vec()],
"the declined open carried no request; the usable launch rode the wire"
);
expect_clean_close(connection, double).await;
}
/// - *Contract stamp:* the task's smoke item (the §module docs'
/// decided posture; like the wave-3 smokes, skipped-with-reason
/// when the tooling is absent): the client exec row driven
/// against the alkssh *server direction*'s real session rows —
/// both russh stacks over one live wire, the chunk bridge's two
/// arms meeting (the client's family plane consuming the
/// server's family plane), data and the exit-status
/// round-tripping end to end.
#[tokio::test]
async fn smoke_client_exec_row_against_the_server_directions_session_rows() {
if std::process::Command::new("sh")
.arg("-c")
.arg("exit 0")
.output()
.is_err()
{
eprintln!("SKIPPED (with reason): the op-row smoke — sh is unavailable");
return;
}
let provider = Arc::new(common::MemIdentityProvider::new());
provider.register(
&fingerprint(&fixture("alpha").public_key()),
common::identity("op-row-smoke", &["ssh"]),
);
let mut serve =
common::harness::serve_config(provider, &[fixture("bravo")], Limits::default());
serve.exec_wrapper_argv = Some(vec!["/bin/sh".to_owned(), "-c".to_owned()]);
{
let mut backends = std::collections::HashMap::new();
backends.insert(
"local".to_owned(),
Arc::new(alktty::LocalTtyBackend::new()) as Arc<dyn alktty::TtyBackend>,
);
serve.session_seam = Some(alkssh::SessionSeam {
backends: Arc::new(backends),
backend: "local".to_owned(),
ownership: None,
});
}
let adapter = Arc::new(
alkssh::SshAdapter::new(serve)
.await
.expect("the smoke adapter constructs"),
);
let listener = tokio::net::TcpListener::bind("127.0.0.1:0")
.await
.expect("the smoke listener binds");
let addr = listener.local_addr().expect("the smoke has an address");
let accept_task = tokio::spawn(async move {
loop {
let Ok((stream, _)) = listener.accept().await else {
return;
};
let adapter = Arc::clone(&adapter);
tokio::spawn(async move {
let _ = adapter.handle(stream).await;
});
}
});
let policy = Arc::new(MemHostKeyPolicy::new());
policy.pin("127.0.0.1", fixture("bravo").fingerprint);
let config = DialConfig::new(
addr.to_string(),
"runner",
alpha_key_source(),
Arc::clone(&policy) as Arc<dyn alkssh::HostKeyPolicy>,
);
let connection = dial(config).await.expect("the smoke dial succeeds");
let session = connection
.exec("echo alkssh-op-row-smoke")
.await
.expect("the client exec row drives the alkssh server direction");
let (stdout, stderr, exit) = read_to_exit(session).await;
assert!(
String::from_utf8_lossy(&stdout).contains("alkssh-op-row-smoke"),
"the program's stdout rode both chunk bridges: {stdout:?}"
);
assert_eq!(stderr, Vec::<u8>::new(), "pipe mode keeps stderr separate");
assert_eq!(
exit,
Some(0),
"the exit chunk rode the family plane, both arms"
);
accept_task.abort();
connection.close().await;
}
}
#[cfg(feature = "tunnel")]
mod tunnel_rows {
use super::*;
use alkssh::TunnelChannel;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
/// - *Contract stamp:* `client.md` §op rows (row 2, the typed
/// `channel_open_direct_tcpip` open — the alksocks `DialFn`
/// composes over this surface) + POC-3 §C.1's recipe reversed —
/// the pump is the raw byte duplex, round-tripping, no channel
/// queue consumed anywhere between the open and the
/// `into_stream()` conversion.
#[tokio::test]
async fn direct_tcpip_pump_round_trips_the_raw_duplex() {
let script = SshdScript {
direct: DblDirect::Echo,
..SshdScript::default()
};
let (connection, double, _observed) = dial_scripted(script).await;
let mut channel: TunnelChannel = connection
.tunnel_direct("hub.example", 4096)
.await
.expect("the typed open succeeds");
let payload = b"tunnel-payload-round-trip";
channel.write_all(payload).await.expect("the pump writes");
channel.flush().await.expect("the pump flushes");
let mut echo = vec![0u8; payload.len()];
tokio::time::timeout(Duration::from_secs(10), channel.read_exact(&mut echo))
.await
.expect("the echo arrives")
.expect("the echo reads");
assert_eq!(echo, payload, "the byte plane round-trips");
channel.shutdown().await.expect("the EOF rides");
expect_clean_close(connection, double).await;
}
/// - *Contract stamp:* the server-rejected open's family mirror
/// (POC-1 §6 — the typed open resolves `Err` on the reject,
/// family-mapped) + the connection-stays-usable row **per
/// decline class**: after the `ConnectFailed` class the
/// streamlocal twin still opens and pumps through the same
/// connection, and the whole thing closes clean.
#[cfg(unix)]
#[tokio::test]
async fn server_rejected_direct_open_leaves_the_connection_usable() {
let script = SshdScript {
direct: DblDirect::Reject,
streamlocal: DblDirect::Echo,
..SshdScript::default()
};
let (connection, double, _observed) = dial_scripted(script).await;
let err = connection
.tunnel_direct("hub.example", 4096)
.await
.expect_err("the scripted server refuses the direct open");
assert!(
matches!(err, OpError::OpenDeclined(OpenDeclineReason::ConnectFailed)),
"the wire class mirrors, got {err:?}"
);
assert!(
!connection.is_closed(),
"the decline never poisons the connection"
);
let mut channel = connection
.tunnel_direct_unix("/run/alkssh-op-row.sock")
.await
.expect("the usable-after-decline row: the twin opens on the same connection");
let payload = b"after-the-decline";
channel.write_all(payload).await.expect("the pump writes");
channel.flush().await.expect("the pump flushes");
let mut echo = vec![0u8; payload.len()];
tokio::time::timeout(Duration::from_secs(10), channel.read_exact(&mut echo))
.await
.expect("the echo arrives")
.expect("the echo reads");
assert_eq!(echo, payload);
expect_clean_close(connection, double).await;
}
/// - *Contract stamp:* OQ-11 (the streamlocal twin's runtime-Unix
/// gate, client arm — universal compile, clean off-Unix decline
/// **before any wire op**): this row compiles only off-Unix;
/// the on-Unix builds ride the usable-after-decline row's
/// success half.
#[cfg(not(unix))]
#[tokio::test]
async fn streamlocal_twin_declines_off_unix_before_any_wire_op() {
let (connection, mut double, _observed) = dial_scripted(SshdScript::default()).await;
let err = connection
.tunnel_direct_unix("/run/alkssh-off-unix.sock")
.await
.expect_err("the OQ-11 gate declines off-Unix");
assert!(matches!(err, OpError::UnixUnavailable), "got {err:?}");
no_open_observed(&mut double, "the off-Unix twin's decline").await;
expect_clean_close(connection, double).await;
}
}
#[cfg(feature = "git")]
mod git_rows {
use super::*;
use alkgit::Service;
use alkssh::GitPackSession;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
/// The scripted "advertisement" bytes the double's program
/// writes (a placeholder pack-side payload for the pump row).
const ADVERT: &[u8] = b"0008version 2\n0000";
/// - *Contract stamp:* `client.md` §op rows (row 4 — the shared
/// exec grammar's command, *reused, not forked*; the wire
/// carries `git-upload-pack '/owner/demo'` byte-verbatim) + the
/// pack duplex pump + the exit-status consumption.
#[tokio::test]
async fn git_row_round_trips_the_pack_duplex_and_consumes_the_exit() {
let script = SshdScript {
stdout: ADVERT,
exit: Some(DblExit::Status(0)),
..SshdScript::default()
};
let (connection, double, observed) = dial_scripted(script).await;
let mut pack: GitPackSession = connection
.git_service(Service::UploadPack, "owner/demo")
.await
.expect("the git row opens");
assert_eq!(
observed.execs(),
vec![b"git-upload-pack '/owner/demo'".to_vec()],
"the shared grammar's command rode the wire byte-verbatim"
);
pack.output
.write_all(b"pack bytes upstream")
.await
.expect("the pack pump writes");
pack.input.shutdown().await.expect("the EOF rides");
let mut advert = Vec::new();
tokio::time::timeout(
Duration::from_secs(10),
pack.output.read_to_end(&mut advert),
)
.await
.expect("the advertisement arrives")
.expect("the advertisement reads");
assert_eq!(advert, ADVERT, "the pack duplex pumped");
let exit = tokio::time::timeout(Duration::from_secs(10), pack.exit)
.await
.expect("the exit report arrives")
.expect("the pump consumed it");
assert_eq!(exit, 0, "exit-status(0) consumed into the family code");
expect_clean_close(connection, double).await;
}
/// - *Contract stamp:* the git row's pre-wire gates riding the
/// shared grammar's verdicts (`client.md` §op rows, row 4; the
/// git row's server-direction gate mirrored): an id outside the
/// ADR-008 grammar declines (`OpError::InvalidRepoId`) **before
/// any wire op**.
#[tokio::test]
async fn invalid_repo_id_declines_before_any_wire_op() {
let (connection, mut double, observed) = dial_scripted(SshdScript::default()).await;
let err = connection
.git_service(Service::UploadPack, "a/b/c")
.await
.expect_err("the validation gate declines");
assert!(matches!(err, OpError::InvalidRepoId), "got {err:?}");
assert!(observed.execs().is_empty());
no_open_observed(&mut double, "the invalid-repo-id decline").await;
expect_clean_close(connection, double).await;
}
/// - *Contract stamp:* the per-length cap's client mirror (the
/// shared grammar's verdict — `Limits::
/// max_exec_command_len`): before any wire op.
#[tokio::test]
async fn oversized_git_command_declines_before_any_wire_op() {
let limits = Limits {
max_exec_command_len: 4,
..Limits::default()
};
let (connection, mut double, observed) =
dial_scripted_limits(SshdScript::default(), limits).await;
let err = connection
.git_service(Service::ReceivePack, "owner/demo")
.await
.expect_err("the length gate declines");
assert!(matches!(err, OpError::CommandTooLong), "got {err:?}");
assert!(observed.execs().is_empty());
no_open_observed(&mut double, "the length gate's decline").await;
expect_clean_close(connection, double).await;
}
/// - *Contract stamp:* the upstream's own refusal is the
/// request-refusal class (the drain's `Failure` — POC-3 §D.3's
/// git-row shape): declined cleanly, the connection intact.
#[tokio::test]
async fn refused_git_exec_surfaces_the_request_refusal() {
let script = SshdScript {
request_success: false,
..SshdScript::default()
};
let (connection, double, observed) = dial_scripted(script).await;
let err = connection
.git_service(Service::ReceivePack, "owner/demo")
.await
.expect_err("the scripted upstream refuses");
assert!(matches!(err, OpError::RequestRefused), "got {err:?}");
assert_eq!(
observed.execs(),
vec![b"git-receive-pack '/owner/demo'".to_vec()],
"the receive-pack command rode the wire (the refusal is its reply)"
);
expect_clean_close(connection, double).await;
}
}