diff --git a/.config/nextest.toml b/.config/nextest.toml index 4162cf17f2..6c4c8fe146 100644 --- a/.config/nextest.toml +++ b/.config/nextest.toml @@ -44,6 +44,11 @@ exec-persistent-service = { max-threads = 1 } # (a durable heartbeat and a `ps` sample on every scheduler tick) is now # bounded to once a second per worker. fleet-manager-lifecycle = { max-threads = 1 } +# Extension fixtures launch real Node hosts with a five-second production +# handshake deadline. Windows full-suite runs on #6776/#6780 reported ordinary +# hosts missing that deadline. Bound contention across nextest processes; +# keep production deadlines and the explicit hang-detection tests unchanged. +extension-host = { max-threads = 1 } # First matching test-group override wins. Keep these more-specific # integration filters before binary(integration) so they are not stolen @@ -70,6 +75,10 @@ test-group = 'telemetry-contract' filter = 'package(codewhale-tui) & kind(lib) & test(/^fleet::manager::tests::/)' test-group = 'fleet-manager-lifecycle' +[[profile.default.overrides]] +filter = 'package(codewhale-tui) & kind(lib) & (test(/^extension_host::/) | test(/^core::engine::.*extension/))' +test-group = 'extension-host' + [[profile.default.overrides]] filter = 'binary(integration) & test(/^exec_persistent_service::/)' test-group = 'exec-persistent-service' diff --git a/crates/cli/src/config_bundles.rs b/crates/cli/src/config_bundles.rs index 8ab266156e..00765811f3 100644 --- a/crates/cli/src/config_bundles.rs +++ b/crates/cli/src/config_bundles.rs @@ -2699,7 +2699,7 @@ log_level = "trace" bundle .preferences .entries - .insert("allow_shell".into(), toml::Value::Boolean(false)); + .insert("log_level".into(), toml::Value::String("info".into())); let error = apply_bundle(&bundle, &mut store, BundleScope::Global, dir.path()) .expect_err("closed choice must fail before the transaction"); assert!(error.to_string().contains(key), "{error:#}"); @@ -2721,16 +2721,18 @@ log_level = "trace" #[test] fn closed_choice_imports_accept_reader_values_without_revalidating_old_fields() { - for (key, value) in [ - ("approval_policy", " On-Request "), - ("sandbox_mode", " WORKSPACE-WRITE "), - ("verbosity", " CONCISE "), - ] { + // Trust posture is intentionally machine-bound and rejected by the + // bundle boundary even when its value is otherwise valid. Exercise + // the portable closed choice here; authority rejection has its own + // no-write and export-scrubbing regressions below. + for value in [" CONCISE ", "normal"] { + let key = "verbosity"; let dir = tempfile::tempdir().unwrap(); let path = dir.path().join("config.toml"); // Loading and unrelated round trips still preserve legacy text; only // the value being written gets the shared closed-choice validation. - let original = "# Preserve old fields\nverbosity = 'quiet'\n"; + let original = + "# Preserve old fields\napproval_policy = 'legacy-unknown'\nverbosity = 'quiet'\n"; std::fs::write(&path, original).unwrap(); let mut store = ConfigStore::load(Some(path.clone())).unwrap(); assert_eq!(std::fs::read_to_string(&path).unwrap(), original); @@ -2749,9 +2751,10 @@ log_level = "trace" ); let reloaded = ConfigStore::load(Some(path)).unwrap(); assert_eq!(reloaded.config.get_value(key).as_deref(), Some(value)); - if key != "verbosity" { - assert_eq!(reloaded.config.verbosity.as_deref(), Some("quiet")); - } + assert_eq!( + reloaded.config.approval_policy.as_deref(), + Some("legacy-unknown") + ); } } diff --git a/crates/config/src/tests.rs b/crates/config/src/tests.rs index be6b750e11..da1ebea38f 100644 --- a/crates/config/src/tests.rs +++ b/crates/config/src/tests.rs @@ -10281,7 +10281,7 @@ fn typed_save_round_trips_every_builtin_provider_selector() { }; store .config - .set_value("verbosity", "quiet") + .set_value("verbosity", "concise") .expect("set verbosity"); store.save().expect("typed save"); let reloaded = ConfigStore::load(Some(path.clone())) @@ -10299,7 +10299,7 @@ fn legacy_siliconflow_cn_spelling_loads_and_is_repaired_on_save() { assert_eq!(store.config.provider, ProviderKind::SiliconflowCN); store .config - .set_value("verbosity", "quiet") + .set_value("verbosity", "concise") .expect("set verbosity"); store.save().expect("typed save"); let body = fs::read_to_string(&path).expect("read config"); @@ -10318,7 +10318,7 @@ fn typed_save_keeps_a_providers_section_holding_only_a_legacy_kind_table() { }; store .config - .set_value("verbosity", "quiet") + .set_value("verbosity", "concise") .expect("set verbosity"); store.save().expect("typed save"); let body = fs::read_to_string(&path).expect("read config"); @@ -10342,7 +10342,7 @@ fn typed_save_keeps_runtime_owned_keys_in_typed_sub_tables() { let mut store = ConfigStore::load(Some(path.clone())).expect("load config"); store .config - .set_value("verbosity", "quiet") + .set_value("verbosity", "concise") .expect("set verbosity"); store.save().expect("typed save"); let saved: toml::Table = toml::from_str(&fs::read_to_string(&path).expect("read config")) diff --git a/crates/tui/src/extension_host/tests.rs b/crates/tui/src/extension_host/tests.rs index f0b111c158..d495e3a13c 100644 --- a/crates/tui/src/extension_host/tests.rs +++ b/crates/tui/src/extension_host/tests.rs @@ -1131,7 +1131,10 @@ async fn typed_author_example_is_reviewed_before_its_tool_can_execute() { "trust alone does not enable code" ); plugins.enable("hello-extension").unwrap(); - let manager = supervised_manager(&fixture, node); + // This tests plugin review and typed loading, not hang detection. The + // 600 ms watchdog used by supervision fault tests can kill a healthy + // typed-plugin load on a busy runner before registration completes. + let manager = fixture.manager(node); let engine = manager.attach(Arc::new(plugins)); engine.sync().await.unwrap(); let tool = host_tool(&engine, fixture.workspace(), "hello_greet"); diff --git a/web/app/[locale]/layout.tsx b/web/app/[locale]/layout.tsx index eb48af1013..6c684b426c 100644 --- a/web/app/[locale]/layout.tsx +++ b/web/app/[locale]/layout.tsx @@ -80,9 +80,10 @@ export default async function LocaleLayout({ params: Promise<{ locale: string }>; }) { const { locale } = await params; - // Middleware leaves dotted paths alone, so `/wp-login.php` reaches this - // segment with that "locale". Without this it rendered the home page with - // HTTP 200 and ``. + // Dotted paths bypass locale redirection so real files keep resolving. + // An unknown path such as /foo.txt (or /foo.txt/faq) still binds `[locale]` + // here. Reject it before reading dictionaries or rendering home chrome, + // so nonexistent files never become HTTP 200 pages with a fake html lang. if (!isValidLocale(locale)) notFound(); const chrome = getChrome(locale); // RTL locales (e.g. ar) set the document direction from the canonical diff --git a/web/lib/locale-layout.test.ts b/web/lib/locale-layout.test.ts index 3b04ec9855..2db4c8c18e 100644 --- a/web/lib/locale-layout.test.ts +++ b/web/lib/locale-layout.test.ts @@ -1,4 +1,6 @@ -import { describe, expect, it, vi } from "vitest"; +import { afterEach, describe, expect, it, vi } from "vitest"; +import { localeDirection, locales } from "./i18n/config"; +import * as dictionaries from "./i18n/dictionaries"; vi.mock("next/font/local", () => ({ default: () => ({ variable: "font" }) })); @@ -9,12 +11,16 @@ function render(locale: string) { return LocaleLayout({ children: null, params: Promise.resolve({ locale }) }); } +afterEach(() => vi.restoreAllMocks()); + describe("locale layout", () => { - // Middleware skips dotted paths, so `/wp-login.php` reaches `[locale]`. - it("answers not-found for a segment that is not a locale", async () => { - await expect(render("wp-login.php")).rejects.toMatchObject({ - digest: expect.stringContaining("404"), - }); + // Middleware leaves dotted files alone; unknown files reach `[locale]` + // just like unknown directory segments, including their child routes. + const invalidLocales = ["foo.txt", "llms-full.txt", "robots.json", "wp-login.php", "xx", ""]; + it.each(invalidLocales)("rejects %j before reading chrome dictionaries", async (locale) => { + const chrome = vi.spyOn(dictionaries, "getChrome"); + await expect(render(locale)).rejects.toMatchObject({ digest: "NEXT_HTTP_ERROR_FALLBACK;404" }); + expect(chrome).not.toHaveBeenCalled(); }); // The layout cannot catch its own notFound(), so the root boundary answers. @@ -29,14 +35,18 @@ describe("locale layout", () => { }); // Otherwise the home page's title, canonical, and hreflang stream into it. - it("gives that not-found the not-found metadata, not the home page's", async () => { - const metadata = await generateMetadata({ params: Promise.resolve({ locale: "wp-login.php" }) }); + it.each(invalidLocales)("gives %j noindex metadata without a home dictionary", async (locale) => { + const home = vi.spyOn(dictionaries, "getHome"); + const metadata = await generateMetadata({ params: Promise.resolve({ locale }) }); expect(metadata.title).toBe("Not found ยท Codewhale"); + expect(metadata.robots).toEqual({ index: false, follow: true }); expect(metadata.alternates).toEqual({}); + expect(home).not.toHaveBeenCalled(); }); - it("renders a real locale", async () => { - const element = await render("en"); - expect(element.props.lang).toBe("en"); + it.each(locales)("renders the registered locale %s", async (locale) => { + const element = await render(locale); + expect(element.props.lang).toBe(locale); + expect(element.props.dir).toBe(localeDirection(locale)); }); });