Merge pull request #235 from Orange-OpenSource/feature/support-predicates-with-bytearray

Support startsWith and contains predicates with bytes
This commit is contained in:
Fabrice Reix 2021-07-07 16:03:26 +02:00 committed by GitHub
commit d3f3c18d2d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 69 additions and 36 deletions

View File

@ -1 +1 @@
<div class="hurl-file"><div class="hurl-entry"><div class="request"><span class="line"><span class="method">GET</span> <span class="url">http://localhost:8000/bytes</span></span></div><div class="response"><span class="line"><span class="version">HTTP/1.0</span> <span class="status">200</span></span><span class="line"><span class="string">Content-Type</span><span>:</span> <span class="string">application/octet-stream</span></span><span class="line section-header">[Asserts]</span><span class="line"><span class="query-type">bytes</span> <span class="predicate-type">equals</span> <span class="hex">hex,ff;</span></span><span class="line"><span class="query-type">bytes</span> <span class="predicate-type">equals</span> <span>base64,/w==;</span></span><span class="line"><span class="query-type">bytes</span> <span class="query-type">count</span> <span class="predicate-type">==</span> <span class="number">1</span></span><span class="line"><span class="query-type">sha256</span> <span class="predicate-type">equals</span> <span class="hex">hex,a8100ae6aa1940d0b663bb31cd466142ebbdbd5187131b92d93818987832eb89;</span></span></div></div></div>
<div class="hurl-file"><div class="hurl-entry"><div class="request"><span class="line"><span class="method">GET</span> <span class="url">http://localhost:8000/bytes</span></span></div><div class="response"><span class="line"><span class="version">HTTP/1.0</span> <span class="status">200</span></span><span class="line"><span class="string">Content-Type</span><span>:</span> <span class="string">application/octet-stream</span></span><span class="line section-header">[Asserts]</span><span class="line"><span class="query-type">bytes</span> <span class="predicate-type">equals</span> <span class="hex">hex,010203;</span></span><span class="line"><span class="query-type">bytes</span> <span class="predicate-type">equals</span> <span>base64,AQID;</span></span><span class="line"><span class="query-type">bytes</span> <span class="query-type">count</span> <span class="predicate-type">==</span> <span class="number">3</span></span><span class="line"><span class="query-type">bytes</span> <span class="predicate-type">startsWith</span> <span class="hex">hex,01;</span></span><span class="line"><span class="query-type">bytes</span> <span class="predicate-type">contains</span> <span class="hex">hex,02;</span></span><span class="line"><span class="query-type">sha256</span> <span class="predicate-type">equals</span> <span class="hex">hex,039058c6f2c0cb492c533b0a4d14ef77cc0f78abccced5287d84a1a2011cfb81;</span></span></div></div></div>

View File

@ -2,7 +2,9 @@ GET http://localhost:8000/bytes
HTTP/1.0 200
Content-Type: application/octet-stream
[Asserts]
bytes equals hex,ff;
bytes equals base64,/w==;
bytes count == 1
sha256 equals hex,a8100ae6aa1940d0b663bb31cd466142ebbdbd5187131b92d93818987832eb89;
bytes equals hex,010203;
bytes equals base64,AQID;
bytes count == 3
bytes startsWith hex,01;
bytes contains hex,02;
sha256 equals hex,039058c6f2c0cb492c533b0a4d14ef77cc0f78abccced5287d84a1a2011cfb81;

View File

@ -1 +1 @@
{"entries":[{"request":{"method":"GET","url":"http://localhost:8000/bytes"},"response":{"version":"HTTP/1.0","status":200,"headers":[{"name":"Content-Type","value":"application/octet-stream"}],"asserts":[{"query":{"type":"bytes"},"predicate":{"type":"equal","value":"/w==","encoding":"base64"}},{"query":{"type":"bytes"},"predicate":{"type":"equal","value":"/w==","encoding":"base64"}},{"query":{"type":"bytes","subquery":{"type":"count"}},"predicate":{"type":"equal","value":1}},{"query":{"type":"sha256"},"predicate":{"type":"equal","value":"qBAK5qoZQNC2Y7sxzUZhQuu9vVGHExuS2TgYmHgy64k=","encoding":"base64"}}]}}]}
{"entries":[{"request":{"method":"GET","url":"http://localhost:8000/bytes"},"response":{"version":"HTTP/1.0","status":200,"headers":[{"name":"Content-Type","value":"application/octet-stream"}],"asserts":[{"query":{"type":"bytes"},"predicate":{"type":"equal","value":"AQID","encoding":"base64"}},{"query":{"type":"bytes"},"predicate":{"type":"equal","value":"AQID","encoding":"base64"}},{"query":{"type":"bytes","subquery":{"type":"count"}},"predicate":{"type":"equal","value":3}},{"query":{"type":"bytes"},"predicate":{"type":"start-with","value":"AQ==","encoding":"base64"}},{"query":{"type":"bytes"},"predicate":{"type":"contain","value":"Ag==","encoding":"base64"}},{"query":{"type":"sha256"},"predicate":{"type":"equal","value":"A5BYxvLAy0ksUzsKTRTvd8wPeKvMztUofYShogEc+4E=","encoding":"base64"}}]}}]}

View File

@ -1 +1 @@
<EFBFBD>


View File

@ -6,7 +6,7 @@ from io import BytesIO
@app.route('/bytes')
def bytes():
result = BytesIO()
result.write(b'\xff')
result.write(b'\x01\x02\x03')
data = result.getvalue()
resp = make_response(data)
resp.content_type = 'application/octet-stream'

View File

@ -55,3 +55,11 @@ error: Assert Failure
| expected: float
|
error: Assert Failure
--> tests/error_assert_value_error.hurl:11:0
|
11 | bytes contains hex,00;
| actual: byte array <7b202276616c756573223a205b312c322c335d2c2022636f756e74223a20327d>
| expected: contains byte array <00>
|

View File

@ -1 +1 @@
<div class="hurl-file"><div class="hurl-entry"><div class="request"><span class="line"><span class="method">GET</span> <span class="url">http://localhost:8000/error-assert-value</span></span></div><div class="response"><span class="line"><span class="version">HTTP/1.0</span> <span class="status">200</span></span><span class="line section-header">[Asserts]</span><span class="line"><span class="query-type">header</span> <span class="string">"content-type"</span> <span class="predicate-type">equals</span> <span class="string">"XXX"</span></span><span class="line"><span class="query-type">header</span> <span class="string">"content-type"</span> <span class="predicate-type">notEquals</span> <span class="string">"text/html; charset=utf-8"</span></span><span class="line"><span class="query-type">jsonpath</span> <span class="string">"$.id"</span> <span class="predicate-type">equals</span> <span class="string">"000001"</span></span><span class="line"><span class="query-type">jsonpath</span> <span class="string">"$.values"</span> <span class="predicate-type">includes</span> <span class="number">100</span></span><span class="line"><span class="query-type">jsonpath</span> <span class="string">"$.values"</span> not <span class="predicate-type">contains</span> <span class="string">"Hello"</span></span><span class="line"><span class="query-type">jsonpath</span> <span class="string">"$.count"</span> <span class="predicate-type">greaterThan</span> <span class="number">5</span></span><span class="line"><span class="query-type">jsonpath</span> <span class="string">"$.count"</span> <span class="predicate-type">isFloat</span></span></div></div></div>
<div class="hurl-file"><div class="hurl-entry"><div class="request"><span class="line"><span class="method">GET</span> <span class="url">http://localhost:8000/error-assert-value</span></span></div><div class="response"><span class="line"><span class="version">HTTP/1.0</span> <span class="status">200</span></span><span class="line section-header">[Asserts]</span><span class="line"><span class="query-type">header</span> <span class="string">"content-type"</span> <span class="predicate-type">equals</span> <span class="string">"XXX"</span></span><span class="line"><span class="query-type">header</span> <span class="string">"content-type"</span> <span class="predicate-type">notEquals</span> <span class="string">"text/html; charset=utf-8"</span></span><span class="line"><span class="query-type">jsonpath</span> <span class="string">"$.id"</span> <span class="predicate-type">equals</span> <span class="string">"000001"</span></span><span class="line"><span class="query-type">jsonpath</span> <span class="string">"$.values"</span> <span class="predicate-type">includes</span> <span class="number">100</span></span><span class="line"><span class="query-type">jsonpath</span> <span class="string">"$.values"</span> not <span class="predicate-type">contains</span> <span class="string">"Hello"</span></span><span class="line"><span class="query-type">jsonpath</span> <span class="string">"$.count"</span> <span class="predicate-type">greaterThan</span> <span class="number">5</span></span><span class="line"><span class="query-type">jsonpath</span> <span class="string">"$.count"</span> <span class="predicate-type">isFloat</span></span><span class="line"><span class="query-type">bytes</span> <span class="predicate-type">contains</span> <span class="hex">hex,00;</span></span></div></div></div>

View File

@ -7,4 +7,5 @@ jsonpath "$.id" equals "000001"
jsonpath "$.values" includes 100
jsonpath "$.values" not contains "Hello"
jsonpath "$.count" greaterThan 5
jsonpath "$.count" isFloat
jsonpath "$.count" isFloat
bytes contains hex,00;

View File

@ -1 +1 @@
{"entries":[{"request":{"method":"GET","url":"http://localhost:8000/error-assert-value"},"response":{"version":"HTTP/1.0","status":200,"asserts":[{"query":{"type":"header","name":"content-type"},"predicate":{"type":"equal","value":"XXX"}},{"query":{"type":"header","name":"content-type"},"predicate":{"type":"not-equal","value":"text/html; charset=utf-8"}},{"query":{"type":"jsonpath","expr":"$.id"},"predicate":{"type":"equal","value":"000001"}},{"query":{"type":"jsonpath","expr":"$.values"},"predicate":{"type":"include","value":100}},{"query":{"type":"jsonpath","expr":"$.values"},"predicate":{"not":true,"type":"contain","value":"Hello"}},{"query":{"type":"jsonpath","expr":"$.count"},"predicate":{"type":"greater","value":5}},{"query":{"type":"jsonpath","expr":"$.count"},"predicate":{"type":"isFloat"}}]}}]}
{"entries":[{"request":{"method":"GET","url":"http://localhost:8000/error-assert-value"},"response":{"version":"HTTP/1.0","status":200,"asserts":[{"query":{"type":"header","name":"content-type"},"predicate":{"type":"equal","value":"XXX"}},{"query":{"type":"header","name":"content-type"},"predicate":{"type":"not-equal","value":"text/html; charset=utf-8"}},{"query":{"type":"jsonpath","expr":"$.id"},"predicate":{"type":"equal","value":"000001"}},{"query":{"type":"jsonpath","expr":"$.values"},"predicate":{"type":"include","value":100}},{"query":{"type":"jsonpath","expr":"$.values"},"predicate":{"not":true,"type":"contain","value":"Hello"}},{"query":{"type":"jsonpath","expr":"$.count"},"predicate":{"type":"greater","value":5}},{"query":{"type":"jsonpath","expr":"$.count"},"predicate":{"type":"isFloat"}},{"query":{"type":"bytes"},"predicate":{"type":"contain","value":"AA==","encoding":"base64"}}]}}]}

View File

@ -302,27 +302,29 @@ fn eval_something(
}),
},
// starts with string
// starts with string or bytes
PredicateFuncValue::StartWith {
value: expected, ..
} => {
let template = if let PredicateValue::String(template) = expected {
template
} else {
panic!("expect a string predicate value")
};
let expected = eval_template(template, variables)?;
match value.clone() {
Value::String(actual) => Ok(AssertResult {
success: actual.as_str().starts_with(expected.as_str()),
let expected_value = eval_predicate_value(expected, variables)?;
let expected = format!("starts with {}", expected_value.clone().display());
match (expected_value, value.clone()) {
(Value::String(s), Value::String(actual)) => Ok(AssertResult {
success: actual.as_str().starts_with(s.as_str()),
actual: value.display(),
expected: format!("starts with string <{}>", expected),
expected,
type_mismatch: false,
}),
(Value::Bytes(bytes), Value::Bytes(actual)) => Ok(AssertResult {
success: actual.starts_with(&bytes),
actual: value.display(),
expected,
type_mismatch: false,
}),
_ => Ok(AssertResult {
success: false,
actual: value.display(),
expected: format!("starts with string <{}>", expected),
expected,
type_mismatch: true,
}),
}
@ -332,23 +334,25 @@ fn eval_something(
PredicateFuncValue::Contain {
value: expected, ..
} => {
let template = if let PredicateValue::String(template) = expected {
template
} else {
panic!("expect a string predicate value")
};
let expected = eval_template(template, variables)?;
match value.clone() {
Value::String(actual) => Ok(AssertResult {
success: actual.as_str().contains(expected.as_str()),
let expected_value = eval_predicate_value(expected, variables)?;
let expected = format!("contains {}", expected_value.clone().display());
match (expected_value, value.clone()) {
(Value::String(s), Value::String(actual)) => Ok(AssertResult {
success: actual.as_str().contains(s.as_str()),
actual: value.display(),
expected: format!("contains string <{}>", expected),
expected,
type_mismatch: false,
}),
(Value::Bytes(bytes), Value::Bytes(actual)) => Ok(AssertResult {
success: contains(actual.as_slice(), bytes.as_slice()),
actual: value.display(),
expected,
type_mismatch: false,
}),
_ => Ok(AssertResult {
success: false,
actual: value.display(),
expected: format!("contains string <{}>", expected),
expected,
type_mismatch: true,
}),
}
@ -750,6 +754,12 @@ fn assert_include(value: Value, element: Value) -> AssertResult {
}
}
fn contains(haystack: &[u8], needle: &[u8]) -> bool {
haystack
.windows(needle.len())
.any(|window| window == needle)
}
#[cfg(test)]
mod tests {
use super::AssertResult;
@ -762,6 +772,14 @@ mod tests {
}
}
#[test]
fn test_contains() {
let haystack = [1 as u8, 2 as u8, 3 as u8];
assert!(contains(&haystack, &[1 as u8]));
assert!(contains(&haystack, &[1 as u8, 2 as u8]));
assert!(!contains(&haystack, &[1 as u8, 3 as u8]));
}
#[test]
fn test_invalid_xpath() {}

View File

@ -110,6 +110,10 @@ impl PredicateValue {
pub fn is_string(&self) -> bool {
matches!(self, PredicateValue::String(_))
}
pub fn is_bytearray(&self) -> bool {
matches!(self, PredicateValue::Hex(_)) | matches!(self, PredicateValue::Base64(_))
}
}
fn equal_predicate(reader: &mut Reader) -> ParseResult<'static, PredicateFuncValue> {
@ -254,7 +258,7 @@ fn start_with_predicate(reader: &mut Reader) -> ParseResult<'static, PredicateFu
let space0 = one_or_more_spaces(reader)?;
let save = reader.state.clone();
let value = predicate_value(reader)?;
if !value.is_string() {
if !value.is_string() && !value.is_bytearray() {
return Err(Error {
pos: save.pos,
recoverable: false,
@ -269,7 +273,7 @@ fn contain_predicate(reader: &mut Reader) -> ParseResult<'static, PredicateFuncV
let space0 = one_or_more_spaces(reader)?;
let save = reader.state.clone();
let value = predicate_value(reader)?;
if !value.is_string() {
if !value.is_string() && !value.is_bytearray() {
return Err(Error {
pos: save.pos,
recoverable: false,