api_testing_core/suite/
safety.rs1use std::path::Path;
2
3use crate::Result;
4
5pub const MSG_NOT_SELECTED: &str = "not_selected";
6pub const MSG_SKIPPED_BY_ID: &str = "skipped_by_id";
7pub const MSG_TAG_MISMATCH: &str = "tag_mismatch";
8
9pub const MSG_WRITE_CASES_DISABLED: &str = "write_cases_disabled";
10pub const MSG_WRITE_CAPABLE_REQUIRES_ALLOW_WRITE_TRUE: &str =
11 "write_capable_case_requires_allowWrite_true";
12pub const MSG_MUTATION_REQUIRES_ALLOW_WRITE_TRUE: &str = "mutation_case_requires_allowWrite_true";
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum SafetyDecision {
16 Allow,
17 Skip(&'static str),
18 Fail(&'static str),
19}
20
21pub fn writes_enabled(allow_writes_flag: bool, effective_env: &str) -> bool {
22 allow_writes_flag || effective_env.trim().eq_ignore_ascii_case("local")
23}
24
25pub fn rest_method_is_write(method: &str) -> bool {
26 let m = method.trim().to_ascii_uppercase();
27 !matches!(m.as_str(), "GET" | "HEAD" | "OPTIONS")
28}
29
30pub fn graphql_operation_is_write_capable(
31 operation_file: &Path,
32 allow_write: bool,
33) -> Result<bool> {
34 if allow_write {
35 return Ok(true);
36 }
37 crate::graphql::mutation::operation_file_is_mutation(operation_file)
38}
39
40pub fn graphql_safety_decision(
41 operation_file: &Path,
42 allow_write: bool,
43 allow_writes_flag: bool,
44 effective_env: &str,
45) -> Result<SafetyDecision> {
46 let writes_enabled = writes_enabled(allow_writes_flag, effective_env);
47
48 if allow_write && !writes_enabled {
50 return Ok(SafetyDecision::Skip(MSG_WRITE_CASES_DISABLED));
51 }
52
53 let is_mutation = crate::graphql::mutation::operation_file_is_mutation(operation_file)?;
54 if !is_mutation {
55 return Ok(SafetyDecision::Allow);
56 }
57
58 if !allow_write {
59 return Ok(SafetyDecision::Fail(MSG_MUTATION_REQUIRES_ALLOW_WRITE_TRUE));
60 }
61
62 if !writes_enabled {
63 return Ok(SafetyDecision::Skip(MSG_WRITE_CASES_DISABLED));
64 }
65
66 Ok(SafetyDecision::Allow)
67}
68
69#[cfg(test)]
70mod tests {
71 use super::*;
72
73 use tempfile::TempDir;
74
75 #[test]
76 fn suite_safety_rest_method_write_detection_matches_script_intent() {
77 assert!(!rest_method_is_write("GET"));
78 assert!(!rest_method_is_write("head"));
79 assert!(!rest_method_is_write(" OPTIONS "));
80 assert!(rest_method_is_write("POST"));
81 assert!(rest_method_is_write("PATCH"));
82 assert!(rest_method_is_write("DELETE"));
83 }
84
85 #[test]
86 fn suite_safety_graphql_allow_write_true_is_write_capable() {
87 let tmp = TempDir::new().unwrap();
88 let op = tmp.path().join("q.graphql");
89 std::fs::write(&op, "query Q { ok }\n").unwrap();
90
91 assert!(graphql_operation_is_write_capable(&op, true).unwrap());
92 }
93}