use agentos_execution::javascript::ModuleResolutionTestHarness;use serde::Deserialize;use serde_json::Value;use std::fs;use std::os::unix::fs::symlink;use std::path::PathBuf;use tempfile::TempDir; struct Fixture { temp: TempDir,} impl Fixture { fn new() -> Self { Self { temp: TempDir::new().expect("create temp dir"), } } fn host_path(&self, relative: &str) -> PathBuf { self.temp.path().join(relative) } fn write(&self, relative: &str, contents: &str) { let path = self.host_path(relative); if let Some(parent) = path.parent() { fs::create_dir_all(parent).expect("create parent dirs"); } fs::write(path, contents).expect("write fixture file"); } fn write_json(&self, relative: &str, value: Value) { self.write( relative, &serde_json::to_string_pretty(&value).expect("serialize json"), ); } fn mkdir(&self, relative: &str) { fs::create_dir_all(self.host_path(relative)).expect("create fixture dir"); } fn symlink_dir(&self, target_relative: &str, link_relative: &str) { let target = self.host_path(target_relative); let link = self.host_path(link_relative); if let Some(parent) = link.parent() { fs::create_dir_all(parent).expect("create symlink parent"); } symlink(target, link).expect("create directory symlink"); } fn resolver(&self) -> ModuleResolutionTestHarness { ModuleResolutionTestHarness::new(self.temp.path()) }} fn assert_import(fixture: &Fixture, specifier: &str, from_path: &str, expected: &str) { let mut resolver = fixture.resolver(); assert_eq!( resolver.resolve_import(specifier, from_path), Some(String::from(expected)) );} fn assert_require(fixture: &Fixture, specifier: &str, from_path: &str, expected: &str) { let mut resolver = fixture.resolver(); assert_eq!( resolver.resolve_require(specifier, from_path), Some(String::from(expected)) );} fn assert_require_missing(fixture: &Fixture, specifier: &str, from_path: &str) { let mut resolver = fixture.resolver(); assert_eq!(resolver.resolve_require(specifier, from_path), None);} #[derive(Debug, Deserialize)]struct SharedModuleResolutionFixture { cases: Vec<SharedModuleResolutionCase>, formats: Vec<SharedModuleFormatCase>,} #[derive(Debug, Deserialize)]struct SharedModuleResolutionCase { name: String, files: std::collections::BTreeMap<String, String>, resolves: Vec<SharedModuleResolutionExpectation>,} #[derive(Debug, Deserialize)]struct SharedModuleResolutionExpectation { specifier: String, from: String, mode: String, expected: Option<String>,} #[derive(Debug, Deserialize)]struct SharedModuleFormatCase { name: String, files: std::collections::BTreeMap<String, String>, path: String, expected: Option<String>,} #[test]fn matches_shared_native_browser_conformance_fixture() { let fixture: SharedModuleResolutionFixture = serde_json::from_str(include_str!( "../../../tests/fixtures/module-resolution-conformance.json" )) .expect("parse shared module resolution fixture"); for test_case in fixture.cases { let fs_fixture = Fixture::new(); for (path, contents) in &test_case.files { fs_fixture.write(path, contents); } for resolution in &test_case.resolves { let mut resolver = fs_fixture.resolver(); let actual = match resolution.mode.as_str() { "import" => resolver.resolve_import(&resolution.specifier, &resolution.from), "require" => resolver.resolve_require(&resolution.specifier, &resolution.from), other => panic!("unsupported shared fixture mode {other}"), }; assert_eq!( actual, resolution.expected, "{}: {} from {} in {} mode", test_case.name, resolution.specifier, resolution.from, resolution.mode ); } } for format_case in fixture.formats { let fs_fixture = Fixture::new(); for (path, contents) in &format_case.files { fs_fixture.write(path, contents); } let mut resolver = fs_fixture.resolver(); let actual = resolver.module_format(&format_case.path).map(String::from); assert_eq!( actual, format_case.expected, "{}: format for {}", format_case.name, format_case.path ); }} #[test]fn builtin_bare_fs_normalizes_to_node_prefix() { let fixture = Fixture::new(); assert_import(&fixture, "fs", "/root/project/index.js", "node:fs");} #[test]fn builtin_node_prefix_is_preserved_for_require() { let fixture = Fixture::new(); assert_require(&fixture, "node:path", "/root/project/index.js", "node:path");} #[test]fn builtin_subpath_normalizes_to_node_prefix() { let fixture = Fixture::new(); assert_import( &fixture, "fs/promises", "/root/project/index.js", "node:fs/promises", );} #[test]fn relative_import_probes_js_extension() { let fixture = Fixture::new(); fixture.write("project/src/foo.js", "export default 1;"); assert_import( &fixture, "./foo", "/root/project/src/index.js", "/root/project/src/foo.js", );} #[test]fn relative_parent_import_probes_json_extension() { let fixture = Fixture::new(); fixture.write("project/data/config.json", r#"{"ok":true}"#); assert_import( &fixture, "../data/config", "/root/project/src/index.js", "/root/project/data/config.json", );} #[test]fn absolute_import_resolves_from_guest_root() { let fixture = Fixture::new(); fixture.write("shared/util.mjs", "export const ok = true;"); assert_import( &fixture, "/root/shared/util", "/root/project/src/index.js", "/root/shared/util.mjs", );} #[test]fn directory_import_uses_package_main_field() { let fixture = Fixture::new(); fixture.write_json( "project/pkg/package.json", serde_json::json!({ "main": "./dist/main.cjs" }), ); fixture.write("project/pkg/dist/main.cjs", "module.exports = 1;"); assert_require( &fixture, "./pkg", "/root/project/index.js", "/root/project/pkg/dist/main.cjs", );} #[test]fn bare_package_main_only_type_module_resolves() { // Regression guard: a bare ESM package whose package.json has `main` but NO // `exports` map (e.g. @agentclientprotocol/sdk@0.16.1) must still resolve via // the `main` fallback. `exports` and `main` are separate code paths; this case // exercises the latter for a scoped package looked up through node_modules. let fixture = Fixture::new(); fixture.write_json( "node_modules/@scope/pkg/package.json", serde_json::json!({ "type": "module", "main": "dist/acp.js" }), ); fixture.write("node_modules/@scope/pkg/dist/acp.js", "export default 1;"); assert_import( &fixture, "@scope/pkg", "/root/project/src/adapter.js", "/root/node_modules/@scope/pkg/dist/acp.js", );} #[test]fn directory_import_falls_back_to_index_file() { let fixture = Fixture::new(); fixture.write("project/lib/index.cjs", "module.exports = 1;"); assert_require( &fixture, "./lib", "/root/project/index.js", "/root/project/lib/index.cjs", );} #[test]fn extension_probe_finds_existing_js_file_directly() { let fixture = Fixture::new(); fixture.write("project/src/direct.js", "export default 1;"); assert_import( &fixture, "./direct.js", "/root/project/src/index.js", "/root/project/src/direct.js", );} #[test]fn extension_probe_finds_mjs_file() { let fixture = Fixture::new(); fixture.write("project/src/mod.mjs", "export default 1;"); assert_import( &fixture, "./mod", "/root/project/src/index.js", "/root/project/src/mod.mjs", );} #[test]fn extension_probe_finds_cjs_file() { let fixture = Fixture::new(); fixture.write("project/src/common.cjs", "module.exports = 1;"); assert_require( &fixture, "./common", "/root/project/src/index.js", "/root/project/src/common.cjs", );} #[test]fn extension_probe_finds_json_file() { let fixture = Fixture::new(); fixture.write("project/src/data.json", r#"{"name":"fixture"}"#); assert_require( &fixture, "./data", "/root/project/src/index.js", "/root/project/src/data.json", );} #[test]fn dot_specifier_resolves_current_package_directory() { let fixture = Fixture::new(); fixture.write_json( "project/pkg/package.json", serde_json::json!({ "main": "./entry.js" }), ); fixture.write("project/pkg/entry.js", "module.exports = 1;"); assert_require( &fixture, ".", "/root/project/pkg/index.js", "/root/project/pkg/entry.js", );} #[test]fn exports_string_shorthand_resolves_package_root() { let fixture = Fixture::new(); fixture.write_json( "node_modules/pkg/package.json", serde_json::json!({ "exports": "./dist/index.js" }), ); fixture.write("node_modules/pkg/dist/index.js", "export default 1;"); assert_import( &fixture, "pkg", "/root/project/src/index.js", "/root/node_modules/pkg/dist/index.js", );} #[test]fn exports_conditions_prefer_import_for_esm_resolution() { let fixture = Fixture::new(); fixture.write_json( "node_modules/pkg/package.json", serde_json::json!({ "exports": { ".": { "import": "./dist/import.mjs", "require": "./dist/require.cjs", "default": "./dist/default.js" } } }), ); fixture.write("node_modules/pkg/dist/import.mjs", "export default 1;"); fixture.write("node_modules/pkg/dist/require.cjs", "module.exports = 1;"); fixture.write("node_modules/pkg/dist/default.js", "export default 1;"); assert_import( &fixture, "pkg", "/root/project/src/index.js", "/root/node_modules/pkg/dist/import.mjs", );} #[test]fn exports_conditions_prefer_require_for_cjs_resolution() { let fixture = Fixture::new(); fixture.write_json( "node_modules/pkg/package.json", serde_json::json!({ "exports": { ".": { "import": "./dist/import.mjs", "require": "./dist/require.cjs", "default": "./dist/default.js" } } }), ); fixture.write("node_modules/pkg/dist/import.mjs", "export default 1;"); fixture.write("node_modules/pkg/dist/require.cjs", "module.exports = 1;"); fixture.write("node_modules/pkg/dist/default.js", "export default 1;"); assert_require( &fixture, "pkg", "/root/project/src/index.js", "/root/node_modules/pkg/dist/require.cjs", );} #[test]fn exports_nested_conditions_recurse_for_import_mode() { let fixture = Fixture::new(); fixture.write_json( "node_modules/pkg/package.json", serde_json::json!({ "exports": { ".": { "import": { "node": "./dist/node.mjs", "default": "./dist/default.mjs" }, "default": "./dist/fallback.js" } } }), ); fixture.write("node_modules/pkg/dist/node.mjs", "export default 1;"); fixture.write("node_modules/pkg/dist/default.mjs", "export default 1;"); fixture.write("node_modules/pkg/dist/fallback.js", "export default 1;"); assert_import( &fixture, "pkg", "/root/project/src/index.js", "/root/node_modules/pkg/dist/node.mjs", );} #[test]fn exports_wildcard_subpaths_expand_requested_segment() { let fixture = Fixture::new(); fixture.write_json( "node_modules/pkg/package.json", serde_json::json!({ "exports": { "./features/*": "./dist/features/*.mjs" } }), ); fixture.write( "node_modules/pkg/dist/features/alpha.mjs", "export default 1;", ); assert_import( &fixture, "pkg/features/alpha", "/root/project/src/index.js", "/root/node_modules/pkg/dist/features/alpha.mjs", );} #[test]fn exports_wildcard_subpaths_prefer_the_more_specific_pattern() { let fixture = Fixture::new(); fixture.write_json( "node_modules/pkg/package.json", serde_json::json!({ "exports": { "./*.js": "./esm/*.js", "./*": "./esm/*.js" } }), ); fixture.write("node_modules/pkg/esm/sdk/user.js", "export class User {};"); assert_import( &fixture, "pkg/sdk/user.js", "/root/project/src/index.js", "/root/node_modules/pkg/esm/sdk/user.js", );} #[test]fn exports_explicit_subpath_resolves_direct_mapping() { let fixture = Fixture::new(); fixture.write_json( "node_modules/pkg/package.json", serde_json::json!({ "exports": { "./feature": "./dist/feature.js" } }), ); fixture.write("node_modules/pkg/dist/feature.js", "export default 1;"); assert_import( &fixture, "pkg/feature", "/root/project/src/index.js", "/root/node_modules/pkg/dist/feature.js", );} #[test]fn exports_array_fallback_uses_first_resolvable_target() { let fixture = Fixture::new(); fixture.write_json( "node_modules/pkg/package.json", serde_json::json!({ "exports": [ null, "./dist/index.js" ] }), ); fixture.write("node_modules/pkg/dist/index.js", "export default 1;"); assert_import( &fixture, "pkg", "/root/project/src/index.js", "/root/node_modules/pkg/dist/index.js", );Lines 1–500 of 1007. Load more
use agentos_execution::javascript::ModuleResolutionTestHarness; use serde::Deserialize; use serde_json::Value; use std::fs; use std::os::unix::fs::symlink; use std::path::PathBuf; use tempfile::TempDir; struct Fixture { temp: TempDir, } impl Fixture { fn new() -> Self { Self { temp: TempDir::new().expect("create temp dir"), } } fn host_path(&self, relative: &str) -> PathBuf { self.temp.path().join(relative) } fn write(&self, relative: &str, contents: &str) { let path = self.host_path(relative); if let Some(parent) = path.parent() { fs::create_dir_all(parent).expect("create parent dirs"); } fs::write(path, contents).expect("write fixture file"); } fn write_json(&self, relative: &str, value: Value) { self.write( relative, &serde_json::to_string_pretty(&value).expect("serialize json"), ); } fn mkdir(&self, relative: &str) { fs::create_dir_all(self.host_path(relative)).expect("create fixture dir"); } fn symlink_dir(&self, target_relative: &str, link_relative: &str) { let target = self.host_path(target_relative); let link = self.host_path(link_relative); if let Some(parent) = link.parent() { fs::create_dir_all(parent).expect("create symlink parent"); } symlink(target, link).expect("create directory symlink"); } fn resolver(&self) -> ModuleResolutionTestHarness { ModuleResolutionTestHarness::new(self.temp.path()) } } fn assert_import(fixture: &Fixture, specifier: &str, from_path: &str, expected: &str) { let mut resolver = fixture.resolver(); assert_eq!( resolver.resolve_import(specifier, from_path), Some(String::from(expected)) ); } fn assert_require(fixture: &Fixture, specifier: &str, from_path: &str, expected: &str) { let mut resolver = fixture.resolver(); assert_eq!( resolver.resolve_require(specifier, from_path), Some(String::from(expected)) ); } fn assert_require_missing(fixture: &Fixture, specifier: &str, from_path: &str) { let mut resolver = fixture.resolver(); assert_eq!(resolver.resolve_require(specifier, from_path), None); } #[derive(Debug, Deserialize)] struct SharedModuleResolutionFixture { cases: Vec<SharedModuleResolutionCase>, formats: Vec<SharedModuleFormatCase>, } #[derive(Debug, Deserialize)] struct SharedModuleResolutionCase { name: String, files: std::collections::BTreeMap<String, String>, resolves: Vec<SharedModuleResolutionExpectation>, } #[derive(Debug, Deserialize)] struct SharedModuleResolutionExpectation { specifier: String, from: String, mode: String, expected: Option<String>, } #[derive(Debug, Deserialize)] struct SharedModuleFormatCase { name: String, files: std::collections::BTreeMap<String, String>, path: String, expected: Option<String>, } #[test] fn matches_shared_native_browser_conformance_fixture() { let fixture: SharedModuleResolutionFixture = serde_json::from_str(include_str!( "../../../tests/fixtures/module-resolution-conformance.json" )) .expect("parse shared module resolution fixture"); for test_case in fixture.cases { let fs_fixture = Fixture::new(); for (path, contents) in &test_case.files { fs_fixture.write(path, contents); } for resolution in &test_case.resolves { let mut resolver = fs_fixture.resolver(); let actual = match resolution.mode.as_str() { "import" => resolver.resolve_import(&resolution.specifier, &resolution.from), "require" => resolver.resolve_require(&resolution.specifier, &resolution.from), other => panic!("unsupported shared fixture mode {other}"), }; assert_eq!( actual, resolution.expected, "{}: {} from {} in {} mode", test_case.name, resolution.specifier, resolution.from, resolution.mode ); } } for format_case in fixture.formats { let fs_fixture = Fixture::new(); for (path, contents) in &format_case.files { fs_fixture.write(path, contents); } let mut resolver = fs_fixture.resolver(); let actual = resolver.module_format(&format_case.path).map(String::from); assert_eq!( actual, format_case.expected, "{}: format for {}", format_case.name, format_case.path ); } } #[test] fn builtin_bare_fs_normalizes_to_node_prefix() { let fixture = Fixture::new(); assert_import(&fixture, "fs", "/root/project/index.js", "node:fs"); } #[test] fn builtin_node_prefix_is_preserved_for_require() { let fixture = Fixture::new(); assert_require(&fixture, "node:path", "/root/project/index.js", "node:path"); } #[test] fn builtin_subpath_normalizes_to_node_prefix() { let fixture = Fixture::new(); assert_import( &fixture, "fs/promises", "/root/project/index.js", "node:fs/promises", ); } #[test] fn relative_import_probes_js_extension() { let fixture = Fixture::new(); fixture.write("project/src/foo.js", "export default 1;"); assert_import( &fixture, "./foo", "/root/project/src/index.js", "/root/project/src/foo.js", ); } #[test] fn relative_parent_import_probes_json_extension() { let fixture = Fixture::new(); fixture.write("project/data/config.json", r#"{"ok":true}"#); assert_import( &fixture, "../data/config", "/root/project/src/index.js", "/root/project/data/config.json", ); } #[test] fn absolute_import_resolves_from_guest_root() { let fixture = Fixture::new(); fixture.write("shared/util.mjs", "export const ok = true;"); assert_import( &fixture, "/root/shared/util", "/root/project/src/index.js", "/root/shared/util.mjs", ); } #[test] fn directory_import_uses_package_main_field() { let fixture = Fixture::new(); fixture.write_json( "project/pkg/package.json", serde_json::json!({ "main": "./dist/main.cjs" }), ); fixture.write("project/pkg/dist/main.cjs", "module.exports = 1;"); assert_require( &fixture, "./pkg", "/root/project/index.js", "/root/project/pkg/dist/main.cjs", ); } #[test] fn bare_package_main_only_type_module_resolves() { // Regression guard: a bare ESM package whose package.json has `main` but NO // `exports` map (e.g. @agentclientprotocol/sdk@0.16.1) must still resolve via // the `main` fallback. `exports` and `main` are separate code paths; this case // exercises the latter for a scoped package looked up through node_modules. let fixture = Fixture::new(); fixture.write_json( "node_modules/@scope/pkg/package.json", serde_json::json!({ "type": "module", "main": "dist/acp.js" }), ); fixture.write("node_modules/@scope/pkg/dist/acp.js", "export default 1;"); assert_import( &fixture, "@scope/pkg", "/root/project/src/adapter.js", "/root/node_modules/@scope/pkg/dist/acp.js", ); } #[test] fn directory_import_falls_back_to_index_file() { let fixture = Fixture::new(); fixture.write("project/lib/index.cjs", "module.exports = 1;"); assert_require( &fixture, "./lib", "/root/project/index.js", "/root/project/lib/index.cjs", ); } #[test] fn extension_probe_finds_existing_js_file_directly() { let fixture = Fixture::new(); fixture.write("project/src/direct.js", "export default 1;"); assert_import( &fixture, "./direct.js", "/root/project/src/index.js", "/root/project/src/direct.js", ); } #[test] fn extension_probe_finds_mjs_file() { let fixture = Fixture::new(); fixture.write("project/src/mod.mjs", "export default 1;"); assert_import( &fixture, "./mod", "/root/project/src/index.js", "/root/project/src/mod.mjs", ); } #[test] fn extension_probe_finds_cjs_file() { let fixture = Fixture::new(); fixture.write("project/src/common.cjs", "module.exports = 1;"); assert_require( &fixture, "./common", "/root/project/src/index.js", "/root/project/src/common.cjs", ); } #[test] fn extension_probe_finds_json_file() { let fixture = Fixture::new(); fixture.write("project/src/data.json", r#"{"name":"fixture"}"#); assert_require( &fixture, "./data", "/root/project/src/index.js", "/root/project/src/data.json", ); } #[test] fn dot_specifier_resolves_current_package_directory() { let fixture = Fixture::new(); fixture.write_json( "project/pkg/package.json", serde_json::json!({ "main": "./entry.js" }), ); fixture.write("project/pkg/entry.js", "module.exports = 1;"); assert_require( &fixture, ".", "/root/project/pkg/index.js", "/root/project/pkg/entry.js", ); } #[test] fn exports_string_shorthand_resolves_package_root() { let fixture = Fixture::new(); fixture.write_json( "node_modules/pkg/package.json", serde_json::json!({ "exports": "./dist/index.js" }), ); fixture.write("node_modules/pkg/dist/index.js", "export default 1;"); assert_import( &fixture, "pkg", "/root/project/src/index.js", "/root/node_modules/pkg/dist/index.js", ); } #[test] fn exports_conditions_prefer_import_for_esm_resolution() { let fixture = Fixture::new(); fixture.write_json( "node_modules/pkg/package.json", serde_json::json!({ "exports": { ".": { "import": "./dist/import.mjs", "require": "./dist/require.cjs", "default": "./dist/default.js" } } }), ); fixture.write("node_modules/pkg/dist/import.mjs", "export default 1;"); fixture.write("node_modules/pkg/dist/require.cjs", "module.exports = 1;"); fixture.write("node_modules/pkg/dist/default.js", "export default 1;"); assert_import( &fixture, "pkg", "/root/project/src/index.js", "/root/node_modules/pkg/dist/import.mjs", ); } #[test] fn exports_conditions_prefer_require_for_cjs_resolution() { let fixture = Fixture::new(); fixture.write_json( "node_modules/pkg/package.json", serde_json::json!({ "exports": { ".": { "import": "./dist/import.mjs", "require": "./dist/require.cjs", "default": "./dist/default.js" } } }), ); fixture.write("node_modules/pkg/dist/import.mjs", "export default 1;"); fixture.write("node_modules/pkg/dist/require.cjs", "module.exports = 1;"); fixture.write("node_modules/pkg/dist/default.js", "export default 1;"); assert_require( &fixture, "pkg", "/root/project/src/index.js", "/root/node_modules/pkg/dist/require.cjs", ); } #[test] fn exports_nested_conditions_recurse_for_import_mode() { let fixture = Fixture::new(); fixture.write_json( "node_modules/pkg/package.json", serde_json::json!({ "exports": { ".": { "import": { "node": "./dist/node.mjs", "default": "./dist/default.mjs" }, "default": "./dist/fallback.js" } } }), ); fixture.write("node_modules/pkg/dist/node.mjs", "export default 1;"); fixture.write("node_modules/pkg/dist/default.mjs", "export default 1;"); fixture.write("node_modules/pkg/dist/fallback.js", "export default 1;"); assert_import( &fixture, "pkg", "/root/project/src/index.js", "/root/node_modules/pkg/dist/node.mjs", ); } #[test] fn exports_wildcard_subpaths_expand_requested_segment() { let fixture = Fixture::new(); fixture.write_json( "node_modules/pkg/package.json", serde_json::json!({ "exports": { "./features/*": "./dist/features/*.mjs" } }), ); fixture.write( "node_modules/pkg/dist/features/alpha.mjs", "export default 1;", ); assert_import( &fixture, "pkg/features/alpha", "/root/project/src/index.js", "/root/node_modules/pkg/dist/features/alpha.mjs", ); } #[test] fn exports_wildcard_subpaths_prefer_the_more_specific_pattern() { let fixture = Fixture::new(); fixture.write_json( "node_modules/pkg/package.json", serde_json::json!({ "exports": { "./*.js": "./esm/*.js", "./*": "./esm/*.js" } }), ); fixture.write("node_modules/pkg/esm/sdk/user.js", "export class User {};"); assert_import( &fixture, "pkg/sdk/user.js", "/root/project/src/index.js", "/root/node_modules/pkg/esm/sdk/user.js", ); } #[test] fn exports_explicit_subpath_resolves_direct_mapping() { let fixture = Fixture::new(); fixture.write_json( "node_modules/pkg/package.json", serde_json::json!({ "exports": { "./feature": "./dist/feature.js" } }), ); fixture.write("node_modules/pkg/dist/feature.js", "export default 1;"); assert_import( &fixture, "pkg/feature", "/root/project/src/index.js", "/root/node_modules/pkg/dist/feature.js", ); } #[test] fn exports_array_fallback_uses_first_resolvable_target() { let fixture = Fixture::new(); fixture.write_json( "node_modules/pkg/package.json", serde_json::json!({ "exports": [ null, "./dist/index.js" ] }), ); fixture.write("node_modules/pkg/dist/index.js", "export default 1;"); assert_import( &fixture, "pkg", "/root/project/src/index.js", "/root/node_modules/pkg/dist/index.js", );