-
Notifications
You must be signed in to change notification settings - Fork 29
Expand file tree
/
Copy pathjson_parity.rs
More file actions
154 lines (130 loc) · 5.1 KB
/
json_parity.rs
File metadata and controls
154 lines (130 loc) · 5.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
// Minimal tests verifying parity between JavaScript (V8) and Proc rule engines.
// Import common test utilities including automatic logging setup
mod common;
use httpjail::rules::proc::ProcRuleEngine;
use httpjail::rules::v8_js::V8JsRuleEngine;
use httpjail::rules::{Action, RuleEngineTrait};
use hyper::Method;
use std::fs;
use std::io::Write;
use tempfile::NamedTempFile;
fn create_temp_script(content: &str) -> tempfile::TempPath {
let mut file = NamedTempFile::new().unwrap();
file.write_all(content.as_bytes()).unwrap();
file.flush().unwrap();
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let mut perms = fs::metadata(file.path()).unwrap().permissions();
perms.set_mode(0o755);
fs::set_permissions(file.path(), perms).unwrap();
}
// IMPORTANT: Convert to TempPath to close the file handle while keeping the file
// This prevents "Text file busy" error on Linux when executing the script
file.into_temp_path()
}
#[tokio::test]
async fn test_json_parity() {
// Test that both engines receive identical request JSON
let proc_script = create_temp_script(
r#"#!/usr/bin/env python3
import sys
import json
for line in sys.stdin:
request = json.loads(line.strip())
print(json.dumps({"deny_message": json.dumps(request)}))
sys.stdout.flush()
"#,
);
let proc_engine = ProcRuleEngine::new(proc_script.to_str().unwrap().to_string());
let js_engine = V8JsRuleEngine::new("({deny_message: JSON.stringify(r)})".to_string()).unwrap();
let proc_result = proc_engine
.evaluate(Method::GET, "https://example.com/test?foo=bar", "127.0.0.1")
.await;
let js_result = js_engine
.evaluate(Method::GET, "https://example.com/test?foo=bar", "127.0.0.1")
.await;
// Both should deny with the request JSON as context
assert!(matches!(proc_result.action, Action::Deny));
assert!(matches!(js_result.action, Action::Deny));
// Parse and compare the JSON
let proc_json: serde_json::Value = serde_json::from_str(&proc_result.context.unwrap()).unwrap();
let js_json: serde_json::Value = serde_json::from_str(&js_result.context.unwrap()).unwrap();
assert_eq!(proc_json, js_json, "Engines should receive identical JSON");
}
#[tokio::test]
async fn test_response_parity() {
// Test that both engines handle responses identically
let test_cases = [
("true", "true", true),
("false", "false", false),
(
r#"{"deny_message": "blocked"}"#,
r#"({deny_message: "blocked"})"#,
false,
),
];
for (proc_response, js_code, expected_allow) in test_cases {
let proc_script = create_temp_script(&format!(
"#!/bin/sh\nwhile read line; do echo '{}'; done",
proc_response
));
let proc_engine = ProcRuleEngine::new(proc_script.to_str().unwrap().to_string());
let js_engine = V8JsRuleEngine::new(js_code.to_string()).unwrap();
let proc_result = proc_engine
.evaluate(Method::GET, "https://example.com", "127.0.0.1")
.await;
let js_result = js_engine
.evaluate(Method::GET, "https://example.com", "127.0.0.1")
.await;
assert_eq!(
matches!(proc_result.action, Action::Allow),
expected_allow,
"Proc mismatch for {}",
proc_response
);
assert_eq!(
matches!(js_result.action, Action::Allow),
expected_allow,
"JS mismatch for {}",
js_code
);
// If denied with message, verify it's the same
if !expected_allow && proc_response.contains("deny_message") {
assert_eq!(proc_result.context, Some("blocked".to_string()));
assert_eq!(js_result.context, Some("blocked".to_string()));
}
}
}
#[tokio::test]
async fn test_console_api() {
// Test that console API methods work without throwing errors.
// The console output is visible in test output when run with RUST_LOG=debug,
// which provides visual confirmation that the console API is working correctly.
// We don't attempt to capture/assert on logs because the global tracing subscriber
// is already initialized by tests/common/logging.rs, making log capture unreliable.
let js_engine = V8JsRuleEngine::new(
r#"
// Test all console methods
console.debug("Test debug");
console.log("Test log");
console.info("Test info");
console.warn("Test warn");
console.error("Test error");
// Test object/array formatting
console.log("Object:", {foo: "bar"});
console.log("Array:", [1, 2, 3]);
// Test multiple arguments
console.log("Multiple", "arguments", 123);
true
"#
.to_string(),
)
.unwrap();
let result = js_engine
.evaluate(Method::GET, "https://example.com", "127.0.0.1")
.await;
// Should allow since the expression returns true
// If console methods threw errors, the rule would fail
assert!(matches!(result.action, Action::Allow));
}