pub const ROUTE_BUNDLE_VERSION: &str = "1.3.0";
pub const MIN_CLI: &str = "1.0";
pub const ROUTE_FILES: &[(&str, &str)] = &[
(
"project.json",
include_str!("../../webdev/routes/project.json"),
),
(
"com.inductiveautomation.webdev/resources/cli/tags/resource.json",
include_str!(
"../../webdev/routes/com.inductiveautomation.webdev/resources/cli/tags/resource.json"
),
),
(
"com.inductiveautomation.webdev/resources/cli/tags/config.json",
include_str!(
"../../webdev/routes/com.inductiveautomation.webdev/resources/cli/tags/config.json"
),
),
(
"com.inductiveautomation.webdev/resources/cli/tags/doPost.py",
include_str!(
"../../webdev/routes/com.inductiveautomation.webdev/resources/cli/tags/doPost.py"
),
),
(
"com.inductiveautomation.webdev/resources/cli/tagConfig/resource.json",
include_str!(
"../../webdev/routes/com.inductiveautomation.webdev/resources/cli/tagConfig/resource.json"
),
),
(
"com.inductiveautomation.webdev/resources/cli/tagConfig/config.json",
include_str!(
"../../webdev/routes/com.inductiveautomation.webdev/resources/cli/tagConfig/config.json"
),
),
(
"com.inductiveautomation.webdev/resources/cli/tagConfig/doPost.py",
include_str!(
"../../webdev/routes/com.inductiveautomation.webdev/resources/cli/tagConfig/doPost.py"
),
),
(
"com.inductiveautomation.webdev/resources/cli/alarms/resource.json",
include_str!(
"../../webdev/routes/com.inductiveautomation.webdev/resources/cli/alarms/resource.json"
),
),
(
"com.inductiveautomation.webdev/resources/cli/alarms/config.json",
include_str!(
"../../webdev/routes/com.inductiveautomation.webdev/resources/cli/alarms/config.json"
),
),
(
"com.inductiveautomation.webdev/resources/cli/alarms/doPost.py",
include_str!(
"../../webdev/routes/com.inductiveautomation.webdev/resources/cli/alarms/doPost.py"
),
),
(
"com.inductiveautomation.webdev/resources/cli/tagHistory/resource.json",
include_str!(
"../../webdev/routes/com.inductiveautomation.webdev/resources/cli/tagHistory/resource.json"
),
),
(
"com.inductiveautomation.webdev/resources/cli/tagHistory/config.json",
include_str!(
"../../webdev/routes/com.inductiveautomation.webdev/resources/cli/tagHistory/config.json"
),
),
(
"com.inductiveautomation.webdev/resources/cli/tagHistory/doPost.py",
include_str!(
"../../webdev/routes/com.inductiveautomation.webdev/resources/cli/tagHistory/doPost.py"
),
),
];
pub const SCRIPT_EXEC_TEMPLATE: &str = include_str!(
"../../webdev/routes/com.inductiveautomation.webdev/resources/cli/scriptExec/doPost.py"
);
#[cfg(test)]
mod tests {
use super::*;
const VERSION_FILE: &str = include_str!("../../webdev/routes/VERSION");
#[test]
fn route_sources_carry_the_embedded_handshake_constants() {
let route_version = format!("ROUTE_VERSION = '{}'", ROUTE_BUNDLE_VERSION);
let min_cli = format!("MIN_CLI = '{}'", MIN_CLI);
let mut do_post_count = 0;
for (name, contents) in ROUTE_FILES {
if name.ends_with("doPost.py") {
do_post_count += 1;
assert!(
contents.contains(&route_version),
"{name}: missing {route_version}"
);
assert!(contents.contains(&min_cli), "{name}: missing {min_cli}");
}
}
assert_eq!(do_post_count, 4, "expected the four always-on dispatchers");
assert_eq!(
VERSION_FILE.trim(),
ROUTE_BUNDLE_VERSION,
"webdev/routes/VERSION drifted from ROUTE_BUNDLE_VERSION"
);
}
#[test]
fn always_on_bundle_carries_no_secret_placeholder() {
for (name, contents) in ROUTE_FILES {
assert!(
!contents.contains("__IGN_CLI_SECRET__"),
"{name}: the secret placeholder must not ship in the always-on bundle"
);
}
}
#[test]
fn script_exec_template_is_substitutable_and_fail_closed() {
assert_eq!(
SCRIPT_EXEC_TEMPLATE.matches("__IGN_CLI_SECRET__").count(),
1,
"the deploy substitution needs exactly one marker occurrence"
);
assert!(
SCRIPT_EXEC_TEMPLATE.contains("SECRET = None"),
"the template must keep its fail-closed SECRET default"
);
}
#[test]
fn member_names_use_forward_slashes_only() {
for (name, _) in ROUTE_FILES {
assert!(!name.contains('\\'), "backslash in member name: {name}");
}
}
#[test]
fn manifest_lists_exactly_thirteen_members() {
assert_eq!(ROUTE_FILES.len(), 13);
assert_eq!(
ROUTE_FILES
.iter()
.filter(|(name, _)| *name == "project.json")
.count(),
1
);
assert_eq!(
ROUTE_FILES
.iter()
.filter(|(name, _)| name.ends_with("doPost.py"))
.count(),
4
);
for (name, _) in ROUTE_FILES {
assert!(
*name == "project.json"
|| name.starts_with("com.inductiveautomation.webdev/resources/cli/"),
"member outside the Designer-native layout: {name}"
);
}
}
#[test]
fn tagconfig_route_source_refuses_provider_roots() {
let (_, source) = ROUTE_FILES
.iter()
.find(|(name, _)| {
*name == "com.inductiveautomation.webdev/resources/cli/tagConfig/doPost.py"
})
.expect("tagConfig doPost.py in the manifest");
assert!(
source.contains("provider_root_unsupported"),
"the tagConfig route must keep its provider-root refusal"
);
assert!(
source.contains("def is_provider_root("),
"the bracket-form detector must stay nested inside doPost (byte-0 rule)"
);
assert!(
source.contains("'No RpcContext' in traceback.format_exc()"),
"the bare-form RpcContext translation must stay"
);
}
}