use crate::error::CoreError;
const PROJECT_MARKER: &str = "__IGN_PROJECT__";
const RUN_PROJECT_MARKER: &str = "__IGN_RUN_PROJECT__";
const GATEWAY_URL_MARKER: &str = "__IGN_GATEWAY_URL__";
pub const TEMPLATE_SUFFIX: &str = ".tmpl";
pub const TEMPLATE_MARKER_COUNT: usize = 11;
pub const E2E_TEMPLATE_FILES: &[(&str, &str)] = &[
(
"package.json",
include_str!("../../e2e-template/package.json.tmpl"),
),
(
"playwright.config.mjs",
include_str!("../../e2e-template/playwright.config.mjs.tmpl"),
),
(
"global-setup.mjs",
include_str!("../../e2e-template/global-setup.mjs.tmpl"),
),
(
"lib/gateway.mjs",
include_str!("../../e2e-template/lib/gateway.mjs.tmpl"),
),
(
"tests/example.spec.mjs",
include_str!("../../e2e-template/tests/example.spec.mjs.tmpl"),
),
(
".gitignore",
include_str!("../../e2e-template/gitignore.tmpl"),
),
(
"README.md",
include_str!("../../e2e-template/README.md.tmpl"),
),
];
pub fn render_template(body: &str, project: &str, run_project: &str, gateway_url: &str) -> String {
body.replace(RUN_PROJECT_MARKER, run_project)
.replace(PROJECT_MARKER, project)
.replace(GATEWAY_URL_MARKER, gateway_url)
}
pub fn member_paths() -> Vec<&'static str> {
E2E_TEMPLATE_FILES.iter().map(|(path, _)| *path).collect()
}
pub fn validate_member_paths() -> Result<(), CoreError> {
for (path, _) in E2E_TEMPLATE_FILES {
let p = std::path::Path::new(path);
if p.is_absolute()
|| p.components()
.any(|c| matches!(c, std::path::Component::ParentDir))
{
return Err(CoreError::Internal(format!(
"e2e template member {path:?} is not a relative in-tree path"
)));
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn every_marker_is_substituted() {
let total: usize = E2E_TEMPLATE_FILES
.iter()
.map(|(_, body)| {
body.matches(PROJECT_MARKER).count()
+ body.matches(RUN_PROJECT_MARKER).count()
+ body.matches(GATEWAY_URL_MARKER).count()
})
.sum();
assert_eq!(
total, TEMPLATE_MARKER_COUNT,
"a template edit moved the marker count — update TEMPLATE_MARKER_COUNT \
deliberately, or restore the marker that was dropped"
);
for (path, body) in E2E_TEMPLATE_FILES {
let rendered = render_template(body, "ign-cli", "Flux", "https://gw:8043");
for marker in [PROJECT_MARKER, RUN_PROJECT_MARKER, GATEWAY_URL_MARKER] {
assert!(
!rendered.contains(marker),
"{path} still carries {marker} after rendering"
);
}
}
}
#[test]
fn no_member_path_keeps_the_template_suffix() {
for path in member_paths() {
assert!(
!path.ends_with(TEMPLATE_SUFFIX),
"member {path:?} would be written with the template suffix"
);
}
assert_eq!(member_paths().len(), 7, "seven members");
assert!(
member_paths().contains(&".gitignore"),
"gitignore.tmpl maps to the DOTTED name"
);
validate_member_paths().expect("every member is a relative in-tree path");
}
#[test]
fn the_localhost_guard_agrees_across_config_and_helper() {
let render = |name: &str| {
let (_, body) = E2E_TEMPLATE_FILES
.iter()
.find(|(path, _)| *path == name)
.unwrap_or_else(|| panic!("no {name} member"));
render_template(body, "ign-cli", "Flux", "http://localhost:9088")
};
let config = render("playwright.config.mjs");
let helper = render("lib/gateway.mjs");
for token in [
"'localhost'",
"'127.0.0.1'",
"'::1'",
"IGNITION_ALLOW_INSECURE_LOCAL",
] {
assert!(
config.contains(token),
"playwright.config.mjs lost the guard token {token}"
);
assert!(
helper.contains(token),
"lib/gateway.mjs lost the guard token {token}"
);
}
assert!(
helper.contains("NODE_TLS_REJECT_UNAUTHORIZED"),
"the helper applies the relaxation"
);
assert!(
helper.contains("NODE_EXTRA_CA_CERTS"),
"the helper names the correct remote-gateway path"
);
assert!(
helper.contains("process.stderr.write"),
"the relaxation is announced on stderr, never silent"
);
}
#[test]
fn the_scaffold_gitignores_its_session_state() {
let (_, body) = E2E_TEMPLATE_FILES
.iter()
.find(|(path, _)| *path == ".gitignore")
.expect("the gitignore member");
assert!(body.contains(".auth/"), "the auth directory is ignored");
assert!(body.contains("node_modules/"));
}
}