Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,9 @@ jobs:
cargo test --locked -p webcodex --lib -- \
openapi_operation_ids_are_minimal \
openapi_all_local_refs_resolve \
explicit_resume_openapi_metadata_is_distinct_from_session_recording \
openapi_tool_call_request_does_not_advertise_hidden_start_bootstrap \
mcp_tools_list_returns_same_names_as_runtime \
mcp_tools_list_parity_with_rest_tools_list \
explicit_resume_mcp_schema_and_metadata_are_exposed \
explicit_resume_advanced_compatibility_schema_and_metadata_are_retained \
http_project_connector_lists_and_dispatches_only_canonical_capabilities

test-linux-rust:
Expand Down
3 changes: 3 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ Product direction: [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md).
- Implementation ownership and independent adversarial review are separate passes. In an implementation-owner pass, prioritize a complete authoritative vertical slice and strong first delivery within the current contract; resolve known correctness issues, including concrete authority/identity boundaries created by the feature, but do not fragment the implementation around speculative reviewer concerns. A later review pass independently challenges the resulting design and implementation.
- Keep only the interfaces actually affected by the change consistent. Do not touch or revalidate unrelated projections merely because they exist.
- Add focused tests for changed behavior when practical. Update documentation when public behavior or operations change.
- When a subsystem already has a dedicated `tests/` module tree, put ordinary new tests there instead of growing production facade files. Keep inline `#[cfg(test)]` blocks small and tightly coupled to private implementation helpers; process, network, and integration fixtures belong in dedicated test modules.
- Do not grow one test file into a multi-domain catch-all. When an already-large test module needs coverage for a distinct lifecycle or contract domain, create or reuse a domain-specific test module and split by canonical ownership, not arbitrary line-count chunks.
- In async tests, required readiness must use a `wait_*` path with one absolute deadline that partial progress never resets. `probe_*` helpers are only for observations where immediate absence is valid or for one iteration inside an already-owned outer deadline; never use a probe when absence means test failure.
- Ask only when required information cannot be discovered, instructions materially conflict, or proceeding could destroy work. Otherwise continue and report any material deviation.

## 4. Validate only changed behavior
Expand Down
69 changes: 55 additions & 14 deletions crates/webcodex-admin/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,47 @@ fn request(values: &[&str]) -> AdminCliRequest {
build_admin_request(&cmd).unwrap()
}

struct EnvGuard {
_lock: std::sync::MutexGuard<'static, ()>,
previous: std::collections::BTreeMap<String, Option<std::ffi::OsString>>,
}

impl EnvGuard {
fn new() -> Self {
Self {
_lock: TEST_ENV_LOCK
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner()),
previous: std::collections::BTreeMap::new(),
}
}

fn set(&mut self, name: &str, value: &str) {
self.previous
.entry(name.to_string())
.or_insert_with(|| std::env::var_os(name));
std::env::set_var(name, value);
}

fn remove(&mut self, name: &str) {
self.previous
.entry(name.to_string())
.or_insert_with(|| std::env::var_os(name));
std::env::remove_var(name);
}
}

impl Drop for EnvGuard {
fn drop(&mut self) {
for (name, value) in &self.previous {
match value {
Some(value) => std::env::set_var(name, value),
None => std::env::remove_var(name),
}
}
}
}

#[test]
fn admin_usage_keeps_rest_registration_commands_but_not_create_local() {
let stdout = usage();
Expand Down Expand Up @@ -162,8 +203,8 @@ fn agent_tokens_register_hash_builds_hash_registration_request() {

#[test]
fn agent_tokens_register_hash_defaults_agent_scopes_and_prefers_admin_token() {
let _guard = TEST_ENV_LOCK.lock().unwrap();
std::env::set_var("WEBCODEX_ACCOUNT_CREDENTIAL", "wc_acct_default");
let mut env = EnvGuard::new();
env.set("WEBCODEX_ACCOUNT_CREDENTIAL", "wc_acct_default");
let req = request(&[
"agent-tokens",
"register-hash",
Expand All @@ -190,13 +231,13 @@ fn agent_tokens_register_hash_defaults_agent_scopes_and_prefers_admin_token() {
"agent:job_update"
])
);
std::env::remove_var("WEBCODEX_ACCOUNT_CREDENTIAL");
env.remove("WEBCODEX_ACCOUNT_CREDENTIAL");
}

#[test]
fn agent_tokens_register_hash_uses_credential_env_and_default_account_credential() {
let _guard = TEST_ENV_LOCK.lock().unwrap();
std::env::set_var("CUSTOM_ACCT", "wc_acct_custom");
let mut env = EnvGuard::new();
env.set("CUSTOM_ACCT", "wc_acct_custom");
let req = request(&[
"agent-tokens",
"register-hash",
Expand All @@ -214,9 +255,9 @@ fn agent_tokens_register_hash_uses_credential_env_and_default_account_credential
"wc_agent_aaaaaaa",
]);
assert_eq!(req.token, "wc_acct_custom");
std::env::remove_var("CUSTOM_ACCT");
env.remove("CUSTOM_ACCT");

std::env::set_var("WEBCODEX_ACCOUNT_CREDENTIAL", "wc_acct_default");
env.set("WEBCODEX_ACCOUNT_CREDENTIAL", "wc_acct_default");
let req = request(&[
"agent-tokens",
"register-hash",
Expand All @@ -232,7 +273,7 @@ fn agent_tokens_register_hash_uses_credential_env_and_default_account_credential
"wc_agent_bbbbbbb",
]);
assert_eq!(req.token, "wc_acct_default");
std::env::remove_var("WEBCODEX_ACCOUNT_CREDENTIAL");
env.remove("WEBCODEX_ACCOUNT_CREDENTIAL");
}

#[test]
Expand Down Expand Up @@ -289,8 +330,8 @@ fn token_file_is_read() {

#[test]
fn env_token_fallback_is_used() {
let _guard = TEST_ENV_LOCK.lock().unwrap();
std::env::set_var("WEBCODEX_TOKEN", "fake-env-token");
let mut env = EnvGuard::new();
env.set("WEBCODEX_TOKEN", "fake-env-token");
let cmd = parse_admin_cli(&args(&[
"users",
"list",
Expand All @@ -300,13 +341,13 @@ fn env_token_fallback_is_used() {
.unwrap();
let req = build_admin_request(&cmd).unwrap();
assert_eq!(req.token, "fake-env-token");
std::env::remove_var("WEBCODEX_TOKEN");
env.remove("WEBCODEX_TOKEN");
}

#[test]
fn explicit_admin_token_wins_over_default_account_credential_env() {
let _guard = TEST_ENV_LOCK.lock().unwrap();
std::env::set_var("WEBCODEX_ACCOUNT_CREDENTIAL", "fake-account-credential");
let mut env = EnvGuard::new();
env.set("WEBCODEX_ACCOUNT_CREDENTIAL", "fake-account-credential");
let cmd = parse_admin_cli(&args(&[
"tokens",
"register-hash",
Expand All @@ -324,7 +365,7 @@ fn explicit_admin_token_wins_over_default_account_credential_env() {
.unwrap();
let req = build_admin_request(&cmd).unwrap();
assert_eq!(req.token, "fake-admin");
std::env::remove_var("WEBCODEX_ACCOUNT_CREDENTIAL");
env.remove("WEBCODEX_ACCOUNT_CREDENTIAL");
}

#[test]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,33 @@ $form.Add_Shown({ $form.Activate(); $button.Focus() })
.expect("launch private WinForms foreground probe");
Self { child }
}

fn wait_for_window(&mut self, title: &str, context: &str) -> PlatformWindow {
let deadline = Instant::now() + Duration::from_secs(10);
loop {
if let Some(status) = self
.child
.try_wait()
.unwrap_or_else(|error| panic!("query {context} process: {error}"))
{
panic!("{context} exited before discovery: {status}");
}
if let Some(candidate) = platform::list_windows(4096)
.unwrap_or_else(|error| panic!("list Windows windows for {context}: {error}"))
.into_iter()
.find(|candidate| candidate.title == title)
{
return candidate;
}
let now = Instant::now();
assert!(now < deadline, "timed out discovering {context}");
thread::sleep(
deadline
.saturating_duration_since(now)
.min(Duration::from_millis(20)),
);
}
}
}

impl Drop for WindowsControlFixture {
Expand Down Expand Up @@ -664,25 +691,10 @@ fn computer_windows_window_activation_live_smoke() {
#[ignore = "requires an interactive Windows desktop; creates and closes a private WinForms control fixture"]
fn computer_windows_control_fixture_live_smoke() {
let mut fixture = WindowsControlFixture::start();
let candidate = (0..500)
.find_map(|_| {
if let Some(status) = fixture
.child
.try_wait()
.expect("query private WinForms fixture process")
{
panic!("private WinForms fixture exited before discovery: {status}");
}
let candidate = platform::list_windows(4096)
.expect("list Windows windows for control fixture")
.into_iter()
.find(|candidate| candidate.title == WINDOWS_CONTROL_FIXTURE_TITLE);
if candidate.is_none() {
thread::sleep(Duration::from_millis(20));
}
candidate
})
.expect("discover private WinForms control fixture");
let candidate = fixture.wait_for_window(
WINDOWS_CONTROL_FIXTURE_TITLE,
"private WinForms control fixture",
);
let record = surface_record(candidate);
let activation = platform::activate_window("surface_windows_control_fixture_activate", &record)
.expect("activate private WinForms control fixture");
Expand Down Expand Up @@ -812,25 +824,10 @@ fn computer_windows_control_fixture_live_smoke() {
#[ignore = "requires an interactive Windows desktop; creates and closes a private scrollable WinForms fixture"]
fn computer_windows_scroll_to_element_fixture_live_smoke() {
let mut fixture = WindowsControlFixture::start();
let candidate = (0..500)
.find_map(|_| {
if let Some(status) = fixture
.child
.try_wait()
.expect("query private WinForms scroll fixture process")
{
panic!("private WinForms scroll fixture exited before discovery: {status}");
}
let candidate = platform::list_windows(4096)
.expect("list Windows windows for scroll fixture")
.into_iter()
.find(|candidate| candidate.title == WINDOWS_CONTROL_FIXTURE_TITLE);
if candidate.is_none() {
thread::sleep(Duration::from_millis(20));
}
candidate
})
.expect("discover private WinForms scroll fixture");
let candidate = fixture.wait_for_window(
WINDOWS_CONTROL_FIXTURE_TITLE,
"private WinForms scroll fixture",
);
let record = surface_record(candidate);
platform::activate_window("surface_windows_scroll_fixture_activate", &record)
.expect("activate private WinForms scroll fixture");
Expand Down Expand Up @@ -904,25 +901,10 @@ fn computer_windows_scroll_to_element_fixture_live_smoke() {
#[ignore = "requires an interactive Windows desktop; creates and closes only private WinForms key-input fixtures"]
fn computer_windows_key_input_fixture_live_smoke() {
let mut fixture = WindowsControlFixture::start();
let candidate = (0..500)
.find_map(|_| {
if let Some(status) = fixture
.child
.try_wait()
.expect("query private WinForms key-input fixture process")
{
panic!("private WinForms key-input fixture exited before discovery: {status}");
}
let candidate = platform::list_windows(4096)
.expect("list Windows windows for key-input fixture")
.into_iter()
.find(|candidate| candidate.title == WINDOWS_CONTROL_FIXTURE_TITLE);
if candidate.is_none() {
thread::sleep(Duration::from_millis(20));
}
candidate
})
.expect("discover private WinForms key-input fixture");
let candidate = fixture.wait_for_window(
WINDOWS_CONTROL_FIXTURE_TITLE,
"private WinForms key-input fixture",
);
let record = surface_record(candidate);
let hwnd = platform::win_hwnd(record.native_id).expect("resolve key fixture HWND");
let foreground_deadline = Instant::now() + Duration::from_secs(2);
Expand Down Expand Up @@ -1073,25 +1055,8 @@ fn computer_windows_key_input_fixture_live_smoke() {
assert!(protected.starts_with("permission_denied:"), "{protected}");

let mut foreground_probe = WindowsControlFixture::start_foreground_probe();
let probe_candidate = (0..500)
.find_map(|_| {
if let Some(status) = foreground_probe
.child
.try_wait()
.expect("query private foreground probe process")
{
panic!("private foreground probe exited before discovery: {status}");
}
let candidate = platform::list_windows(4096)
.expect("list Windows windows for foreground probe")
.into_iter()
.find(|candidate| candidate.title == WINDOWS_FOREGROUND_PROBE_TITLE);
if candidate.is_none() {
thread::sleep(Duration::from_millis(20));
}
candidate
})
.expect("discover private foreground probe");
let probe_candidate = foreground_probe
.wait_for_window(WINDOWS_FOREGROUND_PROBE_TITLE, "private foreground probe");
let probe_record = surface_record(probe_candidate);
let probe_hwnd =
platform::win_hwnd(probe_record.native_id).expect("resolve foreground probe HWND");
Expand All @@ -1118,25 +1083,10 @@ fn computer_windows_key_input_fixture_live_smoke() {
#[ignore = "requires an interactive Windows desktop; creates and replaces indistinguishable private WinForms controls"]
fn computer_windows_uia_stale_identity_rejects_indistinguishable_replacement_live() {
let mut fixture = WindowsControlFixture::start();
let candidate = (0..500)
.find_map(|_| {
if let Some(status) = fixture
.child
.try_wait()
.expect("query private WinForms fixture process")
{
panic!("private WinForms fixture exited before discovery: {status}");
}
let candidate = platform::list_windows(4096)
.expect("list Windows windows for identity fixture")
.into_iter()
.find(|candidate| candidate.title == WINDOWS_CONTROL_FIXTURE_TITLE);
if candidate.is_none() {
thread::sleep(Duration::from_millis(20));
}
candidate
})
.expect("discover private WinForms identity fixture");
let candidate = fixture.wait_for_window(
WINDOWS_CONTROL_FIXTURE_TITLE,
"private WinForms identity fixture",
);
let record = surface_record(candidate);
platform::activate_window("surface_windows_identity_fixture_activate", &record)
.expect("activate private WinForms identity fixture");
Expand Down
Loading
Loading