578f8019fc
项目页只保留打开项目和新建项目并统一原生目录选择 按根目录及一层子目录识别project.godot并记录相对Godot根 保持.agent位于用户选择的工作区根并补齐Windows构建门禁 补充响应式布局、测试、技术方案和共享项目记忆
142 lines
4.8 KiB
Rust
142 lines
4.8 KiB
Rust
#[path = "../build_support/frontend_dist_guard.rs"]
|
||
mod frontend_dist_guard;
|
||
|
||
use frontend_dist_guard::validate_frontend_dist;
|
||
use std::fs;
|
||
use std::path::{Path, PathBuf};
|
||
use std::sync::atomic::{AtomicU64, Ordering};
|
||
|
||
static TEST_DIRECTORY_SEQUENCE: AtomicU64 = AtomicU64::new(0);
|
||
|
||
struct TestDirectory {
|
||
path: PathBuf,
|
||
}
|
||
|
||
impl TestDirectory {
|
||
fn new(label: &str) -> Self {
|
||
let sequence = TEST_DIRECTORY_SEQUENCE.fetch_add(1, Ordering::Relaxed);
|
||
let path = std::env::temp_dir().join(format!(
|
||
"genarrative-frontend-dist-{label}-{}-{sequence}",
|
||
std::process::id()
|
||
));
|
||
fs::create_dir(&path).expect("create frontend dist test directory");
|
||
Self { path }
|
||
}
|
||
|
||
fn path(&self) -> &Path {
|
||
&self.path
|
||
}
|
||
}
|
||
|
||
impl Drop for TestDirectory {
|
||
fn drop(&mut self) {
|
||
let _ = fs::remove_dir_all(&self.path);
|
||
}
|
||
}
|
||
|
||
fn write_file(path: &Path, content: impl AsRef<[u8]>) {
|
||
if let Some(parent) = path.parent() {
|
||
fs::create_dir_all(parent).expect("create frontend dist fixture parent");
|
||
}
|
||
fs::write(path, content).expect("write frontend dist fixture");
|
||
}
|
||
|
||
#[test]
|
||
fn clean_frontend_dist_passes() {
|
||
let fixture = TestDirectory::new("clean");
|
||
let dist = fixture.path().join("dist");
|
||
write_file(&dist.join("index.html"), "<main>GameAgent</main>");
|
||
write_file(
|
||
&dist.join("assets/index.js"),
|
||
"const defaultProjectPath = '';",
|
||
);
|
||
|
||
validate_frontend_dist(&dist).expect("clean frontendDist should pass");
|
||
}
|
||
|
||
#[test]
|
||
fn nested_frontend_asset_with_test_default_path_fails_with_relative_path() {
|
||
let fixture = TestDirectory::new("marker");
|
||
let dist = fixture.path().join("dist");
|
||
write_file(&dist.join("index.html"), "<main>GameAgent</main>");
|
||
write_file(
|
||
&dist.join("assets/chunks/launcher.js"),
|
||
"const projectPath = '/tmp/genarrative-ai-game-draft';",
|
||
);
|
||
|
||
let error = validate_frontend_dist(&dist)
|
||
.expect_err("production frontendDist must reject the test default path");
|
||
let portable_error = error.replace('\\', "/");
|
||
assert!(error.contains("/tmp/genarrative-ai-game-draft"));
|
||
assert!(portable_error.contains("assets/chunks/launcher.js"));
|
||
assert!(!error.contains(&dist.display().to_string()));
|
||
}
|
||
|
||
#[test]
|
||
fn test_fixture_outside_frontend_dist_does_not_fail_the_guard() {
|
||
let fixture = TestDirectory::new("fixture-scope");
|
||
let dist = fixture.path().join("dist");
|
||
write_file(&dist.join("assets/index.js"), "const projectPath = '';");
|
||
write_file(
|
||
&fixture.path().join("tests/project-path.fixture.txt"),
|
||
"/tmp/genarrative-ai-game-draft",
|
||
);
|
||
|
||
validate_frontend_dist(&dist)
|
||
.expect("explicit test fixtures outside frontendDist must remain allowed");
|
||
}
|
||
|
||
#[test]
|
||
fn missing_frontend_dist_fails_without_leaking_an_absolute_path() {
|
||
let fixture = TestDirectory::new("missing");
|
||
let dist = fixture.path().join("dist");
|
||
|
||
let error = validate_frontend_dist(&dist)
|
||
.expect_err("production frontendDist must exist before it can be embedded");
|
||
assert!(error.contains("frontendDist"));
|
||
assert!(error.contains(":."));
|
||
assert!(!error.contains(&dist.display().to_string()));
|
||
}
|
||
|
||
#[cfg(unix)]
|
||
#[test]
|
||
fn frontend_dist_symlink_fails_with_relative_path() {
|
||
use std::os::unix::fs::symlink;
|
||
|
||
let fixture = TestDirectory::new("symlink");
|
||
let dist = fixture.path().join("dist");
|
||
let target = fixture.path().join("fixture.js");
|
||
write_file(&target, "const projectPath = '';");
|
||
fs::create_dir_all(dist.join("assets")).expect("create symlink fixture parent");
|
||
symlink(&target, dist.join("assets/linked.js")).expect("create frontend dist symlink");
|
||
|
||
let error =
|
||
validate_frontend_dist(&dist).expect_err("production frontendDist must reject symlinks");
|
||
let portable_error = error.replace('\\', "/");
|
||
assert!(error.contains("符号链接"));
|
||
assert!(portable_error.contains("assets/linked.js"));
|
||
assert!(!error.contains(&dist.display().to_string()));
|
||
}
|
||
|
||
#[cfg(windows)]
|
||
#[test]
|
||
fn frontend_dist_symlink_fails_when_windows_allows_creating_it() {
|
||
use std::os::windows::fs::symlink_file;
|
||
|
||
let fixture = TestDirectory::new("windows-symlink");
|
||
let dist = fixture.path().join("dist");
|
||
let target = fixture.path().join("fixture.js");
|
||
write_file(&target, "const projectPath = '';");
|
||
fs::create_dir_all(dist.join("assets")).expect("create symlink fixture parent");
|
||
if symlink_file(&target, dist.join("assets/linked.js")).is_err() {
|
||
return;
|
||
}
|
||
|
||
let error = validate_frontend_dist(&dist)
|
||
.expect_err("production frontendDist must reject Windows symlinks");
|
||
let portable_error = error.replace('\\', "/");
|
||
assert!(error.contains("符号链接"));
|
||
assert!(portable_error.contains("assets/linked.js"));
|
||
assert!(!error.contains(&dist.display().to_string()));
|
||
}
|