fix(ci): stabilize full Rust and chat E2E suites (#5241)

This commit is contained in:
Steven Enamakel
2026-07-28 14:44:25 +03:00
committed by GitHub
parent 861b21c98f
commit cfbfe3df2e
23 changed files with 236 additions and 71 deletions
+15 -15
View File
@@ -208,7 +208,7 @@ version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
dependencies = [
"windows-sys 0.60.2",
"windows-sys 0.61.2",
]
[[package]]
@@ -219,7 +219,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
dependencies = [
"anstyle",
"once_cell_polyfill",
"windows-sys 0.60.2",
"windows-sys 0.61.2",
]
[[package]]
@@ -253,7 +253,7 @@ dependencies = [
"objc2-foundation 0.3.2",
"parking_lot",
"percent-encoding",
"windows-sys 0.60.2",
"windows-sys 0.59.0",
"x11rb",
]
@@ -2064,7 +2064,7 @@ dependencies = [
"libc",
"option-ext",
"redox_users 0.5.2",
"windows-sys 0.60.2",
"windows-sys 0.59.0",
]
[[package]]
@@ -2482,7 +2482,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [
"libc",
"windows-sys 0.60.2",
"windows-sys 0.59.0",
]
[[package]]
@@ -3709,7 +3709,7 @@ dependencies = [
"js-sys",
"log",
"wasm-bindgen",
"windows-core 0.57.0",
"windows-core 0.58.0",
]
[[package]]
@@ -4110,7 +4110,7 @@ dependencies = [
"portable-atomic",
"portable-atomic-util",
"serde_core",
"windows-sys 0.60.2",
"windows-sys 0.59.0",
]
[[package]]
@@ -4994,7 +4994,7 @@ version = "0.50.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
dependencies = [
"windows-sys 0.60.2",
"windows-sys 0.59.0",
]
[[package]]
@@ -6549,7 +6549,7 @@ dependencies = [
"once_cell",
"socket2",
"tracing",
"windows-sys 0.60.2",
"windows-sys 0.59.0",
]
[[package]]
@@ -7135,7 +7135,7 @@ dependencies = [
"errno",
"libc",
"linux-raw-sys",
"windows-sys 0.60.2",
"windows-sys 0.59.0",
]
[[package]]
@@ -7194,7 +7194,7 @@ dependencies = [
"security-framework 3.7.0",
"security-framework-sys",
"webpki-root-certs",
"windows-sys 0.60.2",
"windows-sys 0.59.0",
]
[[package]]
@@ -7951,7 +7951,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e"
dependencies = [
"libc",
"windows-sys 0.60.2",
"windows-sys 0.61.2",
]
[[package]]
@@ -8811,7 +8811,7 @@ dependencies = [
"getrandom 0.4.2",
"once_cell",
"rustix",
"windows-sys 0.60.2",
"windows-sys 0.59.0",
]
[[package]]
@@ -9098,7 +9098,7 @@ dependencies = [
[[package]]
name = "tinyplace"
version = "2.0.2"
version = "2.0.4"
dependencies = [
"aes",
"async-trait",
@@ -9655,7 +9655,7 @@ checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e"
dependencies = [
"memoffset",
"tempfile",
"windows-sys 0.60.2",
"windows-sys 0.61.2",
]
[[package]]
@@ -615,7 +615,7 @@ export const ChatThreadView = forwardRef<ChatThreadViewHandle, ChatThreadViewPro
isAgentTextMode ? 'w-full max-w-full' : 'w-fit max-w-[75%]'
}`}>
{msg.sender === 'agent' ? (
<div className="space-y-1">
<div className="space-y-1" data-testid="agent-message">
<div className="relative space-y-1">
{agentMessageViewMode === 'text' ? (
<AgentMessageText content={displayContent} />
+27
View File
@@ -204,6 +204,33 @@ export async function textExists(text: string): Promise<boolean> {
}
}
/**
* Non-blocking check: is matching text rendered and visible right now?
*
* Unlike {@link textExists}, this deliberately ignores matching text retained
* inside a collapsed processing transcript.
*/
export async function visibleTextExists(text: string): Promise<boolean> {
try {
if (isTauriDriver()) {
const literal = xpathStringLiteral(text);
const matches = await browser.$$(`//*[contains(text(),${literal})]`);
for (const match of matches) {
if (await match.isDisplayed()) return true;
}
return false;
}
const matches = await browser.$$(xpathContainsText(text));
for (const match of matches) {
if (await match.isDisplayed()) return true;
}
return false;
} catch {
return false;
}
}
/**
* Wait for the app window to be visible.
*
@@ -35,6 +35,8 @@
* transitions through `phase: 'subagent'` at some point.
* - `chatRuntime.toolTimelineByThread[<thread>]` records an
* entry whose `id` starts with `<thread>:subagent:`.
* - Or the async delegation acknowledgement renders in the
* conversation (the durable signal after the parent turn ends).
* - The final orchestrator text (canary) renders in the DOM.
*
* Rust:
@@ -58,7 +60,7 @@ import {
waitForSocketConnected,
} from '../helpers/chat-harness';
import { callOpenhumanRpc } from '../helpers/core-rpc';
import { textExists } from '../helpers/element-helpers';
import { textExists, visibleTextExists } from '../helpers/element-helpers';
import { resetApp } from '../helpers/reset-app';
import { navigateViaHash } from '../helpers/shared-flows';
import { getRequestLog, setMockBehavior, startMockServer, stopMockServer } from '../mock-server';
@@ -72,6 +74,7 @@ const PROMPT = 'Please delegate a llama-research task and return the marker.';
const DELEGATE_PROMPT = 'Return the coded marker phrase.';
const RESEARCHER_REPLY = 'The researcher trace signal is FORTY-TWO.';
const CANARY_FINAL = 'subagent-canary-final-7afe2';
const ASYNC_DELEGATION_ACK = 'Accepted async sub-agent';
// Content-addressed keyword rules — never depleted, immune to extra
// ancillary /chat/completions calls (#4517).
@@ -202,6 +205,7 @@ describe('Chat harness — orchestrator → subagent flow', () => {
// entry should appear.
let sawSubagentPhase = false;
let sawSubagentTimeline = false;
let sawAsyncDelegationAck = false;
const deadline = Date.now() + 45_000;
while (Date.now() < deadline) {
const snap = await snapshotRuntime(threadId);
@@ -212,9 +216,13 @@ describe('Chat harness — orchestrator → subagent flow', () => {
) {
sawSubagentTimeline = true;
}
if (sawSubagentPhase && sawSubagentTimeline) break;
if (await textExists(CANARY_FINAL)) {
sawAsyncDelegationAck =
sawAsyncDelegationAck || (await visibleTextExists(ASYNC_DELEGATION_ACK));
if ((sawSubagentPhase && sawSubagentTimeline) || sawAsyncDelegationAck) break;
if (await visibleTextExists(CANARY_FINAL)) {
sawSubagentTimeline = sawSubagentTimeline || (await hasRenderedSubagentTimeline());
sawAsyncDelegationAck =
sawAsyncDelegationAck || (await visibleTextExists(ASYNC_DELEGATION_ACK));
break;
}
await browser.pause(200);
@@ -222,10 +230,10 @@ describe('Chat harness — orchestrator → subagent flow', () => {
sawSubagentTimeline = sawSubagentTimeline || (await hasRenderedSubagentTimeline());
// At least ONE of the two signals must have fired — the timeline
// entry is the more durable check (the live phase can flip back to
// 'thinking' or 'idle' before our 200ms poll catches it).
expect(sawSubagentPhase || sawSubagentTimeline).toBe(true);
// At least one delegation signal must have fired. Async delegation
// completes the parent tool call immediately, so its phase/timeline can
// disappear before the 200 ms poll; the acknowledgement remains visible.
expect(sawSubagentPhase || sawSubagentTimeline || sawAsyncDelegationAck).toBe(true);
// Final canary must land in the DOM after the orchestrator wraps up.
await browser.waitUntil(async () => await textExists(CANARY_FINAL), {
@@ -0,0 +1,9 @@
import type { Locator, Page } from '@playwright/test';
/**
* Locate text in the rendered assistant answer, excluding matching text kept
* in the collapsed processing transcript for the same turn.
*/
export function agentMessageText(page: Page, text: string | RegExp): Locator {
return page.getByTestId('agent-message').getByText(text).last();
}
@@ -1,5 +1,6 @@
import { expect, type Page, test } from '@playwright/test';
import { agentMessageText } from '../helpers/chat-locators';
import {
bootAuthenticatedPage,
dismissWalkthroughIfPresent,
@@ -309,7 +310,7 @@ test.describe('Chat Harness - Subagent', () => {
// so this is the full sequence — no extra calls should consume keyword
// rules out from under the next-expected matcher.
await expect.poll(completionRequestCount, { timeout: 90_000 }).toBeGreaterThanOrEqual(3);
await expect(page.getByText(CANARY_FINAL)).toBeVisible({ timeout: 30_000 });
await expect(agentMessageText(page, CANARY_FINAL)).toBeVisible({ timeout: 30_000 });
const runtimeSnapshot = await diagnosticsSnapshot(page);
expect(
@@ -323,6 +324,6 @@ test.describe('Chat Harness - Subagent', () => {
// Re-assert after the runtime probe so the persisted message survives the
// turn-completion store transition rather than only being visible mid-stream.
await expect(page.getByText(CANARY_FINAL)).toBeVisible({ timeout: 15_000 });
await expect(agentMessageText(page, CANARY_FINAL)).toBeVisible({ timeout: 15_000 });
});
});
@@ -1,5 +1,6 @@
import { expect, type Page, test } from '@playwright/test';
import { agentMessageText } from '../helpers/chat-locators';
import {
bootAuthenticatedPage,
callCoreRpc,
@@ -194,11 +195,10 @@ test.describe('Chat Harness - Wallet Flow', () => {
await sendMessage(page, WALLET_PROMPT);
await expect(
page
.getByText(
/Prepared a wallet quote for John\..*wallet-quote-canary-8d13|Done\.\s*wallet-quote-canary-8d13/i
)
.first()
agentMessageText(
page,
/Prepared a wallet quote for John\..*wallet-quote-canary-8d13|Done\.\s*wallet-quote-canary-8d13/i
)
).toBeVisible({ timeout: 40_000 });
});
});
@@ -1,5 +1,6 @@
import { expect, type Page, test } from '@playwright/test';
import { agentMessageText } from '../helpers/chat-locators';
import {
bootAuthenticatedPage,
dismissWalkthroughIfPresent,
@@ -177,7 +178,7 @@ test.describe('Chat Multi Tool Round', () => {
const threadId = await createNewThread(page);
await sendMessage(page, PROMPT);
await expect(page.getByText(CANARY_FINAL).first()).toBeVisible({ timeout: 50_000 });
await expect(agentMessageText(page, CANARY_FINAL)).toBeVisible({ timeout: 50_000 });
await expect
.poll(
@@ -1,5 +1,6 @@
import { expect, type Page, test } from '@playwright/test';
import { agentMessageText } from '../helpers/chat-locators';
import {
bootAuthenticatedPage,
dismissWalkthroughIfPresent,
@@ -169,7 +170,7 @@ test.describe('Chat Tool Call Flow', () => {
const threadId = await createNewThread(page);
await sendMessage(page, PROMPT);
await expect(page.getByText(CANARY_FINAL).first()).toBeVisible({ timeout: 40_000 });
await expect(agentMessageText(page, CANARY_FINAL)).toBeVisible({ timeout: 40_000 });
await expect
.poll(
@@ -1,5 +1,6 @@
import { expect, type Page, test } from '@playwright/test';
import { agentMessageText } from '../helpers/chat-locators';
import {
bootAuthenticatedPage,
dismissWalkthroughIfPresent,
@@ -135,8 +136,10 @@ test.describe('Harness - Cross-channel bridge flow', () => {
await createNewThread(page);
await sendMessage(page, 'set up a daily standup reminder at 9am');
await expect(page.getByText(CANARY)).toBeVisible({ timeout: 60_000 });
await expect(page.getByText(/I created a daily 9am standup reminder for you\./i)).toBeVisible();
await expect(agentMessageText(page, CANARY)).toBeVisible({ timeout: 60_000 });
await expect(
agentMessageText(page, /I created a daily 9am standup reminder for you\./i)
).toBeVisible();
});
test.skip('telegram inbound/outbound bridge scenarios require a live listener restart in this lane', async () => {});
@@ -1,5 +1,6 @@
import { expect, type Page, test } from '@playwright/test';
import { agentMessageText } from '../helpers/chat-locators';
import {
bootAuthenticatedPage,
dismissWalkthroughIfPresent,
@@ -170,8 +171,8 @@ test.describe('Harness - Composio tool-call prompt flow', () => {
await setMockBehavior('llmStreamChunkDelayMs', '10');
await sendMessage(page, 'check my email');
await expect(page.getByText(CANARY).first()).toBeVisible({ timeout: 60_000 });
await expect(page.getByText(/Q3 Budget Review/i).first()).toBeVisible();
await expect(agentMessageText(page, CANARY)).toBeVisible({ timeout: 60_000 });
await expect(agentMessageText(page, /Q3 Budget Review/i)).toBeVisible();
const log = await requests();
const llmHits = log.filter(
@@ -210,8 +211,8 @@ test.describe('Harness - Composio tool-call prompt flow', () => {
await setMockBehavior('llmStreamChunkDelayMs', '10');
await sendMessage(page, 'list my GitHub repos');
await expect(page.getByText(CANARY).first()).toBeVisible({ timeout: 60_000 });
await expect(page.getByText(/openhuman/i).first()).toBeVisible();
await expect(agentMessageText(page, CANARY)).toBeVisible({ timeout: 60_000 });
await expect(agentMessageText(page, /openhuman/i)).toBeVisible();
const log = await requests();
const llmHits = log.filter(
@@ -244,8 +245,8 @@ test.describe('Harness - Composio tool-call prompt flow', () => {
await setMockBehavior('llmStreamChunkDelayMs', '10');
await sendMessage(page, 'check my email inbox please');
await expect(page.getByText(CANARY).first()).toBeVisible({ timeout: 60_000 });
await expect(page.getByText(/unable to fetch your emails/i)).toBeVisible();
await expect(agentMessageText(page, CANARY)).toBeVisible({ timeout: 60_000 });
await expect(agentMessageText(page, /unable to fetch your emails/i)).toBeVisible();
});
test('linear create issue flow confirms creation in the final reply', async ({ page }) => {
@@ -286,9 +287,9 @@ test.describe('Harness - Composio tool-call prompt flow', () => {
await setMockBehavior('llmStreamChunkDelayMs', '10');
await sendMessage(page, 'create a linear issue titled Fix authentication timeout');
await expect(page.getByText(CANARY).first()).toBeVisible({ timeout: 60_000 });
await expect(agentMessageText(page, CANARY)).toBeVisible({ timeout: 60_000 });
// `.first()` — the confirmation text also appears in the tool-result echo
// pane, so an unscoped match trips Playwright strict mode (2 elements).
await expect(page.getByText(/I have created the Linear issue/i).first()).toBeVisible();
await expect(agentMessageText(page, /I have created the Linear issue/i)).toBeVisible();
});
});
@@ -1,5 +1,6 @@
import { expect, type Page, test } from '@playwright/test';
import { agentMessageText } from '../helpers/chat-locators';
import {
bootAuthenticatedPage,
dismissWalkthroughIfPresent,
@@ -142,8 +143,10 @@ test.describe('Harness - Cron prompt-flow', () => {
await setMockBehavior('llmStreamChunkDelayMs', '10');
await sendMessage(page, 'remind me every morning at 9am');
await expect(page.getByText(CANARY).first()).toBeVisible({ timeout: 60_000 });
await expect(page.getByText(/Done! I have set up a daily 9am morning reminder/i)).toBeVisible();
await expect(agentMessageText(page, CANARY)).toBeVisible({ timeout: 60_000 });
await expect(
agentMessageText(page, /Done! I have set up a daily 9am morning reminder/i)
).toBeVisible();
const log = await requests();
const llmHits = log.filter(
request => request.method === 'POST' && request.url.includes('/chat/completions')
@@ -164,7 +167,7 @@ test.describe('Harness - Cron prompt-flow', () => {
await setMockBehavior('llmStreamChunkDelayMs', '10');
await sendMessage(page, 'what are my scheduled tasks');
await expect(page.getByText(CANARY).first()).toBeVisible({ timeout: 60_000 });
await expect(agentMessageText(page, CANARY)).toBeVisible({ timeout: 60_000 });
await expect(page.getByText(/You have 2 scheduled tasks/i)).toBeVisible();
});
@@ -192,8 +195,8 @@ test.describe('Harness - Cron prompt-flow', () => {
await setMockBehavior('llmStreamChunkDelayMs', '10');
await sendMessage(page, 'change my morning reminder to 8am');
await expect(page.getByText(CANARY).first()).toBeVisible({ timeout: 60_000 });
await expect(page.getByText(/changed your morning reminder to 8am/i).first()).toBeVisible();
await expect(agentMessageText(page, CANARY)).toBeVisible({ timeout: 60_000 });
await expect(agentMessageText(page, /changed your morning reminder to 8am/i)).toBeVisible();
});
test('delete flow yields a final reply', async ({ page }) => {
@@ -217,7 +220,7 @@ test.describe('Harness - Cron prompt-flow', () => {
await setMockBehavior('llmStreamChunkDelayMs', '10');
await sendMessage(page, 'delete the morning reminder');
await expect(page.getByText(CANARY).first()).toBeVisible({ timeout: 60_000 });
await expect(page.getByText(/deleted the morning reminder/i).first()).toBeVisible();
await expect(agentMessageText(page, CANARY)).toBeVisible({ timeout: 60_000 });
await expect(agentMessageText(page, /deleted the morning reminder/i)).toBeVisible();
});
});
@@ -1,5 +1,6 @@
import { expect, type Page, test } from '@playwright/test';
import { agentMessageText } from '../helpers/chat-locators';
import {
bootAuthenticatedPage,
dismissWalkthroughIfPresent,
@@ -154,8 +155,8 @@ test.describe('Harness - Search tool-flow', () => {
await setMockBehavior('llmStreamChunkDelayMs', '10');
await sendMessage(page, 'what did we discuss about project Atlas');
await expect(page.getByText(CANARY).first()).toBeVisible({ timeout: 60_000 });
await expect(page.getByText(/Based on my memory search/i)).toBeVisible();
await expect(agentMessageText(page, CANARY)).toBeVisible({ timeout: 60_000 });
await expect(agentMessageText(page, /Based on my memory search/i)).toBeVisible();
const log = await requests();
const llmHits = log.filter(
@@ -186,9 +187,9 @@ test.describe('Harness - Search tool-flow', () => {
await setMockBehavior('llmStreamChunkDelayMs', '10');
await sendMessage(page, 'search for Rust async best practices');
await expect(page.getByText(CANARY).first()).toBeVisible({ timeout: 60_000 });
await expect(agentMessageText(page, CANARY)).toBeVisible({ timeout: 60_000 });
await expect(
page.getByText(/Here are the top results for Rust async best practices/i)
agentMessageText(page, /Here are the top results for Rust async best practices/i)
).toBeVisible();
const log = await requests();
@@ -219,8 +220,8 @@ test.describe('Harness - Search tool-flow', () => {
await setMockBehavior('llmStreamChunkDelayMs', '10');
await sendMessage(page, 'read the README');
await expect(page.getByText(CANARY).first()).toBeVisible({ timeout: 60_000 });
await expect(page.getByText(/OpenHuman is an AI assistant/i).first()).toBeVisible();
await expect(agentMessageText(page, CANARY)).toBeVisible({ timeout: 60_000 });
await expect(agentMessageText(page, /OpenHuman is an AI assistant/i)).toBeVisible();
const log = await requests();
const llmHits = log.filter(
@@ -1,5 +1,6 @@
import { expect, type Page, test } from '@playwright/test';
import { agentMessageText } from '../helpers/chat-locators';
import {
bootAuthenticatedPage,
callCoreRpc,
@@ -135,7 +136,7 @@ test.describe('User journey - full research task', () => {
expect(typeof threadId).toBe('string');
await sendMessage(page, PROMPT);
await expect(page.getByText(CANARY_FINAL).first()).toBeVisible({ timeout: 45_000 });
await expect(agentMessageText(page, CANARY_FINAL)).toBeVisible({ timeout: 45_000 });
// Navigate away and back to confirm the thread (and its messages) persist.
// Home folded into the unified chat surface, so /home now redirects to
@@ -152,6 +153,6 @@ test.describe('User journey - full research task', () => {
await page.goto('/#/chat');
await waitForAppReady(page);
await page.getByTestId(`thread-row-${threadId}`).click({ force: true });
await expect(page.getByText(CANARY_FINAL).first()).toBeVisible({ timeout: 15_000 });
await expect(agentMessageText(page, CANARY_FINAL)).toBeVisible({ timeout: 15_000 });
});
});
+2 -2
View File
@@ -8,8 +8,8 @@
//! ```no_run
//! # async fn demo() -> Result<(), Box<dyn std::error::Error>> {
//! use std::sync::Arc;
//! use openhuman::embed::Core;
//! use openhuman::{CoreBuilder, DomainSet, HostKind, ServiceSet};
//! use openhuman_core::embed::Core;
//! use openhuman_core::{CoreBuilder, DomainSet, HostKind, ServiceSet};
//!
//! let runtime = CoreBuilder::new(HostKind::detect_standalone())
//! .domains(DomainSet::full())
@@ -279,6 +279,7 @@ async fn profile_allowed_tools_restrict_shared_session_builder() {
let tmp = tempfile::TempDir::new().unwrap();
let config = test_config(&tmp);
let orchestrator = builtin_def("orchestrator");
let mut profile = crate::openhuman::profiles::store::built_in_default_profile();
profile.id = "alice".to_string();
profile.built_in = false;
@@ -287,7 +288,7 @@ async fn profile_allowed_tools_restrict_shared_session_builder() {
let agent = Agent::build_session_agent_inner(
&config,
"orchestrator",
None,
Some(&orchestrator),
None,
None,
false,
@@ -414,8 +414,14 @@ async fn channel_processed_event_records_resolved_agent_route() {
for _ in 0..50 {
let event = tokio::time::timeout(Duration::from_millis(200), events.recv())
.await
.expect("ChannelMessageProcessed event should be published")
.expect("event receiver should stay open");
.expect("ChannelMessageProcessed event should be published");
let event = match event {
Ok(event) => event,
Err(tokio::sync::broadcast::error::RecvError::Lagged(_)) => continue,
Err(tokio::sync::broadcast::error::RecvError::Closed) => {
panic!("event receiver should stay open")
}
};
if let DomainEvent::ChannelMessageProcessed {
message_id,
+8 -4
View File
@@ -2081,10 +2081,14 @@ async fn composio_set_api_key_validates_candidate_key_even_when_stored_key_exist
Some("ck_old_valid".to_string()),
"failed replacement must leave the old stored key intact"
);
assert_eq!(
seen_keys.lock().unwrap().as_slice(),
["ck_new_invalid"],
"validation must probe the candidate key, not the stored key"
assert!(
seen_keys
.lock()
.unwrap()
.iter()
.any(|key| key == "ck_new_invalid"),
"validation must probe the candidate key even when other parallel direct-mode tests \
share the process-wide mock base URL"
);
}
+2
View File
@@ -31,6 +31,8 @@ pub use loader::{
// expose internal helpers needed by tests (ops_tests.rs uses super::*)
#[cfg(test)]
pub(crate) use crate::openhuman::config::Config;
#[cfg(all(test, windows))]
pub(crate) use loader::reset_local_data_remove_error;
#[cfg(test)]
pub(crate) use loader::{
active_workspace_marker_path, config_openhuman_dir, default_openhuman_dir, env_flag_enabled,
+2 -4
View File
@@ -1460,15 +1460,13 @@ async fn load_and_resolve_api_url_returns_api_url_in_response() {
#[test]
fn resolve_api_url_keeps_inference_overrides_away_from_backend_credentials() {
let mut config = Config::default();
let expected_backend = crate::api::config::effective_backend_api_url(&None);
for inference_url in ["http://localhost:11434/v1", "https://openrouter.ai/api/v1"] {
config.api_url = Some(inference_url.to_string());
let resolved = resolve_backend_api_url(&config);
assert_ne!(resolved, inference_url);
assert!(
resolved.contains("tinyhumans.ai"),
"expected hosted backend fallback, got {resolved}"
);
assert_eq!(resolved, expected_backend);
}
}
@@ -496,6 +496,13 @@ mod tests {
const TEST_SLOT_ENGINE_A: &str = "__test_slot_engine_a__";
const TEST_SLOT_ENGINE_B: &str = "__test_slot_engine_b__";
fn slot_test_lock() -> std::sync::MutexGuard<'static, ()> {
static LOCK: std::sync::OnceLock<std::sync::Mutex<()>> = std::sync::OnceLock::new();
LOCK.get_or_init(|| std::sync::Mutex::new(()))
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner())
}
/// Best-effort drain of a test slot so the global set is clean across
/// runs. Tests that leave a slot held (e.g. by forgetting it) would
/// pollute subsequent runs in the same `cargo test` invocation.
@@ -507,6 +514,7 @@ mod tests {
#[test]
fn try_acquire_install_slot_grants_then_blocks_then_releases() {
let _test_guard = slot_test_lock();
drain_test_slot(TEST_SLOT_ENGINE_A);
// First caller gets the slot.
@@ -534,6 +542,7 @@ mod tests {
#[test]
fn install_slot_keys_are_independent_per_engine() {
let _test_guard = slot_test_lock();
drain_test_slot(TEST_SLOT_ENGINE_A);
drain_test_slot(TEST_SLOT_ENGINE_B);
@@ -561,7 +570,8 @@ mod tests {
/// same time and both spawn install tasks" — the bug CodeRabbit
/// flagged on PR #1755.
#[tokio::test]
async fn concurrent_acquire_grants_exactly_one_slot() {
async fn concurrent_install_slot_acquire_grants_exactly_one() {
let _test_guard = slot_test_lock();
drain_test_slot(TEST_SLOT_ENGINE_A);
// 32 concurrent acquirers — high enough to make a non-atomic
@@ -127,9 +127,51 @@ impl Tool for MemoryStoreRawChunksTool {
#[cfg(test)]
mod tests {
use super::*;
use std::ffi::OsString;
use tempfile::TempDir;
use crate::openhuman::config::{Config, TEST_ENV_LOCK};
use crate::openhuman::tools::traits::Tool;
use serde_json::json;
struct WorkspaceEnvGuard {
_lock: std::sync::MutexGuard<'static, ()>,
previous: Option<OsString>,
}
impl WorkspaceEnvGuard {
fn set(path: &std::path::Path) -> Self {
let lock = TEST_ENV_LOCK.lock().unwrap_or_else(|err| err.into_inner());
let previous = std::env::var_os("OPENHUMAN_WORKSPACE");
unsafe {
std::env::set_var("OPENHUMAN_WORKSPACE", path);
}
Self {
_lock: lock,
previous,
}
}
}
impl Drop for WorkspaceEnvGuard {
fn drop(&mut self) {
unsafe {
if let Some(previous) = self.previous.as_ref() {
std::env::set_var("OPENHUMAN_WORKSPACE", previous);
} else {
std::env::remove_var("OPENHUMAN_WORKSPACE");
}
}
}
}
async fn isolated_config(tmp: &TempDir) -> (WorkspaceEnvGuard, Config) {
let guard = WorkspaceEnvGuard::set(tmp.path());
let config = Config::load_or_init().await.expect("load config");
(guard, config)
}
#[test]
fn args_deserialize_optional_filters() {
let args: Args = serde_json::from_value(json!({
@@ -192,6 +234,8 @@ mod tests {
#[tokio::test]
async fn execute_success_path_returns_json_array() {
let tmp = TempDir::new().expect("tempdir");
let (_workspace, _config) = isolated_config(&tmp).await;
let tool = MemoryStoreRawChunksTool;
let result = tool
.execute(json!({
@@ -106,9 +106,51 @@ impl Tool for MemoryStoreRawSearchTool {
#[cfg(test)]
mod tests {
use super::*;
use std::ffi::OsString;
use tempfile::TempDir;
use crate::openhuman::config::{Config, TEST_ENV_LOCK};
use crate::openhuman::tools::traits::Tool;
use serde_json::json;
struct WorkspaceEnvGuard {
_lock: std::sync::MutexGuard<'static, ()>,
previous: Option<OsString>,
}
impl WorkspaceEnvGuard {
fn set(path: &std::path::Path) -> Self {
let lock = TEST_ENV_LOCK.lock().unwrap_or_else(|err| err.into_inner());
let previous = std::env::var_os("OPENHUMAN_WORKSPACE");
unsafe {
std::env::set_var("OPENHUMAN_WORKSPACE", path);
}
Self {
_lock: lock,
previous,
}
}
}
impl Drop for WorkspaceEnvGuard {
fn drop(&mut self) {
unsafe {
if let Some(previous) = self.previous.as_ref() {
std::env::set_var("OPENHUMAN_WORKSPACE", previous);
} else {
std::env::remove_var("OPENHUMAN_WORKSPACE");
}
}
}
}
async fn isolated_config(tmp: &TempDir) -> (WorkspaceEnvGuard, Config) {
let guard = WorkspaceEnvGuard::set(tmp.path());
let config = Config::load_or_init().await.expect("load config");
(guard, config)
}
#[test]
fn default_limit_is_five() {
assert_eq!(default_limit(), 5);
@@ -158,6 +200,8 @@ mod tests {
#[tokio::test]
async fn execute_success_path_returns_json_array() {
let tmp = TempDir::new().expect("tempdir");
let (_workspace, _config) = isolated_config(&tmp).await;
let tool = MemoryStoreRawSearchTool;
let result = tool
.execute(json!({