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
9 changes: 9 additions & 0 deletions .config/nextest.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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'
Expand Down
23 changes: 13 additions & 10 deletions crates/cli/src/config_bundles.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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:#}");
Expand All @@ -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);
Expand All @@ -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")
);
}
}

Expand Down
8 changes: 4 additions & 4 deletions crates/config/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()))
Expand All @@ -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");
Expand All @@ -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");
Expand All @@ -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"))
Expand Down
5 changes: 4 additions & 1 deletion crates/tui/src/extension_host/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down
7 changes: 4 additions & 3 deletions web/app/[locale]/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 `<html lang="wp-login.php">`.
// 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
Expand Down
32 changes: 21 additions & 11 deletions web/lib/locale-layout.test.ts
Original file line number Diff line number Diff line change
@@ -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" }) }));

Expand All @@ -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.
Expand All @@ -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));
});
});
Loading