Add bytes query and equals predicate for bytearray

This commit is contained in:
Fabrice Reix 2021-06-16 21:13:17 +02:00
parent 45a3ab480e
commit 93c2fede66
16 changed files with 205 additions and 13 deletions

1
Cargo.lock generated
View File

@ -365,6 +365,7 @@ name = "hurlfmt"
version = "1.3.0"
dependencies = [
"atty",
"base64",
"clap",
"colored",
"hurl_core",

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></div></div><span class="line"><span class="comment">#TODO create a bytes query to get e byte array</span></span><span class="line"><span class="comment">#body countEquals 1</span></span><span class="line"></span><span class="line"></span><span class="line"></span></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><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 class="number">1</span></span></div></div><span class="line"></span></div>

View File

@ -2,8 +2,6 @@ GET http://localhost:8000/bytes
HTTP/1.0 200
Content-Type: application/octet-stream
[Asserts]
#TODO create a bytes query to get e byte array
#body countEquals 1
bytes equals hex,ff;
bytes countEquals 1

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"}]}}]}
{"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":{"value":"/w==","encoding":"base64"}}},{"query":{"type":"bytes"},"predicate":{"type":"count","value":1}}]}}]}

View File

@ -164,6 +164,9 @@ fn expected(
let expected = eval_template(expected, variables)?;
Ok(format!("string <{}>", expected))
}
PredicateFuncValue::EqualHex {
value: expected, ..
} => Ok(format!("bytearray <{}>", expected.to_string())),
PredicateFuncValue::EqualExpression {
value: expected, ..
} => {
@ -326,6 +329,14 @@ fn eval_something(
Ok(assert_values_equal(value, Value::String(expected)))
}
// equals hex
PredicateFuncValue::EqualHex {
value: Hex {
value: expected, ..
},
..
} => Ok(assert_values_equal(value, Value::Bytes(expected))),
// equals expression
PredicateFuncValue::EqualExpression {
value: expected, ..
@ -392,6 +403,12 @@ fn eval_something(
expected: expected_value.to_string(),
type_mismatch: false,
}),
Value::Bytes(data) => Ok(AssertResult {
success: data.len() as u64 == expected_value,
actual: data.len().to_string(),
expected: expected_value.to_string(),
type_mismatch: false,
}),
_ => Ok(AssertResult {
success: false,
actual: value.clone().display(),

View File

@ -203,6 +203,7 @@ pub fn eval_query(
QueryValue::Duration {} => Ok(Some(Value::Integer(
http_response.duration.as_millis() as i64
))),
QueryValue::Bytes {} => Ok(Some(Value::Bytes(http_response.body))),
}
}
@ -1006,4 +1007,22 @@ pub mod tests {
assert_eq!(error.source_info, SourceInfo::init(1, 7, 1, 10));
assert_eq!(error.inner, RunnerError::InvalidRegex());
}
#[test]
fn test_query_bytes() {
let variables = HashMap::new();
assert_eq!(
eval_query(
Query {
source_info: SourceInfo::init(0, 0, 0, 0),
value: QueryValue::Bytes {},
},
&variables,
http::hello_http_response(),
)
.unwrap()
.unwrap(),
Value::Bytes(String::into_bytes(String::from("Hello World!")))
);
}
}

View File

@ -323,6 +323,7 @@ pub enum QueryValue {
name: Template,
},
Duration {},
Bytes {},
}
#[derive(Clone, Debug, PartialEq, Eq)]
@ -403,6 +404,7 @@ pub enum PredicateFuncValue {
EqualFloat { space0: Whitespace, value: Float },
EqualBool { space0: Whitespace, value: bool },
EqualNull { space0: Whitespace },
EqualHex { space0: Whitespace, value: Hex },
EqualExpression { space0: Whitespace, value: Expr },
GreaterThanInt { space0: Whitespace, value: i64 },
GreaterThanFloat { space0: Whitespace, value: Float },
@ -528,6 +530,14 @@ pub enum Bytes {
},
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Hex {
pub space0: Whitespace,
pub value: Vec<u8>,
pub encoded: String,
pub space1: Whitespace,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Pos {
pub line: usize,

View File

@ -131,6 +131,16 @@ impl fmt::Display for CookieAttribute {
}
}
impl fmt::Display for Hex {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"hex,{}{}{};",
self.space0.value, self.encoded, self.space1.value
)
}
}
#[cfg(test)]
mod tests {
use super::*;

View File

@ -375,6 +375,9 @@ impl Htmlable for QueryValue {
QueryValue::Duration {} => {
buffer.push_str("<span class=\"query-type\">duration</span>");
}
QueryValue::Bytes {} => {
buffer.push_str("<span class=\"query-type\">bytes</span>");
}
}
buffer
}
@ -463,6 +466,12 @@ impl Htmlable for PredicateFuncValue {
format!("<span class=\"number\">{}</span>", value.to_string()).as_str(),
);
}
PredicateFuncValue::EqualHex { space0, value } => {
buffer.push_str("<span class=\"predicate-type\">equals</span>");
buffer.push_str(space0.to_html().as_str());
buffer
.push_str(format!("<span class=\"hex\">{}</span>", value.to_string()).as_str());
}
PredicateFuncValue::GreaterThanInt { space0, value } => {
buffer.push_str("<span class=\"predicate-type\">greaterThan</span>");
buffer.push_str(space0.to_html().as_str());

View File

@ -103,6 +103,7 @@ fn equal_predicate(reader: &mut Reader) -> ParseResult<'static, PredicateFuncVal
Ok(PredicateValue::Bool { value }) => Ok(PredicateFuncValue::EqualBool { space0, value }),
Ok(PredicateValue::Int { value }) => Ok(PredicateFuncValue::EqualInt { space0, value }),
Ok(PredicateValue::Float { value }) => Ok(PredicateFuncValue::EqualFloat { space0, value }),
Ok(PredicateValue::Hex { value }) => Ok(PredicateFuncValue::EqualHex { space0, value }),
Ok(PredicateValue::Expression { value }) => {
Ok(PredicateFuncValue::EqualExpression { space0, value })
}
@ -273,6 +274,9 @@ fn include_predicate(reader: &mut Reader) -> ParseResult<'static, PredicateFuncV
Ok(PredicateValue::Template { value }) => {
Ok(PredicateFuncValue::IncludeString { space0, value })
}
Ok(PredicateValue::Hex { value: _ }) => {
todo!()
}
Ok(PredicateValue::Expression { value }) => {
Ok(PredicateFuncValue::IncludeExpression { space0, value })
}
@ -332,6 +336,7 @@ enum PredicateValue {
Float { value: Float },
Bool { value: bool },
Template { value: Template },
Hex { value: Hex },
Expression { value: Expr },
}
@ -354,6 +359,10 @@ fn predicate_value(reader: &mut Reader) -> ParseResult<'static, PredicateValue>
Ok(value) => Ok(PredicateValue::Int { value }),
Err(e) => Err(e),
},
|p1| match hex(p1) {
Ok(value) => Ok(PredicateValue::Hex { value }),
Err(e) => Err(e),
},
|p1| match expr::parse(p1) {
Ok(value) => Ok(PredicateValue::Expression { value }),
Err(e) => Err(e),

View File

@ -263,6 +263,42 @@ pub fn key_value(reader: &mut Reader) -> ParseResult<'static, KeyValue> {
})
}
pub fn hex(reader: &mut Reader) -> ParseResult<'static, Hex> {
try_literal("hex", reader)?;
literal(",", reader)?;
let space0 = zero_or_more_spaces(reader)?;
let mut value: Vec<u8> = vec![];
let start = reader.state.cursor;
let mut current: i32 = -1;
loop {
let s = reader.state.clone();
match hex_digit(reader) {
Ok(d) => {
if current != -1 {
value.push((current * 16 + d as i32) as u8);
current = -1;
} else {
current = d as i32;
}
}
Err(_) => {
reader.state = s;
break;
}
};
}
let encoded = reader.from(start);
let space1 = zero_or_more_spaces(reader)?;
literal(";", reader)?;
Ok(Hex {
space0,
value,
encoded,
space1,
})
}
pub fn filename(reader: &mut Reader) -> ParseResult<'static, Filename> {
// this is an absolure file
// that you have to write with a relative name
@ -882,7 +918,7 @@ mod tests {
Float {
int: 1,
decimal: 0,
decimal_digits: 1
decimal_digits: 1,
}
);
assert_eq!(reader.state.cursor, 3);
@ -893,7 +929,7 @@ mod tests {
Float {
int: -1,
decimal: 0,
decimal_digits: 1
decimal_digits: 1,
}
);
assert_eq!(reader.state.cursor, 4);
@ -904,7 +940,7 @@ mod tests {
Float {
int: 1,
decimal: 100_000_000_000_000_000,
decimal_digits: 1
decimal_digits: 1,
}
);
assert_eq!(reader.state.cursor, 3);
@ -915,7 +951,7 @@ mod tests {
Float {
int: 1,
decimal: 100_000_000_000_000_000,
decimal_digits: 3
decimal_digits: 3,
}
);
assert_eq!(reader.state.cursor, 5);
@ -926,7 +962,7 @@ mod tests {
Float {
int: 1,
decimal: 10_000_000_000_000_000,
decimal_digits: 2
decimal_digits: 2,
}
);
assert_eq!(reader.state.cursor, 4);
@ -937,7 +973,7 @@ mod tests {
Float {
int: 1,
decimal: 10_000_000_000_000_000,
decimal_digits: 3
decimal_digits: 3,
}
);
assert_eq!(reader.state.cursor, 5);
@ -948,7 +984,7 @@ mod tests {
Float {
int: 0,
decimal: 333_333_333_333_333_333,
decimal_digits: 18
decimal_digits: 18,
}
);
assert_eq!(reader.state.cursor, 21);
@ -1254,4 +1290,41 @@ mod tests {
assert_eq!(error.inner, ParseError::HexDigit {});
assert_eq!(error.recoverable, true);
}
#[test]
fn test_hex() {
let mut reader = Reader::init("hex, ff;");
assert_eq!(
hex(&mut reader).unwrap(),
Hex {
space0: Whitespace {
value: " ".to_string(),
source_info: SourceInfo::init(1, 5, 1, 6)
},
value: vec![255],
encoded: "ff".to_string(),
space1: Whitespace {
value: "".to_string(),
source_info: SourceInfo::init(1, 8, 1, 8)
},
}
);
let mut reader = Reader::init("hex,010203 ;");
assert_eq!(
hex(&mut reader).unwrap(),
Hex {
space0: Whitespace {
value: "".to_string(),
source_info: SourceInfo::init(1, 5, 1, 5)
},
value: vec![1, 2, 3],
encoded: "010203".to_string(),
space1: Whitespace {
value: " ".to_string(),
source_info: SourceInfo::init(1, 11, 1, 12)
},
}
);
}
}

View File

@ -56,6 +56,7 @@ fn query_value(reader: &mut Reader) -> ParseResult<'static, QueryValue> {
regex_query,
variable_query,
duration_query,
bytes_query,
],
reader,
)
@ -154,6 +155,11 @@ fn duration_query(reader: &mut Reader) -> ParseResult<'static, QueryValue> {
Ok(QueryValue::Duration {})
}
fn bytes_query(reader: &mut Reader) -> ParseResult<'static, QueryValue> {
try_literal("bytes", reader)?;
Ok(QueryValue::Bytes {})
}
#[cfg(test)]
mod tests {
use super::*;

View File

@ -15,6 +15,7 @@ strict = []
[dependencies]
atty = "0.2.13"
base64 = "0.11.0"
clap = "2.33.0"
colored = "2"
hurl_core = { version = "1.1.0", path = "../hurl_core" }

View File

@ -293,6 +293,9 @@ impl ToJson for QueryValue {
QueryValue::Duration {} => {
attributes.push(("type".to_string(), JValue::String("duration".to_string())));
}
QueryValue::Bytes {} => {
attributes.push(("type".to_string(), JValue::String("bytes".to_string())));
}
};
JValue::Object(attributes)
}
@ -344,6 +347,17 @@ impl ToJson for Predicate {
attributes.push(("type".to_string(), JValue::String("equal".to_string())));
attributes.push(("value".to_string(), JValue::Null));
}
PredicateFuncValue::EqualHex { value, .. } => {
attributes.push(("type".to_string(), JValue::String("equal".to_string())));
let value = JValue::Object(vec![
(
"value".to_string(),
JValue::String(base64::encode(&value.value)),
),
("encoding".to_string(), JValue::String("base64".to_string())),
]);
attributes.push(("value".to_string(), value));
}
PredicateFuncValue::EqualExpression { value, .. } => {
attributes.push(("type".to_string(), JValue::String("equal".to_string())));
attributes.push(("value".to_string(), JValue::String(value.to_string())));

View File

@ -463,6 +463,7 @@ impl Tokenizable for Query {
add_tokens(&mut tokens, name.tokenize());
}
QueryValue::Duration {} => tokens.push(Token::QueryType(String::from("duration"))),
QueryValue::Bytes {} => tokens.push(Token::QueryType(String::from("bytes"))),
}
tokens
}
@ -551,6 +552,11 @@ impl Tokenizable for PredicateFuncValue {
add_tokens(&mut tokens, space0.tokenize());
tokens.push(Token::Number(value.to_string()));
}
PredicateFuncValue::EqualHex { space0, value } => {
tokens.push(Token::PredicateType(String::from("equals")));
add_tokens(&mut tokens, space0.tokenize());
tokens.push(Token::String(value.to_string()));
}
PredicateFuncValue::EqualExpression { space0, value } => {
tokens.push(Token::PredicateType(String::from("equals")));
add_tokens(&mut tokens, space0.tokenize());

View File

@ -296,6 +296,7 @@ impl Lintable<QueryValue> for QueryValue {
space0: one_whitespace(),
},
QueryValue::Duration {} => QueryValue::Duration {},
QueryValue::Bytes {} => QueryValue::Bytes {},
}
}
}
@ -403,6 +404,10 @@ impl Lintable<PredicateFuncValue> for PredicateFuncValue {
space0: one_whitespace(),
value: value.clone(),
},
PredicateFuncValue::EqualHex { value, .. } => PredicateFuncValue::EqualHex {
space0: one_whitespace(),
value: value.lint(),
},
PredicateFuncValue::EqualExpression { value, .. } => {
PredicateFuncValue::EqualExpression {
space0: one_whitespace(),
@ -664,6 +669,20 @@ fn one_whitespace() -> Whitespace {
source_info: SourceInfo::init(0, 0, 0, 0),
}
}
impl Lintable<Hex> for Hex {
fn errors(&self) -> Vec<Error> {
unimplemented!()
}
fn lint(&self) -> Hex {
Hex {
space0: one_whitespace(),
value: self.value.clone(),
encoded: self.encoded.clone(),
space1: empty_whitespace(),
}
}
}
impl Lintable<LineTerminator> for LineTerminator {
fn errors(&self) -> Vec<Error> {