diff --git a/Cargo.lock b/Cargo.lock index 9ba2b2d76c..9025f216b1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1416,6 +1416,24 @@ version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" +[[package]] +name = "deadpool" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0be2b1d1d6ec8d846f05e137292d0b89133caf95ef33695424c09568bdd39b1b" +dependencies = [ + "deadpool-runtime", + "lazy_static", + "num_cpus", + "tokio", +] + +[[package]] +name = "deadpool-runtime" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "092966b41edc516079bdf31ec78a2e0588d1d0c08f78b91d8307215928642b2b" + [[package]] name = "decoded-char" version = "0.1.1" @@ -8101,6 +8119,7 @@ dependencies = [ "thiserror 2.0.16", "tokio", "url", + "wiremock", "xx", ] @@ -8839,6 +8858,29 @@ version = "0.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" +[[package]] +name = "wiremock" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08db1edfb05d9b3c1542e521aea074442088292f00b5f28e435c714a98f85031" +dependencies = [ + "assert-json-diff", + "base64 0.22.1", + "deadpool", + "futures", + "http", + "http-body-util", + "hyper", + "hyper-util", + "log", + "once_cell", + "regex", + "serde", + "serde_json", + "tokio", + "url", +] + [[package]] name = "wit-bindgen" version = "0.46.0" diff --git a/crates/vfox/Cargo.toml b/crates/vfox/Cargo.toml index 9b7d579358..84346cf863 100644 --- a/crates/vfox/Cargo.toml +++ b/crates/vfox/Cargo.toml @@ -47,6 +47,7 @@ tempfile = "3" [dev-dependencies] insta = "1" +wiremock = "0.6" #pretty_assertions = "1.4.0" [features] diff --git a/crates/vfox/src/lua_mod/archiver.rs b/crates/vfox/src/lua_mod/archiver.rs index 259c05e118..79f9509c1a 100644 --- a/crates/vfox/src/lua_mod/archiver.rs +++ b/crates/vfox/src/lua_mod/archiver.rs @@ -41,19 +41,21 @@ mod tests { #[test] fn test_zip() { - let _ = std::fs::remove_dir_all("/tmp/test_zip_dst"); + let temp_dir = tempfile::TempDir::new().unwrap(); + let dst_path = temp_dir.path().join("unzip"); + let dst_path_str = dst_path.to_string_lossy().to_string(); let lua = Lua::new(); mod_archiver(&lua).unwrap(); lua.load(mlua::chunk! { local archiver = require("archiver") - archiver.decompress("test/data/foo.zip", "/tmp/test_zip_dst") + archiver.decompress("test/data/foo.zip", $dst_path_str) }) .exec() .unwrap(); assert_eq!( - std::fs::read_to_string("/tmp/test_zip_dst/foo/test.txt").unwrap(), + std::fs::read_to_string(dst_path.join("foo/test.txt")).unwrap(), "yep\n" ); - std::fs::remove_dir_all("/tmp/test_zip_dst").unwrap(); + // TempDir automatically cleans up when dropped } } diff --git a/crates/vfox/src/lua_mod/cmd.rs b/crates/vfox/src/lua_mod/cmd.rs index 1f4a523360..afffa9f07f 100644 --- a/crates/vfox/src/lua_mod/cmd.rs +++ b/crates/vfox/src/lua_mod/cmd.rs @@ -102,16 +102,26 @@ mod tests { #[test] fn test_cmd_with_cwd() { + let temp_dir = tempfile::TempDir::new().unwrap(); + let temp_path = temp_dir.path(); + // Canonicalize to resolve symlinks (e.g., /var -> /private/var on macOS) + let temp_path_canonical = temp_path + .canonicalize() + .unwrap_or_else(|_| temp_path.to_path_buf()); + let temp_dir_str = temp_path_canonical.to_string_lossy().to_string(); + let expected_path = temp_dir_str.trim_end_matches('/').to_string(); let lua = Lua::new(); mod_cmd(&lua).unwrap(); lua.load(mlua::chunk! { local cmd = require("cmd") -- Test with working directory - local result = cmd.exec("pwd", {cwd = "/tmp"}) - assert(result:find("/tmp") ~= nil) + local result = cmd.exec("pwd", {cwd = $temp_dir_str}) + -- Check that result contains the expected path (handles trailing slashes/newlines) + assert(result:find($expected_path) ~= nil, "Expected result to contain: " .. $expected_path .. " but got: " .. result) }) .exec() .unwrap(); + // TempDir automatically cleans up when dropped } #[test] diff --git a/crates/vfox/src/lua_mod/file.rs b/crates/vfox/src/lua_mod/file.rs index 34c30ab601..54625fe5b1 100644 --- a/crates/vfox/src/lua_mod/file.rs +++ b/crates/vfox/src/lua_mod/file.rs @@ -79,13 +79,15 @@ mod tests { #[test] fn test_read() { - let filepath = "/tmp/vfox-lua-file-read"; - fs::write(filepath, "hello world").unwrap(); + let temp_dir = tempfile::TempDir::new().unwrap(); + let filepath = temp_dir.path().join("file-read.txt"); + let filepath_str = filepath.to_string_lossy().to_string(); + fs::write(&filepath, "hello world").unwrap(); let lua = Lua::new(); mod_file(&lua).unwrap(); lua.load(mlua::chunk! { local file = require("file") - local success, contents = pcall(file.read, "/tmp/vfox-lua-file-read") + local success, contents = pcall(file.read, $filepath_str) if not success then error("Failed to read: " .. contents) end @@ -97,24 +99,25 @@ mod tests { }) .exec() .unwrap(); - fs::remove_file(filepath).unwrap(); + // TempDir automatically cleans up when dropped } #[test] fn test_symlink() { - let _ = fs::remove_file("/tmp/test_symlink_dst"); + let temp_dir = tempfile::TempDir::new().unwrap(); + let src_path = temp_dir.path().join("symlink_src"); + let dst_path = temp_dir.path().join("symlink_dst"); + let src_path_str = src_path.to_string_lossy().to_string(); + let dst_path_str = dst_path.to_string_lossy().to_string(); let lua = Lua::new(); mod_file(&lua).unwrap(); lua.load(mlua::chunk! { local file = require("file") - file.symlink("/tmp/test_symlink_src", "/tmp/test_symlink_dst") + file.symlink($src_path_str, $dst_path_str) }) .exec() .unwrap(); - assert_eq!( - fs::read_link("/tmp/test_symlink_dst").unwrap(), - Path::new("/tmp/test_symlink_src") - ); - fs::remove_file("/tmp/test_symlink_dst").unwrap(); + assert_eq!(fs::read_link(&dst_path).unwrap(), src_path); + // TempDir automatically cleans up when dropped } } diff --git a/crates/vfox/src/lua_mod/http.rs b/crates/vfox/src/lua_mod/http.rs index 47cf5fe00e..8811e06a80 100644 --- a/crates/vfox/src/lua_mod/http.rs +++ b/crates/vfox/src/lua_mod/http.rs @@ -75,14 +75,34 @@ fn get_headers(lua: &Lua, headers: &reqwest::header::HeaderMap) -> Result mod tests { use super::*; use std::fs; + use wiremock::matchers::{method, path}; + use wiremock::{Mock, MockServer, ResponseTemplate}; #[tokio::test] async fn test_get() { + // Start a local mock server + let server = MockServer::start().await; + + // Create a mock endpoint + Mock::given(method("GET")) + .and(path("/get")) + .respond_with( + ResponseTemplate::new(200) + .set_body_json(serde_json::json!({ + "message": "test response" + })) + .insert_header("content-type", "application/json"), + ) + .mount(&server) + .await; + let lua = Lua::new(); mod_http(&lua).unwrap(); + + let url = server.uri() + "/get"; lua.load(mlua::chunk! { local http = require("http") - local resp = http.get({ url = "https://httpbin.org/get" }) + local resp = http.get({ url = $url }) assert(resp.status_code == 200) assert(type(resp.body) == "string") }) @@ -93,15 +113,29 @@ mod tests { #[tokio::test] async fn test_head() { + let server = MockServer::start().await; + + Mock::given(method("HEAD")) + .and(path("/get")) + .respond_with( + ResponseTemplate::new(200) + .insert_header("content-type", "application/json") + .insert_header("x-test-header", "test-value"), + ) + .mount(&server) + .await; + let lua = Lua::new(); mod_http(&lua).unwrap(); + + let url = server.uri() + "/get"; lua.load(mlua::chunk! { local http = require("http") - local resp = http.head({ url = "https://httpbin.org/get" }) - print(resp.headers) + local resp = http.head({ url = $url }) assert(resp.status_code == 200) assert(type(resp.headers) == "table") assert(resp.headers["content-type"] == "application/json") + assert(resp.headers["x-test-header"] == "test-value") assert(resp.content_length == nil) }) .exec_async() @@ -110,24 +144,47 @@ mod tests { } #[tokio::test] - #[ignore] // TODO: find out why this often fails in CI async fn test_download_file() { + let server = MockServer::start().await; + + // Create test content + let test_content = r#"{"name": "vfox-nodejs", "version": "1.0.0"}"#; + + Mock::given(method("GET")) + .and(path("/index.json")) + .respond_with( + ResponseTemplate::new(200) + .set_body_string(test_content) + .insert_header("content-type", "application/json"), + ) + .mount(&server) + .await; + let lua = Lua::new(); mod_http(&lua).unwrap(); - let path = "test/data/test_download_file.txt"; + + // Use isolated temp directory for test isolation + let temp_dir = tempfile::TempDir::new().unwrap(); + let path = temp_dir.path().join("download_file.txt"); + let path_str = path.to_string_lossy().to_string(); + let url = server.uri() + "/index.json"; + lua.load(mlua::chunk! { local http = require("http") err = http.download_file({ - url = "https://vfox-plugins.lhan.me/index.json", + url = $url, headers = {} - }, $path) + }, $path_str) assert(err == nil, [[must be nil]]) }) .exec_async() .await .unwrap(); - // TODO: figure out why this fails on gha - assert!(fs::read_to_string(path).unwrap().contains("vfox-nodejs")); - tokio::fs::remove_file(path).await.unwrap(); + + // Verify file was downloaded correctly + let content = fs::read_to_string(&path).unwrap(); + assert!(content.contains("vfox-nodejs")); + + // TempDir automatically cleans up when dropped } }