mirror of
https://github.com/Orange-OpenSource/hurl.git
synced 2024-11-22 15:42:20 +03:00
Init Commit - beta release 0.99.12
This commit is contained in:
commit
d849e0d1b3
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
*.swp
|
||||
*.swo
|
||||
*.pyc
|
||||
|
||||
target/
|
||||
.idea/
|
47
.travis.yml
Normal file
47
.travis.yml
Normal file
@ -0,0 +1,47 @@
|
||||
language: rust
|
||||
sudo: true
|
||||
rust:
|
||||
- 1.42.0
|
||||
|
||||
jobs:
|
||||
include:
|
||||
- os: linux
|
||||
dist: bionic
|
||||
- os: osx
|
||||
|
||||
#cache: cargo
|
||||
|
||||
before_install:
|
||||
- if [ "$TRAVIS_OS_NAME" = "osx" ]; then brew update && brew install libxml2 jq; fi
|
||||
- if [ "$TRAVIS_OS_NAME" = "linux" ]; then sudo apt-get update && sudo apt install python3-pip; fi
|
||||
|
||||
before_script:
|
||||
- rustup component add clippy
|
||||
- cargo clippy --version
|
||||
- cargo install cargo-deb
|
||||
- which python3
|
||||
- python3 -V
|
||||
- pip3 install Flask
|
||||
- cd integration && python3 server.py&
|
||||
- sleep 2
|
||||
- export VERSION=$(grep '^version' Cargo.toml | cut -f2 -d'"')
|
||||
|
||||
script:
|
||||
- ./build.sh
|
||||
- cargo build --release --verbose
|
||||
- strip $PWD/target/release/hurl
|
||||
- strip $PWD/target/release/hurlfmt
|
||||
- export PATH="$PWD/target/release:$PATH"
|
||||
- integration/integration.sh
|
||||
- ci/create_tarballs target/upload "$TRAVIS_OS_NAME"
|
||||
- if [ "$TRAVIS_OS_NAME" = "linux" ]; then cargo deb && cp target/debian/*.deb target/upload; fi
|
||||
|
||||
|
||||
deploy:
|
||||
provider: script
|
||||
script: ci/deploy.sh
|
||||
skip_cleanup: true
|
||||
on:
|
||||
all_branch: true
|
||||
tags: true
|
||||
|
1941
Cargo.lock
generated
Normal file
1941
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
49
Cargo.toml
Normal file
49
Cargo.toml
Normal file
@ -0,0 +1,49 @@
|
||||
[package]
|
||||
name = "hurl"
|
||||
version = "0.99.12"
|
||||
authors = ["Fabrice Reix <fabrice.reix@orange.com>"]
|
||||
edition = "2018"
|
||||
build = "build.rs"
|
||||
|
||||
[lib]
|
||||
name = "hurl"
|
||||
|
||||
[features]
|
||||
# Treat warnings as a build error.
|
||||
strict = []
|
||||
|
||||
|
||||
[dependencies]
|
||||
clap = "2.33.0"
|
||||
structopt = "0.2.10"
|
||||
reqwest = "0.9.20"
|
||||
libxml = "0.2.12"
|
||||
regex = "1.1.0"
|
||||
serde_json = "1.0.40"
|
||||
xmlparser = "0.10.0"
|
||||
roxmltree = "0.7.1"
|
||||
serde-xml-rs = "0.3.1"
|
||||
atty = "0.2.13"
|
||||
url = "2.1.0"
|
||||
sxd-document = "0.3.2"
|
||||
serde = "1.0.104"
|
||||
percent-encoding = "2.1.0"
|
||||
cookie = "0.12.0"
|
||||
base64 = "0.11.0"
|
||||
float-cmp = "0.6.0"
|
||||
encoding = "0.2"
|
||||
chrono = "0.4.11"
|
||||
curl = "0.4.33"
|
||||
|
||||
#[dev-dependencies]
|
||||
proptest = "0.9.4"
|
||||
|
||||
[package.metadata.deb]
|
||||
assets = [
|
||||
["target/release/hurl", "usr/bin/", "755"],
|
||||
["target/release/hurlfmt", "usr/bin/", "755"],
|
||||
["target/man/hurl.1.gz", "usr/share/man/man1/hurl.1.gz", "644"],
|
||||
["target/man/hurlfmt.1.gz", "usr/share/man/man1/hurlfmt.1.gz", "644"]
|
||||
]
|
||||
|
||||
|
11
LICENSE.md
Normal file
11
LICENSE.md
Normal file
@ -0,0 +1,11 @@
|
||||
Hurl is licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
111
README.md
Normal file
111
README.md
Normal file
@ -0,0 +1,111 @@
|
||||
<a href="https://hurl.dev"><img src="https://raw.githubusercontent.com/Orange-OpenSource/hurl/master/docs/logo.svg?sanitize=true" align="center" width="264px"/></a>
|
||||
|
||||
<br/>
|
||||
|
||||
[![deploy status](https://travis-ci.org/Orange-OpenSource/hurl.svg?branch=master)](https://travis-ci.org/Orange-OpenSource/hurl/)
|
||||
[![documentation](https://img.shields.io/badge/-documentation-informational)](https://hurl.dev)
|
||||
|
||||
|
||||
# What's Hurl?
|
||||
|
||||
Hurl is a command line tool and a simple plain text format for describing an HTTP session.
|
||||
|
||||
Hurl is used in command lines or scripts to run HTTP sessions. Hurl can performs requests, capture values
|
||||
and evaluate queries on headers and body response. Hurl is very versatile: it can be used to get HTTP data and
|
||||
also to test HTTP sessions.
|
||||
|
||||
|
||||
```hurl
|
||||
# Get home:
|
||||
GET https://example.net
|
||||
|
||||
HTTP/1.1 200
|
||||
[Captures]
|
||||
csrf_token: xpath "string(//meta[@name='_csrf_token']/@content)"
|
||||
|
||||
# Do login!
|
||||
POST https://example.net/login?user=toto&password=1234
|
||||
X-CSRF-TOKEN: {{csrf_token}}
|
||||
|
||||
HTTP/1.1 302
|
||||
```
|
||||
|
||||
Chaining multiple requests is easy:
|
||||
|
||||
```hurl
|
||||
GET https://api.example.net/health
|
||||
GET https://api.example.net/health
|
||||
GET https://api.example.net/health
|
||||
GET https://api.example.net/health
|
||||
```
|
||||
|
||||
# Also an HTTP Test Tool
|
||||
|
||||
Hurl can run HTTP requests but can also be used to test HTTP responses.
|
||||
Different type of queries and predicates are supported, from [XPath](https://en.wikipedia.org/wiki/XPath)
|
||||
and [JSONPath](https://goessner.net/articles/JsonPath/) on body response, to assert on status code and response headers.
|
||||
|
||||
```hurl
|
||||
GET https://example.net
|
||||
|
||||
HTTP/1.1 200
|
||||
[Asserts]
|
||||
xpath "normalize-space(//head/title)" equals "Hello world!"
|
||||
```
|
||||
|
||||
and is well adapted for REST/json apis
|
||||
|
||||
```hurl
|
||||
POST https://api.example.net/tests
|
||||
{
|
||||
"id": "456",
|
||||
"evaluate": true
|
||||
}
|
||||
|
||||
HTTP/1.1 200
|
||||
[Asserts]
|
||||
jsonpath "$.status" equals "RUNNING" # Check the status code
|
||||
jsonpath "$.tests" countEquals 25 # Check the number of items
|
||||
|
||||
```
|
||||
|
||||
and even SOAP apis
|
||||
|
||||
```hurl
|
||||
POST https://example.net/InStock
|
||||
Content-Type: application/soap+xml; charset=utf-8
|
||||
SOAPAction: "http://www.w3.org/2003/05/soap-envelope"
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:m="http://www.example.org">
|
||||
<soap:Header></soap:Header>
|
||||
<soap:Body>
|
||||
<m:GetStockPrice>
|
||||
<m:StockName>GOOG</m:StockName>
|
||||
</m:GetStockPrice>
|
||||
</soap:Body>
|
||||
</soap:Envelope>
|
||||
|
||||
HTTP/1.1 200
|
||||
```
|
||||
|
||||
# Documentation
|
||||
|
||||
Visit the [Hurl web site](https://hurl.dev) to find out how to install and use Hurl.
|
||||
|
||||
- [Installation](https://hurl.dev/docs/installation.html)
|
||||
- [Samples](https://hurl.dev/docs/samples.html)
|
||||
- [File Format](https://hurl.dev/docs/entry.html)
|
||||
|
||||
|
||||
# Feedbacks
|
||||
|
||||
Hurl is still in beta, any feedback, suggestion, bugs or improvements are welcome.
|
||||
|
||||
```hurl
|
||||
POST https://hurl.dev/api/feedback
|
||||
{
|
||||
"name": "John Doe",
|
||||
"feedback": "Hurl is awesome !"
|
||||
}
|
||||
HTTP/1.1 200
|
||||
```
|
45
build.rs
Normal file
45
build.rs
Normal file
@ -0,0 +1,45 @@
|
||||
/*
|
||||
* hurl (https://hurl.dev)
|
||||
* Copyright (C) 2020 Orange
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
use std::process;
|
||||
|
||||
// objectives
|
||||
// get version
|
||||
// get git commit
|
||||
// get build date
|
||||
// => hard-code into your binaries
|
||||
fn main() {
|
||||
// Make the current git hash available to the build.
|
||||
if let Some(rev) = git_revision_hash() {
|
||||
println!("cargo:rustc-env=HURL_BUILD_GIT_HASH={}", rev);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fn git_revision_hash() -> Option<String> {
|
||||
let result = process::Command::new("git")
|
||||
.args(&["rev-parse", "--short=10", "HEAD"])
|
||||
.output();
|
||||
result.ok().and_then(|output| {
|
||||
let v = String::from_utf8_lossy(&output.stdout).trim().to_string();
|
||||
if v.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(v)
|
||||
}
|
||||
})
|
||||
}
|
27
build.sh
Executable file
27
build.sh
Executable file
@ -0,0 +1,27 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
|
||||
ROOT_DIR="$(dirname "$0")"
|
||||
|
||||
#cargo clean
|
||||
cargo build --features "strict"
|
||||
cargo test
|
||||
cargo doc --document-private-items
|
||||
|
||||
touch src/lib.rs
|
||||
cargo clippy -- -D warnings
|
||||
|
||||
DOCS_DIR="$ROOT_DIR/docs"
|
||||
MAN_DIR="$ROOT_DIR/target/man"
|
||||
mkdir -p "$MAN_DIR"
|
||||
cp "$DOCS_DIR/hurl.1" "$MAN_DIR"
|
||||
cp "$DOCS_DIR/hurlfmt.1" "$MAN_DIR"
|
||||
gzip "$MAN_DIR"/hurl.1
|
||||
gzip "$MAN_DIR"/hurlfmt.1
|
||||
|
||||
|
||||
echo
|
||||
echo "!!! Build successful !!!"
|
||||
|
||||
|
20
ci/check_tag
Executable file
20
ci/check_tag
Executable file
@ -0,0 +1,20 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
set -u
|
||||
|
||||
VERSION=$1
|
||||
|
||||
echo check git tag
|
||||
TAG=$(git tag --points-at HEAD |tr -d '\n')
|
||||
if [ "$TAG" == "" ]; then
|
||||
echo "Tag is not set"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ "$TAG" != "$VERSION" ]; then
|
||||
echo "Tag '$TAG' does not match version '$VERSION'"
|
||||
exit 1
|
||||
fi
|
||||
echo tag matches version
|
||||
|
||||
|
28
ci/create_tarballs
Executable file
28
ci/create_tarballs
Executable file
@ -0,0 +1,28 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
set -u
|
||||
|
||||
OUTPUT_DIR="$1"
|
||||
OS="$2"
|
||||
RELEASE_DIR=$PWD/target/release
|
||||
MAN_DIR=$PWD/target/man
|
||||
VERSION=$(grep '^version' Cargo.toml | cut -f2 -d'"')
|
||||
PACKAGE_DIR="hurl-$VERSION"
|
||||
TARBALL_FILE="hurl-$VERSION-x86_64-$OS.tar.gz"
|
||||
|
||||
mkdir -p "$OUTPUT_DIR"
|
||||
echo create tarballs into "$OUTPUT_DIR"
|
||||
cd "$OUTPUT_DIR"
|
||||
|
||||
rm -rf "$PACKAGE_DIR"
|
||||
mkdir "$PACKAGE_DIR"
|
||||
|
||||
cp "$RELEASE_DIR/hurl" "$PACKAGE_DIR"
|
||||
cp "$RELEASE_DIR/hurlfmt" "$PACKAGE_DIR"
|
||||
cp "$MAN_DIR"/* "$PACKAGE_DIR"
|
||||
|
||||
tar cvfz "$TARBALL_FILE" "$PACKAGE_DIR"
|
||||
rm -rf "$PACKAGE_DIR"
|
||||
|
||||
|
26
ci/deploy.sh
Executable file
26
ci/deploy.sh
Executable file
@ -0,0 +1,26 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
set -u
|
||||
|
||||
echo check git tag
|
||||
TAG=$(git tag --points-at HEAD |tr -d '\n')
|
||||
if [ "$TAG" == "" ]; then
|
||||
echo "Tag is not set"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ "$TAG" != "$VERSION" ]; then
|
||||
echo "Tag '$TAG' does not match version '$VERSION'"
|
||||
exit 1
|
||||
fi
|
||||
echo tag matches version
|
||||
|
||||
|
||||
ci/upload.sh "$VERSION" target/upload/*
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
45
ci/upload.sh
Executable file
45
ci/upload.sh
Executable file
@ -0,0 +1,45 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
set -u
|
||||
|
||||
tag=$1
|
||||
shift
|
||||
|
||||
API_URL="https://api.github.com"
|
||||
REPO_NAME=hurl
|
||||
repo_url="$API_URL/repos/Orange-OpenSource/$REPO_NAME"
|
||||
auth_header="Authorization: token $GITHUB_API_TOKEN"
|
||||
|
||||
echo "Uploading to $REPO_NAME for tag $tag"
|
||||
release_id=$(curl -s -H "$auth_header" "$repo_url/releases/tags/$tag" | jq '.id')
|
||||
upload_url=https://uploads.github.com/repos/Orange-OpenSource/$REPO_NAME/releases/$release_id/assets
|
||||
echo "release from tag: $release_id"
|
||||
|
||||
|
||||
asset_files=$*
|
||||
|
||||
for asset_file in $asset_files; do
|
||||
echo "Uploading asset file $asset_file"
|
||||
if [ ! -f "$asset_file" ]; then
|
||||
echo "does not exist!"
|
||||
exit 1
|
||||
fi
|
||||
asset_name=$(basename "$asset_file")
|
||||
asset_url="https://github.com/Orange-OpenSource/$REPO_NAME/releases/download/$VERSION/$asset_name"
|
||||
echo "asset url: $asset_url"
|
||||
|
||||
if curl --output /dev/null --silent --head --fail "$asset_url"; then
|
||||
echo "File already uploaded"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
curl -v -X POST \
|
||||
-H "$auth_header" \
|
||||
-H "Content-Type: application/octet-stream" \
|
||||
--data-binary "@$asset_file" "$upload_url?name=$asset_name"
|
||||
|
||||
done
|
||||
|
||||
|
||||
|
43
docs/gen_doc.py
Executable file
43
docs/gen_doc.py
Executable file
@ -0,0 +1,43 @@
|
||||
#!/usr/bin/env python3
|
||||
import sys
|
||||
import re
|
||||
|
||||
|
||||
def header():
|
||||
return '''---
|
||||
layout: doc
|
||||
title: Man Page
|
||||
---
|
||||
# {{ page.title }}
|
||||
'''
|
||||
|
||||
|
||||
def escape(s):
|
||||
return s.replace('<', '<').replace('--', '\\-\\-')
|
||||
|
||||
|
||||
def add_anchor_for_h2(s):
|
||||
lines = []
|
||||
p = re.compile('^## (.*)$')
|
||||
for line in s.split('\n'):
|
||||
m = p.match(line)
|
||||
if m:
|
||||
value = m.group(1)
|
||||
anchor = value.lower().strip().replace(' ', '-')
|
||||
lines.append('## ' + value + ' {#' + anchor + '}')
|
||||
else:
|
||||
lines.append(line)
|
||||
return '\n'.join(lines)
|
||||
|
||||
|
||||
def main():
|
||||
input_file = sys.argv[1]
|
||||
lines = open(input_file).readlines()
|
||||
s = ''.join(lines)
|
||||
s = escape(s)
|
||||
s = add_anchor_for_h2(s)
|
||||
print(header() + s)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
57
docs/gen_manpage.py
Executable file
57
docs/gen_manpage.py
Executable file
@ -0,0 +1,57 @@
|
||||
#!/usr/bin/env python3
|
||||
import sys
|
||||
import re
|
||||
|
||||
|
||||
def header(version):
|
||||
return '.TH hurl 1 "DATE" "hurl %s" " Hurl Manual"' % (version)
|
||||
|
||||
|
||||
def version():
|
||||
s = open('../Cargo.toml', 'r').read()
|
||||
p = re.compile('version(.*)"')
|
||||
p = re.compile('(.*)', re.MULTILINE)
|
||||
m = p.match(s)
|
||||
return '0.99'
|
||||
|
||||
|
||||
def process_code_block(s):
|
||||
p = re.compile("```(.*?)```" , re.DOTALL )
|
||||
return p.sub('\\\\f[C]\\1\\\\f[R]', s)
|
||||
|
||||
|
||||
def convert_md(s):
|
||||
|
||||
p = re.compile('^###\s+(.*)')
|
||||
s = p.sub('.IP "\\1"', s)
|
||||
|
||||
p = re.compile('^##')
|
||||
s = p.sub('.SH', s)
|
||||
|
||||
|
||||
p = re.compile('\*\*(.*)\*\*\s+')
|
||||
s = p.sub('.B \\1\n', s)
|
||||
|
||||
|
||||
# Remove link Text
|
||||
p = re.compile('\[(.*)\]\(.*\)')
|
||||
s = p.sub('\\\\fI\\1\\\\fP', s)
|
||||
|
||||
# Remove local anchor
|
||||
p = re.compile('{#.*}')
|
||||
s = p.sub('', s)
|
||||
return s
|
||||
|
||||
|
||||
def main():
|
||||
input_file = sys.argv[1]
|
||||
data = open(input_file).readlines()
|
||||
print(header(version()))
|
||||
|
||||
s = ''.join([convert_md(line) for line in data])
|
||||
#s = process_code_block(s)
|
||||
print(s)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
322
docs/hurl.1
Normal file
322
docs/hurl.1
Normal file
@ -0,0 +1,322 @@
|
||||
.TH hurl 1 "DATE" "hurl 0.99" " Hurl Manual"
|
||||
.SH NAME
|
||||
|
||||
hurl - run and test HTTP requests.
|
||||
|
||||
|
||||
.SH SYNOPSIS
|
||||
|
||||
.B hurl
|
||||
[options] [FILE...]
|
||||
|
||||
|
||||
.SH DESCRIPTION
|
||||
|
||||
.B Hurl
|
||||
is an HTTP client that performs HTTP requests defined in a simple plain text format.
|
||||
|
||||
Hurl is very versatile, it enables to chain HTTP requests, capture values from HTTP responses and make asserts.
|
||||
|
||||
|
||||
$ hurl session.hurl
|
||||
|
||||
|
||||
If no input-files are specified, input is read from stdin.
|
||||
|
||||
|
||||
$ echo GET http://httpbin.org/get | hurl
|
||||
{
|
||||
"args": {},
|
||||
"headers": {
|
||||
"Accept": "*/*",
|
||||
"Accept-Encoding": "gzip",
|
||||
"Content-Length": "0",
|
||||
"Host": "httpbin.org",
|
||||
"User-Agent": "hurl/0.99.10",
|
||||
"X-Amzn-Trace-Id": "Root=1-5eedf4c7-520814d64e2f9249ea44e0f0"
|
||||
},
|
||||
"origin": "1.2.3.4",
|
||||
"url": "http://httpbin.org/get"
|
||||
}
|
||||
|
||||
|
||||
|
||||
Output goes to stdout by default. For output to a file, use the -o option:
|
||||
|
||||
|
||||
$ hurl -o output input.hurl
|
||||
|
||||
|
||||
By default, Hurl executes all the HTTP requests and output the response body of the last http call.
|
||||
|
||||
|
||||
|
||||
.SH HURL FILE FORMAT
|
||||
|
||||
The Hurl file format is fully documented in \fIhttps://hurl.dev/docs/hurl-file.html\fP
|
||||
|
||||
It consists of one or several HTTP requests
|
||||
|
||||
GET http:/example.net/endpoint1
|
||||
GET http:/example.net/endpoint2
|
||||
|
||||
|
||||
|
||||
.IP "Capturing values"
|
||||
|
||||
A value from an HTTP response can be-reused for successive HTTP requests.
|
||||
|
||||
A typical example occurs with csrf tokens.
|
||||
|
||||
GET https://example.net
|
||||
HTTP/1.1 200
|
||||
# Capture the CSRF token value from html body.
|
||||
[Captures]
|
||||
csrf_token: xpath "normalize-space(//meta[@name='_csrf_token']/@content)"
|
||||
|
||||
# Do the login !
|
||||
POST https://example.net/login?user=toto&password=1234
|
||||
X-CSRF-TOKEN: {{csrf_token}}
|
||||
|
||||
|
||||
.IP "Asserts"
|
||||
|
||||
The HTTP response defined in the Hurl session are used to make asserts.
|
||||
|
||||
At the minimum, the response includes the asserts on the HTTP version and status code.
|
||||
|
||||
GET http:/google.com
|
||||
HTTP/1.1 302
|
||||
|
||||
|
||||
It can also include asserts on the response headers
|
||||
|
||||
GET http:/google.com
|
||||
HTTP/1.1 302
|
||||
Location: http://www.google.com
|
||||
|
||||
|
||||
You can also include explicit asserts combining query and predicate
|
||||
|
||||
GET http:/google.com
|
||||
HTTP/1.1 302
|
||||
[Asserts]
|
||||
xpath "//title" equals "301 Moved"
|
||||
|
||||
|
||||
Thanks to asserts, Hurl can be used as a testing tool to run scenarii.
|
||||
|
||||
|
||||
|
||||
|
||||
.SH OPTIONS
|
||||
|
||||
Options that exist in curl have exactly the same semantic.
|
||||
|
||||
|
||||
.IP "--append "
|
||||
|
||||
This option can only be used with \fI--json\fP. It appends sessions to existing file instead of overwriting it.
|
||||
This is typically used in a CI pipeline.
|
||||
|
||||
|
||||
.IP "--color "
|
||||
|
||||
Colorize Output
|
||||
|
||||
|
||||
|
||||
.IP "-b, --cookie <filename> "
|
||||
|
||||
Read cookies from file (using the Netscape cookie file format).
|
||||
|
||||
Combined with \fI-c, --cookie-jar\fP, you can simulate a cookie storage between successive Hurl runs.
|
||||
|
||||
|
||||
|
||||
.IP "-c, --cookie-jar <filename> "
|
||||
|
||||
Write cookies to FILE after running the session (only for one session).
|
||||
The file will be written using the Netscape cookie file format.
|
||||
|
||||
Combined with \fI-b, --cookie\fP,you can simulate a cookie storage between successive Hurl runs.
|
||||
|
||||
|
||||
|
||||
.IP "--fail-at-end "
|
||||
|
||||
Continue executing requests to the end of the Hurl file even when an assert error occurs.
|
||||
By default, Hurl exits after an assert error in the HTTP response.
|
||||
|
||||
Note that this option does not affect the behavior with mutiple input Hurl files.
|
||||
|
||||
All the input files are executed independently. The result of one file does not affect the excecution of the other Hurl files.
|
||||
|
||||
|
||||
.IP "--file-root <dir> "
|
||||
|
||||
Set root filesystem to import files in Hurl. This is used for both files in multipart form data and request body.
|
||||
When this is not explicitly defined, the files are relative to the current directory in which Hurl is running.
|
||||
|
||||
|
||||
|
||||
|
||||
.IP "-h, --help "
|
||||
|
||||
Usage help. This lists all current command line options with a short description.
|
||||
|
||||
|
||||
|
||||
.IP "--html <dir> "
|
||||
|
||||
Generate html report in dir.
|
||||
|
||||
If you want to combine results from different Hurl executions in a unique html report, you must also use the options \fI--json](#json) and [--append\fP.
|
||||
|
||||
|
||||
|
||||
.IP "-i, --include "
|
||||
|
||||
Include the HTTP headers in the output.
|
||||
|
||||
|
||||
|
||||
.IP "--json <file> "
|
||||
|
||||
Write full session(s) to a json file. The format is very closed to HAR format.
|
||||
|
||||
By default, this file is overwritten by the current run execution.
|
||||
In order to append sessions to an existing json file, the option \fI--append\fP must be used.
|
||||
This is typically used in a CI pipeline.
|
||||
|
||||
|
||||
|
||||
.IP "-k, --insecure "
|
||||
|
||||
This option explicitly allows Hurl to perform "insecure" SSL connections and transfers.
|
||||
|
||||
|
||||
|
||||
.IP "-L, --location "
|
||||
|
||||
Follow redirect. You can limit the amount of redirects to follow by using the \fI--max-redirs\fP option.
|
||||
|
||||
|
||||
|
||||
.IP "--max-redirs <num> "
|
||||
|
||||
Set maximum number of redirection-followings allowed
|
||||
By default, the limit is set to 50 redirections. Set this option to -1 to make it unlimited.
|
||||
|
||||
|
||||
.IP "--no-color "
|
||||
|
||||
Do not colorize Output
|
||||
|
||||
|
||||
|
||||
.IP "--noproxy <no-proxy-list> "
|
||||
|
||||
Comma-separated list of hosts which do not use a proxy.
|
||||
Override value from Environment variable no_proxy.
|
||||
|
||||
|
||||
|
||||
.IP "--to-entry <entry-number> "
|
||||
|
||||
Execute Hurl file to ENTRY_NUMBER (starting at 1).
|
||||
Ignore the remaining of the file. It is useful for debugging a session.
|
||||
|
||||
|
||||
|
||||
.IP "-o, --output <file> "
|
||||
|
||||
Write output to <file> instead of stdout.
|
||||
|
||||
|
||||
|
||||
.IP "-x, --proxy [protocol://]host[:port] "
|
||||
|
||||
Use the specified proxy.
|
||||
|
||||
|
||||
|
||||
.IP "--variable <name=value> "
|
||||
|
||||
Define variable (name/value) to be used in Hurl templates.
|
||||
Only string values can be defined.
|
||||
|
||||
|
||||
|
||||
.IP "-v, --verbose "
|
||||
|
||||
Turn on verbose output on standard error stream
|
||||
Useful for debugging.
|
||||
|
||||
A line starting with '>' means data sent by Hurl.
|
||||
A line staring with '<' means data received by Hurl.
|
||||
A line starting with '*' means additional info provided by Hurl.
|
||||
|
||||
If you only want HTTP headers in the output, -i, --include might be the option you're looking for.
|
||||
|
||||
|
||||
.IP "-V, --version "
|
||||
|
||||
Prints version information
|
||||
|
||||
|
||||
|
||||
.SH ENVIRONMENT
|
||||
|
||||
Environment variables can only be specified in lowercase.
|
||||
|
||||
Using an environment variable to set the proxy has the same effect as using
|
||||
the \fI-x, --proxy\fP option.
|
||||
|
||||
.IP "http_proxy [protocol://]<host>[:port]"
|
||||
|
||||
Sets the proxy server to use for HTTP.
|
||||
|
||||
|
||||
.IP "https_proxy [protocol://]<host>[:port]"
|
||||
|
||||
Sets the proxy server to use for HTTPS.
|
||||
|
||||
|
||||
.IP "all_proxy [protocol://]<host>[:port]"
|
||||
|
||||
Sets the proxy server to use if no protocol-specific proxy is set.
|
||||
|
||||
.IP "no_proxy <comma-separated list of hosts>"
|
||||
|
||||
list of host names that shouldn't go through any proxy.
|
||||
|
||||
|
||||
.SH EXIT CODES
|
||||
|
||||
.IP "1"
|
||||
Failed to parse command-line options.
|
||||
|
||||
|
||||
.IP "2"
|
||||
Input File Parsing Error.
|
||||
|
||||
|
||||
.IP "3"
|
||||
Runtime error (such as failure to connect to host).
|
||||
|
||||
|
||||
.IP "4"
|
||||
Assert Error.
|
||||
|
||||
|
||||
|
||||
.SH WWW
|
||||
|
||||
\fIhttps://hurl.dev\fP
|
||||
|
||||
|
||||
.SH SEE ALSO
|
||||
|
||||
curl(1) hurlfmt(1)
|
||||
|
318
docs/hurl.md
Normal file
318
docs/hurl.md
Normal file
@ -0,0 +1,318 @@
|
||||
## NAME
|
||||
|
||||
hurl - run and test HTTP requests.
|
||||
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
**hurl** [options] [FILE...]
|
||||
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
**Hurl** is an HTTP client that performs HTTP requests defined in a simple plain text format.
|
||||
|
||||
Hurl is very versatile, it enables to chain HTTP requests, capture values from HTTP responses and make asserts.
|
||||
|
||||
|
||||
$ hurl session.hurl
|
||||
|
||||
|
||||
If no input-files are specified, input is read from stdin.
|
||||
|
||||
|
||||
$ echo GET http://httpbin.org/get | hurl
|
||||
{
|
||||
"args": {},
|
||||
"headers": {
|
||||
"Accept": "*/*",
|
||||
"Accept-Encoding": "gzip",
|
||||
"Content-Length": "0",
|
||||
"Host": "httpbin.org",
|
||||
"User-Agent": "hurl/0.99.10",
|
||||
"X-Amzn-Trace-Id": "Root=1-5eedf4c7-520814d64e2f9249ea44e0f0"
|
||||
},
|
||||
"origin": "1.2.3.4",
|
||||
"url": "http://httpbin.org/get"
|
||||
}
|
||||
|
||||
|
||||
|
||||
Output goes to stdout by default. For output to a file, use the -o option:
|
||||
|
||||
|
||||
$ hurl -o output input.hurl
|
||||
|
||||
|
||||
By default, Hurl executes all the HTTP requests and output the response body of the last http call.
|
||||
|
||||
|
||||
|
||||
## HURL FILE FORMAT
|
||||
|
||||
The Hurl file format is fully documented in [https://hurl.dev/docs/hurl-file.html](https://hurl.dev/docs/hurl-file.html)
|
||||
|
||||
It consists of one or several HTTP requests
|
||||
|
||||
GET http:/example.net/endpoint1
|
||||
GET http:/example.net/endpoint2
|
||||
|
||||
|
||||
|
||||
### Capturing values
|
||||
|
||||
A value from an HTTP response can be-reused for successive HTTP requests.
|
||||
|
||||
A typical example occurs with csrf tokens.
|
||||
|
||||
GET https://example.net
|
||||
HTTP/1.1 200
|
||||
# Capture the CSRF token value from html body.
|
||||
[Captures]
|
||||
csrf_token: xpath "normalize-space(//meta[@name='_csrf_token']/@content)"
|
||||
|
||||
# Do the login !
|
||||
POST https://example.net/login?user=toto&password=1234
|
||||
X-CSRF-TOKEN: {{csrf_token}}
|
||||
|
||||
|
||||
### Asserts
|
||||
|
||||
The HTTP response defined in the Hurl session are used to make asserts.
|
||||
|
||||
At the minimum, the response includes the asserts on the HTTP version and status code.
|
||||
|
||||
GET http:/google.com
|
||||
HTTP/1.1 302
|
||||
|
||||
|
||||
It can also include asserts on the response headers
|
||||
|
||||
GET http:/google.com
|
||||
HTTP/1.1 302
|
||||
Location: http://www.google.com
|
||||
|
||||
|
||||
You can also include explicit asserts combining query and predicate
|
||||
|
||||
GET http:/google.com
|
||||
HTTP/1.1 302
|
||||
[Asserts]
|
||||
xpath "//title" equals "301 Moved"
|
||||
|
||||
|
||||
Thanks to asserts, Hurl can be used as a testing tool to run scenarii.
|
||||
|
||||
|
||||
|
||||
|
||||
## OPTIONS
|
||||
|
||||
Options that exist in curl have exactly the same semantic.
|
||||
|
||||
|
||||
### --append {#append}
|
||||
|
||||
This option can only be used with [--json](#json). It appends sessions to existing file instead of overwriting it.
|
||||
This is typically used in a CI pipeline.
|
||||
|
||||
|
||||
### --color {#color}
|
||||
|
||||
Colorize Output
|
||||
|
||||
|
||||
|
||||
### -b, --cookie <filename> {#cookie}
|
||||
|
||||
Read cookies from file (using the Netscape cookie file format).
|
||||
|
||||
Combined with [-c, --cookie-jar](#cookie-jar), you can simulate a cookie storage between successive Hurl runs.
|
||||
|
||||
|
||||
|
||||
### -c, --cookie-jar <filename> {#cookie-jar}
|
||||
|
||||
Write cookies to FILE after running the session (only for one session).
|
||||
The file will be written using the Netscape cookie file format.
|
||||
|
||||
Combined with [-b, --cookie](#cookie),you can simulate a cookie storage between successive Hurl runs.
|
||||
|
||||
|
||||
|
||||
### --fail-at-end {#fail-at-end}
|
||||
|
||||
Continue executing requests to the end of the Hurl file even when an assert error occurs.
|
||||
By default, Hurl exits after an assert error in the HTTP response.
|
||||
|
||||
Note that this option does not affect the behavior with mutiple input Hurl files.
|
||||
|
||||
All the input files are executed independently. The result of one file does not affect the excecution of the other Hurl files.
|
||||
|
||||
|
||||
### --file-root <dir> {#file-root}
|
||||
|
||||
Set root filesystem to import files in Hurl. This is used for both files in multipart form data and request body.
|
||||
When this is not explicitly defined, the files are relative to the current directory in which Hurl is running.
|
||||
|
||||
|
||||
|
||||
|
||||
### -h, --help {#help}
|
||||
|
||||
Usage help. This lists all current command line options with a short description.
|
||||
|
||||
|
||||
|
||||
### --html <dir> {#html}
|
||||
|
||||
Generate html report in dir.
|
||||
|
||||
If you want to combine results from different Hurl executions in a unique html report, you must also use the options [--json](#json) and [--append](#append).
|
||||
|
||||
|
||||
|
||||
### -i, --include {#include}
|
||||
|
||||
Include the HTTP headers in the output.
|
||||
|
||||
|
||||
|
||||
### --json <file> {#json}
|
||||
|
||||
Write full session(s) to a json file. The format is very closed to HAR format.
|
||||
|
||||
By default, this file is overwritten by the current run execution.
|
||||
In order to append sessions to an existing json file, the option [--append](#append) must be used.
|
||||
This is typically used in a CI pipeline.
|
||||
|
||||
|
||||
|
||||
### -k, --insecure {#insecure}
|
||||
|
||||
This option explicitly allows Hurl to perform "insecure" SSL connections and transfers.
|
||||
|
||||
|
||||
|
||||
### -L, --location {#location}
|
||||
|
||||
Follow redirect. You can limit the amount of redirects to follow by using the [--max-redirs](#max-redirs) option.
|
||||
|
||||
|
||||
|
||||
### --max-redirs <num> {#max-redirs}
|
||||
|
||||
Set maximum number of redirection-followings allowed
|
||||
By default, the limit is set to 50 redirections. Set this option to -1 to make it unlimited.
|
||||
|
||||
|
||||
### --no-color {#color}
|
||||
|
||||
Do not colorize Output
|
||||
|
||||
|
||||
|
||||
### --noproxy <no-proxy-list> {#noproxy}
|
||||
|
||||
Comma-separated list of hosts which do not use a proxy.
|
||||
Override value from Environment variable no_proxy.
|
||||
|
||||
|
||||
|
||||
### --to-entry <entry-number> {#to-entry}
|
||||
|
||||
Execute Hurl file to ENTRY_NUMBER (starting at 1).
|
||||
Ignore the remaining of the file. It is useful for debugging a session.
|
||||
|
||||
|
||||
|
||||
### -o, --output <file> {#output}
|
||||
|
||||
Write output to <file> instead of stdout.
|
||||
|
||||
|
||||
|
||||
### -x, --proxy [protocol://]host[:port] {#proxy}
|
||||
|
||||
Use the specified proxy.
|
||||
|
||||
|
||||
|
||||
### --variable <name=value> {#variables}
|
||||
|
||||
Define variable (name/value) to be used in Hurl templates.
|
||||
Only string values can be defined.
|
||||
|
||||
|
||||
|
||||
### -v, --verbose {#verbose}
|
||||
|
||||
Turn on verbose output on standard error stream
|
||||
Useful for debugging.
|
||||
|
||||
A line starting with '>' means data sent by Hurl.
|
||||
A line staring with '<' means data received by Hurl.
|
||||
A line starting with '*' means additional info provided by Hurl.
|
||||
|
||||
If you only want HTTP headers in the output, -i, --include might be the option you're looking for.
|
||||
|
||||
|
||||
### -V, --version {#version}
|
||||
|
||||
Prints version information
|
||||
|
||||
|
||||
|
||||
## ENVIRONMENT
|
||||
|
||||
Environment variables can only be specified in lowercase.
|
||||
|
||||
Using an environment variable to set the proxy has the same effect as using
|
||||
the [-x, --proxy](#proxy) option.
|
||||
|
||||
### http_proxy [protocol://]<host>[:port]
|
||||
|
||||
Sets the proxy server to use for HTTP.
|
||||
|
||||
|
||||
### https_proxy [protocol://]<host>[:port]
|
||||
|
||||
Sets the proxy server to use for HTTPS.
|
||||
|
||||
|
||||
### all_proxy [protocol://]<host>[:port]
|
||||
|
||||
Sets the proxy server to use if no protocol-specific proxy is set.
|
||||
|
||||
### no_proxy <comma-separated list of hosts>
|
||||
|
||||
list of host names that shouldn't go through any proxy.
|
||||
|
||||
|
||||
## EXIT CODES
|
||||
|
||||
### 1
|
||||
Failed to parse command-line options.
|
||||
|
||||
|
||||
### 2
|
||||
Input File Parsing Error.
|
||||
|
||||
|
||||
### 3
|
||||
Runtime error (such as failure to connect to host).
|
||||
|
||||
|
||||
### 4
|
||||
Assert Error.
|
||||
|
||||
|
||||
|
||||
## WWW
|
||||
|
||||
[https://hurl.dev](https://hurl.dev)
|
||||
|
||||
|
||||
## SEE ALSO
|
||||
|
||||
curl(1) hurlfmt(1)
|
66
docs/hurlfmt.1
Normal file
66
docs/hurlfmt.1
Normal file
@ -0,0 +1,66 @@
|
||||
.TH hurl 1 "DATE" "hurl 0.99" " Hurl Manual"
|
||||
.SH NAME
|
||||
|
||||
hurlfmt - format Hurl files
|
||||
|
||||
|
||||
.SH SYNOPSIS
|
||||
|
||||
.B hurlfmt
|
||||
[options] [FILE...]
|
||||
|
||||
|
||||
.SH DESCRIPTION
|
||||
|
||||
.B hurlfmt
|
||||
formats Hurl files.
|
||||
|
||||
|
||||
|
||||
.SH OPTIONS
|
||||
|
||||
|
||||
|
||||
.IP "--color "
|
||||
|
||||
Colorize Output
|
||||
|
||||
|
||||
.IP "-h, --help "
|
||||
|
||||
Usage help. This lists all current command line options with a short description.
|
||||
|
||||
|
||||
|
||||
.IP "--html "
|
||||
|
||||
Generate html output.
|
||||
|
||||
|
||||
|
||||
.IP "-V, --version "
|
||||
|
||||
Prints version information
|
||||
|
||||
|
||||
|
||||
|
||||
.SH EXIT CODES
|
||||
|
||||
.IP "1"
|
||||
Failed to parse command-line options.
|
||||
|
||||
|
||||
.IP "2"
|
||||
Input File Parsing Error.
|
||||
|
||||
|
||||
.SH WWW
|
||||
|
||||
\fIhttps://hurl.dev\fP
|
||||
|
||||
|
||||
.SH SEE ALSO
|
||||
|
||||
hurl(1)
|
||||
|
62
docs/hurlfmt.md
Normal file
62
docs/hurlfmt.md
Normal file
@ -0,0 +1,62 @@
|
||||
## NAME
|
||||
|
||||
hurlfmt - format Hurl files
|
||||
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
**hurlfmt** [options] [FILE...]
|
||||
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
**hurlfmt** formats Hurl files.
|
||||
|
||||
|
||||
|
||||
## OPTIONS
|
||||
|
||||
|
||||
|
||||
### --color {#color}
|
||||
|
||||
Colorize Output
|
||||
|
||||
|
||||
### -h, --help {#help}
|
||||
|
||||
Usage help. This lists all current command line options with a short description.
|
||||
|
||||
|
||||
|
||||
### --html {#html}
|
||||
|
||||
Generate html output.
|
||||
|
||||
|
||||
|
||||
### -V, --version {#version}
|
||||
|
||||
Prints version information
|
||||
|
||||
|
||||
|
||||
|
||||
## EXIT CODES
|
||||
|
||||
### 1
|
||||
Failed to parse command-line options.
|
||||
|
||||
|
||||
### 2
|
||||
Input File Parsing Error.
|
||||
|
||||
|
||||
## WWW
|
||||
|
||||
[https://hurl.dev](https://hurl.dev)
|
||||
|
||||
|
||||
## SEE ALSO
|
||||
|
||||
hurl(1)
|
5
docs/logo.svg
Normal file
5
docs/logo.svg
Normal file
@ -0,0 +1,5 @@
|
||||
<svg width="704px" height="183px" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g style=""> <path d="M498.000000,33.000000 L652.046555,33.000000 C652.051235,14.993509,652.042435,0.117931,652.000665,0.000622 L703.499993,48.000000 L651.999993,96.000000 C651.999993,96.000000,652.015443,82.165430,652.028753,65.000000 L497.999990,65.000000 Z M652.000000,0.000000 C652.000220,-0.000210,652.000440,0.000000,652.000670,0.000620 Z " style="fill: rgba(251, 0, 117, 1.000000); " fill="#ff0187" stroke="none" />
|
||||
</g><g style=""> <path d="M695.000000,115.000000 L540.953445,115.000000 C540.948765,96.993509,540.957565,82.117931,540.999335,82.000622 L489.500007,130.000000 L541.000007,178.000000 C541.000007,178.000000,540.984557,164.165430,540.971247,147.000000 L695.000010,147.000000 Z M541.000000,82.000000 C540.999780,81.999790,540.999560,82.000000,540.999330,82.000620 Z " style="fill: rgba(251, 0, 117, 1.000000); " fill="#ff0187" stroke="none" />
|
||||
</g><g style=""> <path d="M0.250000,0.500000 L0.250000,179.000000 L39.500000,179.000000 L39.500000,102.000000 L111.750000,102.000000 L111.750000,179.000000 L151.000000,179.000000 L151.000000,0.500000 L111.750000,0.500000 L111.750000,69.000000 L39.500000,69.000000 L39.500000,0.500000 Z M303.000000,179.000000 L303.000000,49.750000 L267.500000,49.750000 L267.500000,117.500000 C267.500000,130.666733,265.333355,140.124971,261.000000,145.875000 C256.666645,151.625029,249.666715,154.500000,240.000000,154.500000 C231.499957,154.500000,225.583350,151.875026,222.250000,146.625000 C218.916650,141.374974,217.250000,133.416720,217.250000,122.750000 L217.250000,49.750000 L181.750000,49.750000 L181.750000,129.250000 C181.750000,137.250040,182.458326,144.541634,183.875000,151.125000 C185.291674,157.708366,187.749982,163.291644,191.250000,167.875000 C194.750018,172.458356,199.541636,175.999988,205.625000,178.500000 C211.708364,181.000013,219.499953,182.250000,229.000000,182.250000 C236.500037,182.250000,243.833298,180.583350,251.000000,177.250000 C258.166702,173.916650,263.999977,168.500038,268.500000,161.000000 L269.250000,161.000000 L269.250000,179.000000 Z M330.000000,49.750000 L330.000000,179.000000 L365.500000,179.000000 L365.500000,120.750000 C365.500000,114.916637,366.083327,109.500025,367.250000,104.500000 C368.416673,99.499975,370.374986,95.125019,373.125000,91.375000 C375.875014,87.624981,379.499977,84.666677,384.000000,82.500000 C388.500023,80.333323,393.999968,79.250000,400.500000,79.250000 C402.666677,79.250000,404.916655,79.374999,407.250000,79.625000 C409.583345,79.875001,411.583325,80.166665,413.250000,80.500000 L413.250000,47.500000 C410.416652,46.666662,407.833345,46.250000,405.500000,46.250000 C400.999977,46.250000,396.666687,46.916660,392.500000,48.250000 C388.333313,49.583340,384.416685,51.458321,380.750000,53.875000 C377.083315,56.291679,373.833348,59.208316,371.000000,62.625000 C368.166652,66.041684,365.916675,69.749980,364.250000,73.750000 L363.750000,73.750000 L363.750000,49.750000 Z M428.250000,0.500000 L428.250000,179.000000 L463.750000,179.000000 L463.750000,0.500000 Z " style="fill: rgba(38, 38, 38, 1.000000); stroke-width: 0.000000px; stroke: rgba(0, 0, 0, 1.000000); " fill="#333333" stroke="#000000" stroke-width="0.000000" />
|
||||
</g></svg>
|
After Width: | Height: | Size: 3.2 KiB |
6
docs/update.sh
Executable file
6
docs/update.sh
Executable file
@ -0,0 +1,6 @@
|
||||
#!/bin/bash
|
||||
./gen_manpage.py hurl.md > hurl.1
|
||||
./gen_manpage.py hurlfmt.md > hurlfmt.1
|
||||
./gen_doc.py hurl.md > ../../hurl-dev/sites/hurl.dev/_docs/man-page.md
|
||||
|
||||
|
13
integration/generate_html
Executable file
13
integration/generate_html
Executable file
@ -0,0 +1,13 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
mkdir -p target
|
||||
find tests -name "*.hurl" | while read -r hurl_file; do
|
||||
echo "$hurl_file"
|
||||
html_file=target/"${hurl_file%.*}".html
|
||||
echo "Generating $html_file"
|
||||
mkdir -p "$(dirname "$html_file")"
|
||||
hurlfmt --html --standalone "$hurl_file" > "$html_file"
|
||||
done
|
||||
|
||||
|
16
integration/hurl_echo
Executable file
16
integration/hurl_echo
Executable file
@ -0,0 +1,16 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
for input_file in "$@"; do
|
||||
echo "$input_file"
|
||||
output_file="/tmp/$(basename "$input_file")"
|
||||
hurlfmt --no-format "$input_file" >"$output_file"
|
||||
|
||||
expected=$(cat "$input_file")
|
||||
actual=$(cat "$output_file")
|
||||
if [ "$actual" != "$expected" ]; then
|
||||
echo "=> Difference!"
|
||||
diff "$output_file" "$input_file"
|
||||
exit &
|
||||
fi
|
||||
done
|
14
integration/hurl_output.sh
Executable file
14
integration/hurl_output.sh
Executable file
@ -0,0 +1,14 @@
|
||||
#!/bin/bash
|
||||
|
||||
for command_file in "$@"; do
|
||||
echo "$command_file"
|
||||
command=$(cat $command_file)
|
||||
expected=$(cat ${command_file%.*}.output)
|
||||
output="$($command 2>&1)"
|
||||
|
||||
if [ "$output" != "$expected" ]; then
|
||||
diff <(echo "$output" ) <(echo "$expected")
|
||||
exit 1
|
||||
fi
|
||||
|
||||
done
|
18
integration/integration.sh
Executable file
18
integration/integration.sh
Executable file
@ -0,0 +1,18 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
# Static Analysis
|
||||
./hurl_echo tests/*.hurl tests_error_lint/*.hurl
|
||||
./lint.sh tests_error_lint/*.hurl
|
||||
./generate_html
|
||||
|
||||
# Dynamic
|
||||
./run.sh tests/*.hurl tests_error_parser/*.hurl
|
||||
#./hurl_output.sh output/*.command
|
||||
|
||||
set +e
|
||||
rm -rf report/*
|
||||
hurl --json report/tests.json --html report/html --output /dev/null tests/*.hurl
|
||||
|
||||
echo "test integration ok!"
|
46
integration/lint.sh
Executable file
46
integration/lint.sh
Executable file
@ -0,0 +1,46 @@
|
||||
#!/bin/bash
|
||||
set -u
|
||||
|
||||
EXITCODE_EXPECTED=1
|
||||
|
||||
for hurl_file in "$@"; do
|
||||
echo "$hurl_file";
|
||||
set +e
|
||||
hurlfmt --color --check "$hurl_file" 2>/tmp/test.stderr
|
||||
|
||||
EXITCODE_ACTUAL=$?
|
||||
set -e
|
||||
if [ "$EXITCODE_ACTUAL" != "$EXITCODE_EXPECTED" ]; then
|
||||
echo "ERROR Exit Code"
|
||||
echo " Expected: $EXITCODE_EXPECTED"
|
||||
echo " Actual: $EXITCODE_ACTUAL"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
STDERR_ACTUAL=$(cat /tmp/test.stderr)
|
||||
STDERR_EXPECTED=$(cat ${hurl_file%%.*}.err)
|
||||
diff ${hurl_file%%.*}.err /tmp/test.stderr
|
||||
if [ "$STDERR_ACTUAL" != "$STDERR_EXPECTED" ]; then
|
||||
echo "ERROR stderr"
|
||||
echo " expected:"
|
||||
echo "$STDERR_EXPECTED" | perl -pe 'chomp;s/.*/ $_\n/'
|
||||
echo " actual:"
|
||||
echo "$STDERR_ACTUAL" | perl -pe 'chomp;s/.*/ $_\n/'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
hurlfmt "$hurl_file" --no-color >/tmp/test.lint
|
||||
LINT_ACTUAL=$(cat /tmp/test.lint)
|
||||
LINT_EXPECTED=$(cat ${hurl_file%%.*}.hurl.lint)
|
||||
if [ "$LINT_ACTUAL" != "$LINT_EXPECTED" ]; then
|
||||
echo "ERROR linting"
|
||||
echo " expected:"
|
||||
echo "$LINT_EXPECTED" | perl -pe 'chomp;s/.*/ $_\n/'
|
||||
echo " actual:"
|
||||
echo "$LINT_ACTUAL" | perl -pe 'chomp;s/.*/ $_\n/'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
done
|
||||
|
||||
|
1
integration/output/default.command
Normal file
1
integration/output/default.command
Normal file
@ -0,0 +1 @@
|
||||
hurl tests/output.hurl
|
1
integration/output/default.output
Normal file
1
integration/output/default.output
Normal file
@ -0,0 +1 @@
|
||||
Response endpoint2
|
1
integration/output/include.command
Normal file
1
integration/output/include.command
Normal file
@ -0,0 +1 @@
|
||||
hurl --include tests/output.hurl
|
7
integration/output/include.output
Normal file
7
integration/output/include.output
Normal file
@ -0,0 +1,7 @@
|
||||
HTTP/1.0 200
|
||||
date: DATE2
|
||||
content-type: text/html; charset=utf-8
|
||||
content-length: 19
|
||||
server: Werkzeug/0.16.1 Python/3.5.3
|
||||
|
||||
Response endpoint2
|
1
integration/output/verbose.command
Normal file
1
integration/output/verbose.command
Normal file
@ -0,0 +1 @@
|
||||
hurl --verbose tests/output.hurl
|
33
integration/output/verbose.output
Normal file
33
integration/output/verbose.output
Normal file
@ -0,0 +1,33 @@
|
||||
* Fail fast: true
|
||||
* variables: {}
|
||||
* ------------------------------------------------------------------------------
|
||||
* executing entry 1
|
||||
> POST http://localhost:8000/output/endpoint1
|
||||
> Content-Type: application/json
|
||||
> User-Agent: hurl/0.99.8
|
||||
> Host: localhost
|
||||
>
|
||||
> { "user": "bob" }
|
||||
< HTTP/1.0 200
|
||||
< date: DATE1
|
||||
< content-type: text/html; charset=utf-8
|
||||
< content-length: 19
|
||||
< server: Werkzeug/0.16.1 Python/3.5.3
|
||||
<
|
||||
< Response endpoint1
|
||||
<
|
||||
* ------------------------------------------------------------------------------
|
||||
* executing entry 2
|
||||
> GET http://localhost:8000/output/endpoint2
|
||||
> User-Agent: hurl/0.99.8
|
||||
> Host: localhost
|
||||
>
|
||||
< HTTP/1.0 200
|
||||
< date: DATE2
|
||||
< content-type: text/html; charset=utf-8
|
||||
< content-length: 19
|
||||
< server: Werkzeug/0.16.1 Python/3.5.3
|
||||
<
|
||||
< Response endpoint2
|
||||
<
|
||||
Response endpoint2
|
2
integration/report/html/index.html
Normal file
2
integration/report/html/index.html
Normal file
@ -0,0 +1,2 @@
|
||||
<!DOCTYPE html>
|
||||
<html><head><title>Hurl Report</title><link rel="stylesheet" type="text/css" href="report.css"></head><body><h2>Hurl Report</h2><div class="date">Wed, 26 Aug 2020 11:50:20 +0200</div><table><thead><tr><td>filename</td><td>duration</td></tr></thead><tbody><tr><td class="success">tests/assert_base64.hurl</td><td>0.019s</td></tr><tr><td class="success">tests/assert_header.hurl</td><td>0.023s</td></tr><tr><td class="success">tests/assert_json.hurl</td><td>0.073s</td></tr><tr><td class="success">tests/assert_match.hurl</td><td>0.049s</td></tr><tr><td class="success">tests/assert_regex.hurl</td><td>0.025s</td></tr><tr><td class="success">tests/assert_xpath.hurl</td><td>0.023s</td></tr><tr><td class="success">tests/bytes.hurl</td><td>0.02s</td></tr><tr><td class="success">tests/capture_and_assert.hurl</td><td>0.022s</td></tr><tr><td class="success">tests/captures.hurl</td><td>0.078s</td></tr><tr><td class="success">tests/cookies.hurl</td><td>0.256s</td></tr><tr><td class="success">tests/delete.hurl</td><td>0.033s</td></tr><tr><td class="success">tests/empty.hurl</td><td>0s</td></tr><tr><td class="success">tests/encoding.hurl</td><td>0.059s</td></tr><tr><td class="failure">tests/error_assert_base64.hurl</td><td>0.052s</td></tr><tr><td class="failure">tests/error_assert_file.hurl</td><td>0.025s</td></tr><tr><td class="failure">tests/error_assert_header_not_found.hurl</td><td>0.028s</td></tr><tr><td class="failure">tests/error_assert_header_value.hurl</td><td>0.025s</td></tr><tr><td class="failure">tests/error_assert_http_version.hurl</td><td>0.025s</td></tr><tr><td class="failure">tests/error_assert_invalid_predicate_type.hurl</td><td>0.02s</td></tr><tr><td class="failure">tests/error_assert_match_utf8.hurl</td><td>0.022s</td></tr><tr><td class="failure">tests/error_assert_query_invalid_regex.hurl</td><td>0.018s</td></tr><tr><td class="failure">tests/error_assert_query_invalid_xpath.hurl</td><td>0.021s</td></tr><tr><td class="failure">tests/error_assert_status.hurl</td><td>0.019s</td></tr><tr><td class="failure">tests/error_assert_template_variable_not_found.hurl</td><td>0.027s</td></tr><tr><td class="failure">tests/error_assert_value_error.hurl</td><td>0.03s</td></tr><tr><td class="failure">tests/error_assert_variable.hurl</td><td>0.025s</td></tr><tr><td class="failure">tests/error_file_read_access.hurl</td><td>0s</td></tr><tr><td class="failure">tests/error_invalid_jsonpath.hurl</td><td>0.043s</td></tr><tr><td class="failure">tests/error_invalid_url.hurl</td><td>0s</td></tr><tr><td class="failure">tests/error_invalid_xml.hurl</td><td>0.036s</td></tr><tr><td class="failure">tests/error_multipart_form_data.hurl</td><td>0s</td></tr><tr><td class="failure">tests/error_predicate.hurl</td><td>0.06s</td></tr><tr><td class="failure">tests/error_query_header_not_found.hurl</td><td>0.033s</td></tr><tr><td class="failure">tests/error_query_invalid_json.hurl</td><td>0.022s</td></tr><tr><td class="failure">tests/error_query_invalid_utf8.hurl</td><td>0.024s</td></tr><tr><td class="failure">tests/error_template_variable_not_found.hurl</td><td>0s</td></tr><tr><td class="failure">tests/error_template_variable_not_renderable.hurl</td><td>0.021s</td></tr><tr><td class="success">tests/form_params.hurl</td><td>0.045s</td></tr><tr><td class="success">tests/headers.hurl</td><td>0.14s</td></tr><tr><td class="success">tests/hello.hurl</td><td>0.046s</td></tr><tr><td class="success">tests/multipart_form_data.hurl</td><td>0.024s</td></tr><tr><td class="success">tests/no_entry.hurl</td><td>0s</td></tr><tr><td class="success">tests/output.hurl</td><td>0.052s</td></tr><tr><td class="success">tests/patch.hurl</td><td>0.023s</td></tr><tr><td class="success">tests/post_base64.hurl</td><td>0.027s</td></tr><tr><td class="success">tests/post_file.hurl</td><td>0.02s</td></tr><tr><td class="success">tests/post_json.hurl</td><td>0.294s</td></tr><tr><td class="success">tests/post_multilines.hurl</td><td>0.078s</td></tr><tr><td class="success">tests/post_xml.hurl</td><td>0.068s</td></tr><tr><td class="success">tests/predicates-string.hurl</td><td>0.045s</td></tr><tr><td class="success">tests/put.hurl</td><td>0.024s</td></tr><tr><td class="success">tests/querystring_params.hurl</td><td>0.12s</td></tr><tr><td class="success">tests/redirect.hurl</td><td>0.062s</td></tr><tr><td class="success">tests/utf8.hurl</td><td>0.035s</td></tr></tbody></table></body></html>
|
20
integration/report/html/report.css
Normal file
20
integration/report/html/report.css
Normal file
@ -0,0 +1,20 @@
|
||||
.date {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
thead {
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
.success {
|
||||
color: green;
|
||||
}
|
||||
|
||||
.failure {
|
||||
color: red;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
6119
integration/report/tests.json
Normal file
6119
integration/report/tests.json
Normal file
File diff suppressed because it is too large
Load Diff
51
integration/run.sh
Executable file
51
integration/run.sh
Executable file
@ -0,0 +1,51 @@
|
||||
#!/bin/bash
|
||||
set -u
|
||||
set -e
|
||||
|
||||
for hurl_file in "$@"; do
|
||||
echo "$hurl_file";
|
||||
set +e
|
||||
hurl "$hurl_file" --color 2>/tmp/test.stderr >/tmp/test.stdout
|
||||
EXITCODE_ACTUAL=$?
|
||||
set -e
|
||||
|
||||
EXITCODE_EXPECTED=$(cat "${hurl_file%.*}.exit")
|
||||
if [ "$EXITCODE_ACTUAL" != "$EXITCODE_EXPECTED" ]; then
|
||||
echo "ERROR Exit Code"
|
||||
echo " Expected: $EXITCODE_EXPECTED"
|
||||
echo " Actual: $EXITCODE_ACTUAL"
|
||||
|
||||
# log unexpected error
|
||||
if [ "$EXITCODE_ACTUAL" != 0 ]; then
|
||||
cat /tmp/test.stderr
|
||||
fi
|
||||
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$EXITCODE_ACTUAL" == 0 ]; then
|
||||
expected=$(cat "${hurl_file%.*}.out")
|
||||
actual=$(cat /tmp/test.stdout)
|
||||
if [ "$actual" != "$expected" ]; then
|
||||
diff <(echo "$actual" ) <(echo "$expected")
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
STDERR_EXPECTED=$(cat "${hurl_file%.*}.err")
|
||||
STDERR_ACTUAL=$(cat /tmp/test.stderr)
|
||||
if [ "$STDERR_ACTUAL" != "$STDERR_EXPECTED" ]; then
|
||||
echo "ERROR StandardError"
|
||||
echo " Expected: $STDERR_EXPECTED"
|
||||
echo " Actual: $STDERR_ACTUAL"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
|
||||
done
|
||||
|
||||
|
||||
|
||||
|
||||
|
4
integration/server.py
Normal file
4
integration/server.py
Normal file
@ -0,0 +1,4 @@
|
||||
from tests import app
|
||||
if __name__ == '__main__':
|
||||
app.run(debug=True, host='0.0.0.0', port=8000)
|
||||
|
18
integration/tests/__init__.py
Normal file
18
integration/tests/__init__.py
Normal file
@ -0,0 +1,18 @@
|
||||
from flask import Flask
|
||||
import glob
|
||||
import importlib
|
||||
import os
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
current_dir = os.path.basename(os.path.dirname(__file__))
|
||||
for python_file in glob.glob(current_dir + '/*.py'):
|
||||
if python_file.endswith('__init__.py'):
|
||||
continue
|
||||
module_name = python_file.split('.')[0].replace('/', '.')
|
||||
print('loading %s' % module_name)
|
||||
try:
|
||||
importlib.import_module(module_name)
|
||||
except ImportError as err:
|
||||
print('Error:', err)
|
||||
|
1
integration/tests/assert_base64.exit
Normal file
1
integration/tests/assert_base64.exit
Normal file
@ -0,0 +1 @@
|
||||
0
|
11
integration/tests/assert_base64.hurl
Normal file
11
integration/tests/assert_base64.hurl
Normal file
@ -0,0 +1,11 @@
|
||||
# Test body response with line ending LF and CRLF.
|
||||
# We receive the text body "line1\nline2\r\nline3\n"
|
||||
#
|
||||
# $ printf "line1\nline2\r\nline3\n" | base64
|
||||
# bGluZTEKbGluZTINCmxpbmUzCg==
|
||||
|
||||
|
||||
GET http://localhost:8000/assert-base64
|
||||
HTTP/1.0 200
|
||||
base64,bGluZTEKbGluZTINCmxpbmUzCg==;
|
||||
|
3
integration/tests/assert_base64.out
Normal file
3
integration/tests/assert_base64.out
Normal file
@ -0,0 +1,3 @@
|
||||
line1
|
||||
line2
|
||||
line3
|
5
integration/tests/assert_base64.py
Normal file
5
integration/tests/assert_base64.py
Normal file
@ -0,0 +1,5 @@
|
||||
from tests import app
|
||||
|
||||
@app.route("/assert-base64")
|
||||
def assert_base64():
|
||||
return 'line1\nline2\r\nline3\n'
|
2
integration/tests/assert_header.exit
Normal file
2
integration/tests/assert_header.exit
Normal file
@ -0,0 +1,2 @@
|
||||
0
|
||||
|
13
integration/tests/assert_header.hurl
Normal file
13
integration/tests/assert_header.hurl
Normal file
@ -0,0 +1,13 @@
|
||||
GET http://localhost:8000/assert-header
|
||||
HTTP/1.0 200
|
||||
Content-Type: text/html; charset=utf-8
|
||||
Set-Cookie: cookie1=value1; Path=/
|
||||
Set-Cookie: cookie2=value2; Path=/
|
||||
[Asserts]
|
||||
header "Custom" not exists
|
||||
header "Content-Type" exists
|
||||
header "Header1" equals "value1"
|
||||
header "Set-Cookie" exists
|
||||
header "Set-Cookie" countEquals 3
|
||||
header "Set-Cookie" includes "cookie1=value1; Path=/"
|
||||
header "Set-Cookie" not includes "cookie4=value4; Path=/"
|
0
integration/tests/assert_header.out
Normal file
0
integration/tests/assert_header.out
Normal file
13
integration/tests/assert_header.py
Normal file
13
integration/tests/assert_header.py
Normal file
@ -0,0 +1,13 @@
|
||||
from tests import app
|
||||
from flask import make_response
|
||||
|
||||
@app.route("/assert-header")
|
||||
def assert_header():
|
||||
resp = make_response()
|
||||
resp.headers['Header1'] = 'value1'
|
||||
resp.set_cookie('cookie1', 'value1')
|
||||
resp.set_cookie('cookie2', 'value2')
|
||||
resp.set_cookie('cookie3', 'value3')
|
||||
return resp
|
||||
|
||||
|
1
integration/tests/assert_json.exit
Normal file
1
integration/tests/assert_json.exit
Normal file
@ -0,0 +1 @@
|
||||
0
|
38
integration/tests/assert_json.hurl
Normal file
38
integration/tests/assert_json.hurl
Normal file
@ -0,0 +1,38 @@
|
||||
GET http://localhost:8000/assert-json
|
||||
HTTP/1.0 200
|
||||
[Asserts]
|
||||
jsonpath "$.success" equals false
|
||||
jsonpath "$.success" equals false
|
||||
jsonpath "$.errors" countEquals 2
|
||||
jsonpath "$.warnings" countEquals 0
|
||||
jsonpath "$.toto" not exists
|
||||
jsonpath "$.warnings" exists
|
||||
jsonpath "$.warnings" exists
|
||||
jsonpath "$.errors[0]" exists
|
||||
jsonpath "$.errors[0].id" equals "error1"
|
||||
jsonpath "$.errors[0]['id']" equals "error1"
|
||||
jsonpath "$.duration" equals 1.5
|
||||
jsonpath "$.nullable" equals null
|
||||
|
||||
{
|
||||
"success": false,
|
||||
"errors": [{"id":"error1"},{"id":"error2"}],
|
||||
"warnings": [],
|
||||
"duration": 1.5,
|
||||
"tags": ["test"],
|
||||
"nullable": null
|
||||
}
|
||||
|
||||
GET http://localhost:8000/assert-json/index
|
||||
HTTP/* 200
|
||||
[Captures]
|
||||
index: body
|
||||
|
||||
GET http://localhost:8000/assert-json
|
||||
HTTP/1.0 200
|
||||
[Asserts]
|
||||
jsonpath "$.errors[{{index}}].id" equals "error2"
|
||||
jsonpath "$.tags" includes "test"
|
||||
jsonpath "$.tags" not includes "prod"
|
||||
jsonpath "$.tags" not includes null
|
||||
|
8
integration/tests/assert_json.out
Normal file
8
integration/tests/assert_json.out
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"success": false,
|
||||
"errors": [{"id":"error1"},{"id":"error2"}],
|
||||
"warnings": [],
|
||||
"duration": 1.5,
|
||||
"tags": ["test"],
|
||||
"nullable": null
|
||||
}
|
18
integration/tests/assert_json.py
Normal file
18
integration/tests/assert_json.py
Normal file
@ -0,0 +1,18 @@
|
||||
from tests import app
|
||||
from flask import Response
|
||||
|
||||
@app.route("/assert-json")
|
||||
def assert_json():
|
||||
return Response('''{
|
||||
"success": false,
|
||||
"errors": [{"id":"error1"},{"id":"error2"}],
|
||||
"warnings": [],
|
||||
"duration": 1.5,
|
||||
"tags": ["test"],
|
||||
"nullable": null
|
||||
}''', mimetype='application/json')
|
||||
|
||||
|
||||
@app.route("/assert-json/index")
|
||||
def assert_json_index():
|
||||
return "1"
|
1
integration/tests/assert_match.exit
Normal file
1
integration/tests/assert_match.exit
Normal file
@ -0,0 +1 @@
|
||||
0
|
7
integration/tests/assert_match.hurl
Normal file
7
integration/tests/assert_match.hurl
Normal file
@ -0,0 +1,7 @@
|
||||
GET http://localhost:8000/assert-match
|
||||
HTTP/1.0 200
|
||||
[Asserts]
|
||||
jsonpath "$.date1" matches "\\d{4}-\\d{2}-\\d{2}"
|
||||
jsonpath "$.date2" matches "\\d{4}-\\d{2}-\\d{2}"
|
||||
jsonpath "$.date1" matches "^\\d{4}-\\d{2}-\\d{2}$"
|
||||
jsonpath "$.date2" not matches "^\\d{4}-\\d{2}-\\d{2}$"
|
4
integration/tests/assert_match.out
Normal file
4
integration/tests/assert_match.out
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"date1": "2014-01-01",
|
||||
"date2": "x2014-01-01"
|
||||
}
|
11
integration/tests/assert_match.py
Normal file
11
integration/tests/assert_match.py
Normal file
@ -0,0 +1,11 @@
|
||||
from tests import app
|
||||
from flask import Response
|
||||
|
||||
@app.route("/assert-match")
|
||||
def assert_match():
|
||||
return Response('''{
|
||||
"date1": "2014-01-01",
|
||||
"date2": "x2014-01-01"
|
||||
}''', mimetype='application/json')
|
||||
|
||||
|
1
integration/tests/assert_regex.exit
Normal file
1
integration/tests/assert_regex.exit
Normal file
@ -0,0 +1 @@
|
||||
0
|
6
integration/tests/assert_regex.hurl
Normal file
6
integration/tests/assert_regex.hurl
Normal file
@ -0,0 +1,6 @@
|
||||
GET http://localhost:8000/assert-regex
|
||||
|
||||
HTTP/1.0 200
|
||||
[Asserts]
|
||||
regex "Hello ([0-9]+)!" not exists
|
||||
regex "Hello ([a-zA-Z]+)!" equals "World"
|
1
integration/tests/assert_regex.out
Normal file
1
integration/tests/assert_regex.out
Normal file
@ -0,0 +1 @@
|
||||
Hello World!
|
8
integration/tests/assert_regex.py
Normal file
8
integration/tests/assert_regex.py
Normal file
@ -0,0 +1,8 @@
|
||||
# coding=utf-8
|
||||
from tests import app
|
||||
|
||||
|
||||
@app.route("/assert-regex")
|
||||
def assert_regex():
|
||||
return 'Hello World!'
|
||||
|
1
integration/tests/assert_xpath.exit
Normal file
1
integration/tests/assert_xpath.exit
Normal file
@ -0,0 +1 @@
|
||||
0
|
9
integration/tests/assert_xpath.hurl
Normal file
9
integration/tests/assert_xpath.hurl
Normal file
@ -0,0 +1,9 @@
|
||||
GET http://localhost:8000/assert-xpath
|
||||
|
||||
HTTP/1.0 200
|
||||
[Asserts]
|
||||
xpath "normalize-space(//data)" equals "café"
|
||||
xpath "normalize-space(//data)" equals "caf\u{00e9}"
|
||||
xpath "//toto" not exists
|
||||
|
||||
<data>café</data>
|
1
integration/tests/assert_xpath.out
Normal file
1
integration/tests/assert_xpath.out
Normal file
@ -0,0 +1 @@
|
||||
<data>café</data>
|
8
integration/tests/assert_xpath.py
Normal file
8
integration/tests/assert_xpath.py
Normal file
@ -0,0 +1,8 @@
|
||||
# coding=utf-8
|
||||
from tests import app
|
||||
|
||||
|
||||
@app.route("/assert-xpath")
|
||||
def assert_xpath():
|
||||
return '<data>café</data>'
|
||||
|
4
integration/tests/bytes.exit
Normal file
4
integration/tests/bytes.exit
Normal file
@ -0,0 +1,4 @@
|
||||
0
|
||||
|
||||
|
||||
|
9
integration/tests/bytes.hurl
Normal file
9
integration/tests/bytes.hurl
Normal file
@ -0,0 +1,9 @@
|
||||
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
|
||||
|
||||
|
||||
|
1
integration/tests/bytes.out
Normal file
1
integration/tests/bytes.out
Normal file
@ -0,0 +1 @@
|
||||
<EFBFBD>
|
16
integration/tests/bytes.py
Normal file
16
integration/tests/bytes.py
Normal file
@ -0,0 +1,16 @@
|
||||
from tests import app
|
||||
from flask import make_response, request
|
||||
from io import BytesIO
|
||||
|
||||
|
||||
@app.route('/bytes')
|
||||
def bytes():
|
||||
result = BytesIO()
|
||||
result.write(b'\xff')
|
||||
data = result.getvalue()
|
||||
resp = make_response(data)
|
||||
resp.content_type = 'application/octet-stream'
|
||||
return resp
|
||||
|
||||
|
||||
|
1
integration/tests/capture_and_assert.exit
Normal file
1
integration/tests/capture_and_assert.exit
Normal file
@ -0,0 +1 @@
|
||||
0
|
8
integration/tests/capture_and_assert.hurl
Normal file
8
integration/tests/capture_and_assert.hurl
Normal file
@ -0,0 +1,8 @@
|
||||
GET http://localhost:8000/capture-and-assert
|
||||
HTTP/1.0 200
|
||||
[Captures]
|
||||
content_type: header "content-type"
|
||||
[Asserts]
|
||||
header "content-type" equals "{{content_type}}"
|
||||
|
||||
|
1
integration/tests/capture_and_assert.out
Normal file
1
integration/tests/capture_and_assert.out
Normal file
@ -0,0 +1 @@
|
||||
Hello World!
|
7
integration/tests/capture_and_assert.py
Normal file
7
integration/tests/capture_and_assert.py
Normal file
@ -0,0 +1,7 @@
|
||||
from tests import app
|
||||
|
||||
|
||||
@app.route("/capture-and-assert")
|
||||
def capture_and_assert():
|
||||
return 'Hello World!'
|
||||
|
1
integration/tests/captures.exit
Normal file
1
integration/tests/captures.exit
Normal file
@ -0,0 +1 @@
|
||||
0
|
40
integration/tests/captures.hurl
Normal file
40
integration/tests/captures.hurl
Normal file
@ -0,0 +1,40 @@
|
||||
GET http://localhost:8000/captures
|
||||
|
||||
HTTP/1.0 200
|
||||
[Captures]
|
||||
param1: header "header1"
|
||||
param2: header "header2" regex "Hello (.*)!"
|
||||
[Asserts]
|
||||
variable "param1" equals "value1"
|
||||
variable "param2" equals "Bob"
|
||||
|
||||
GET http://localhost:8000/captures-check
|
||||
[QueryStringParams]
|
||||
param1: {{param1}}
|
||||
param2: {{param2}}
|
||||
|
||||
HTTP/1.0 200
|
||||
|
||||
|
||||
GET http://localhost:8000/captures-json
|
||||
HTTP/1.0 200
|
||||
[Captures]
|
||||
an_object: jsonpath "$['an_object']"
|
||||
a_list: jsonpath "$['a_list']"
|
||||
a_null: jsonpath "$['a_null']"
|
||||
an_integer: jsonpath "$['an_integer']"
|
||||
a_float: jsonpath "$['a_float']"
|
||||
a_bool: jsonpath "$['a_bool']"
|
||||
a_string: jsonpath "$['a_string']"
|
||||
all: jsonpath "$"
|
||||
[Asserts]
|
||||
variable "a_null" exists
|
||||
variable "undefined" not exists
|
||||
variable "a_null" equals {{a_null}}
|
||||
variable "an_integer" equals {{an_integer}}
|
||||
variable "a_float" equals {{a_float}}
|
||||
variable "a_bool" equals {{a_bool}}
|
||||
variable "a_string" equals {{a_string}}
|
||||
variable "a_list" equals {{a_list}}
|
||||
|
||||
|
1
integration/tests/captures.out
Normal file
1
integration/tests/captures.out
Normal file
@ -0,0 +1 @@
|
||||
{ "a_null": null, "an_object": {"id": "123"}, "a_list": [1,2,3], "an_integer": 1, "a_float": 1.1, "a_bool": true, "a_string": "hello" }
|
23
integration/tests/captures.py
Normal file
23
integration/tests/captures.py
Normal file
@ -0,0 +1,23 @@
|
||||
from tests import app
|
||||
from flask import make_response, request
|
||||
|
||||
|
||||
@app.route('/captures')
|
||||
def captures():
|
||||
resp = make_response()
|
||||
resp.headers['Header1'] = 'value1'
|
||||
resp.headers['Header2'] = 'Hello Bob!'
|
||||
return resp
|
||||
|
||||
|
||||
@app.route('/captures-check')
|
||||
def captures_check():
|
||||
assert request.args.get('param1') == 'value1'
|
||||
assert request.args.get('param2') == 'Bob'
|
||||
return ''
|
||||
|
||||
|
||||
|
||||
@app.route('/captures-json')
|
||||
def captures_json():
|
||||
return '{ "a_null": null, "an_object": {"id": "123"}, "a_list": [1,2,3], "an_integer": 1, "a_float": 1.1, "a_bool": true, "a_string": "hello" }'
|
1
integration/tests/cookies.exit
Normal file
1
integration/tests/cookies.exit
Normal file
@ -0,0 +1 @@
|
||||
0
|
74
integration/tests/cookies.hurl
Normal file
74
integration/tests/cookies.hurl
Normal file
@ -0,0 +1,74 @@
|
||||
# Request Cookie
|
||||
|
||||
GET http://localhost:8000/cookies/set-request-cookie1-valueA
|
||||
[Cookies]
|
||||
cookie1: valueA
|
||||
HTTP/1.0 200
|
||||
|
||||
# In the future we want a dedicated directive to set the session cookie on the client
|
||||
# but for the time-being, sending a request cookie will update the cookiejar
|
||||
#GET http://localhost:8000/cookies/assert-that-cookie1-is-not-in-session
|
||||
#HTTP/1.0 200
|
||||
|
||||
|
||||
|
||||
# Session Cookie
|
||||
|
||||
GET http://localhost:8000/cookies/set-session-cookie2-valueA
|
||||
HTTP/1.0 200
|
||||
[Asserts]
|
||||
cookie "cookie2" equals "valueA"
|
||||
|
||||
GET http://localhost:8000/cookies/assert-that-cookie2-is-valueA
|
||||
HTTP/1.0 200
|
||||
|
||||
|
||||
|
||||
GET http://127.0.0.1:8000/cookies/assert-that-cookie2-is-not-in-session
|
||||
HTTP/1.0 200
|
||||
|
||||
|
||||
GET http://localhost:8000/cookies/set-request-cookie2-valueB
|
||||
[Cookies]
|
||||
cookie2: valueB
|
||||
HTTP/1.0 200
|
||||
|
||||
# In the future we want a dedicated directive to set the session cookie on the client
|
||||
# but for the time-being, sending a request cookie will update the cookiejar
|
||||
#GET http://localhost:8000/cookies/assert-that-cookie2-is-valueA
|
||||
GET http://localhost:8000/cookies/assert-that-cookie2-is-valueB
|
||||
HTTP/1.0 200
|
||||
|
||||
GET http://localhost:8000/cookies/delete-cookie2
|
||||
HTTP/1.0 200
|
||||
[Asserts]
|
||||
header "Set-Cookie" contains "Max-Age=0" # TODO replace by cookie query
|
||||
|
||||
#GET http://localhost:8000/cookies/assert-that-cookie2-is-not-in-session
|
||||
#HTTP/1.0 200
|
||||
|
||||
|
||||
|
||||
GET http://localhost:8000/cookies/set
|
||||
HTTP/1.0 200
|
||||
Set-Cookie: LSID=DQAAAKEaem_vYg; Expires=Wed, 13 Jan 2021 22:23:01 GMT; Secure; HttpOnly; Path=/accounts
|
||||
Set-Cookie: HSID=AYQEVnDKrdst; Domain=.foo.com; Expires=Wed, 13 Jan 2021 22:23:01 GMT; HttpOnly; Path=/
|
||||
Set-Cookie: SSID=Ap4PGTEq; Domain=.foo.com; Expires=Wed, 13 Jan 2021 22:23:01 GMT; Secure; HttpOnly; Path=/
|
||||
|
||||
[Asserts]
|
||||
header "Set-Cookie" countEquals 3
|
||||
cookie "LSID" equals "DQAAAKEaem_vYg"
|
||||
cookie "LSID[Value]" equals "DQAAAKEaem_vYg"
|
||||
cookie "LSID[Expires]" exists
|
||||
cookie "LSID[Expires]" equals "Wed, 13 Jan 2021 22:23:01 GMT"
|
||||
cookie "LSID[Max-Age]" not exists
|
||||
cookie "LSID[Domain]" not exists
|
||||
cookie "LSID[Path]" equals "/accounts"
|
||||
cookie "LSID[Secure]" equals true
|
||||
cookie "LSID[HttpOnly]" equals true
|
||||
cookie "LSID[SameSite]" not exists
|
||||
|
||||
|
||||
|
||||
|
||||
|
0
integration/tests/cookies.out
Normal file
0
integration/tests/cookies.out
Normal file
92
integration/tests/cookies.py
Normal file
92
integration/tests/cookies.py
Normal file
@ -0,0 +1,92 @@
|
||||
from flask import request, make_response
|
||||
from tests import app
|
||||
|
||||
|
||||
@app.route("/cookies/set-request-cookie1-valueA")
|
||||
def set_request_cookie1_value1():
|
||||
assert request.cookies['cookie1'] == 'valueA'
|
||||
return ''
|
||||
|
||||
|
||||
@app.route("/cookies/set-session-cookie2-valueA")
|
||||
def set_session_cookie2_valuea():
|
||||
resp = make_response()
|
||||
resp.set_cookie('cookie2', 'valueA')
|
||||
return resp
|
||||
|
||||
|
||||
@app.route("/cookies/set-request-cookie2-valueB")
|
||||
def set_request_cookie2_valueb():
|
||||
assert request.cookies['cookie2'] == 'valueB'
|
||||
return ''
|
||||
|
||||
|
||||
@app.route("/cookies/send-cookie2-value1")
|
||||
def send_cookie2_value1():
|
||||
assert'cookie1' not in request.cookies
|
||||
assert request.cookies['cookie2'] == 'value1'
|
||||
return ''
|
||||
|
||||
|
||||
@app.route("/cookies/send-cookie2-value2")
|
||||
def send_cookie2_value2():
|
||||
assert request.cookies['cookie2'] == 'value2'
|
||||
return ''
|
||||
|
||||
|
||||
@app.route("/cookies/delete-cookie2")
|
||||
def delete_cookie2():
|
||||
resp = make_response()
|
||||
resp.set_cookie('cookie2', '', max_age=0)
|
||||
return resp
|
||||
|
||||
|
||||
@app.route("/cookies/assert-that-cookie1-is-not-in-session")
|
||||
def assert_that_cookie1_is_not_in_session():
|
||||
assert'cookie1' not in request.cookies
|
||||
return ''
|
||||
|
||||
|
||||
@app.route("/cookies/assert-that-cookie2-is-not-in-session")
|
||||
def assert_that_cookie2_is_not_in_session():
|
||||
assert'cookie2' not in request.cookies
|
||||
return ''
|
||||
|
||||
|
||||
@app.route("/cookies/assert-that-cookie2-is-valueA")
|
||||
def assert_that_cookie2_is_valuea():
|
||||
assert request.cookies['cookie2'] == 'valueA'
|
||||
return ''
|
||||
|
||||
@app.route("/cookies/assert-that-cookie2-is-valueB")
|
||||
def assert_that_cookie2_is_valueb():
|
||||
assert request.cookies['cookie2'] == 'valueB'
|
||||
return ''
|
||||
|
||||
@app.route("/cookies/set-session-cookie2-valueA-subdomain")
|
||||
def set_session_cookie2_valuea_subdomain():
|
||||
resp = make_response()
|
||||
resp.set_cookie('cookie2', 'valueA', domain='myshop.orange.localhost')
|
||||
return resp
|
||||
|
||||
@app.route("/cookies/set-session-cookie2-valueA-subdomain2")
|
||||
def set_session_cookie2_valuea_subdomain2():
|
||||
resp = make_response()
|
||||
resp.set_cookie('cookie2', 'valueA', domain='orange.localhost')
|
||||
return resp
|
||||
|
||||
|
||||
# Set-Cookie: LSID=; Path=/accounts; Expires=Wed, 13 Jan 2021 22:23:01 GMT; Secure; HttpOnly
|
||||
# Set-Cookie: HSID=AYQEVn…DKrdst; Domain=.foo.com; Path=/; Expires=Wed, 13 Jan 2021 22:23:01 GMT; HttpOnly
|
||||
# Set-Cookie: SSID=Ap4P…GTEq; Domain=foo.com; Path=/; Expires=Wed, 13 Jan 2021 22:23:01 GMT; Secure; HttpOnly
|
||||
@app.route("/cookies/set")
|
||||
def set_cookies():
|
||||
resp = make_response()
|
||||
resp.set_cookie('LSID', 'DQAAAKEaem_vYg', path='/accounts', secure=True, httponly=True, expires='Wed, 13 Jan 2021 22:23:01 GMT')
|
||||
resp.set_cookie('HSID', 'AYQEVnDKrdst', domain='.foo.com', path='/', expires='Wed, 13 Jan 2021 22:23:01 GMT', httponly=True)
|
||||
resp.set_cookie('SSID', 'Ap4PGTEq',domain='.foo.com', path='/', expires='Wed, 13 Jan 2021 22:23:01 GMT', secure=True, httponly=True)
|
||||
return resp
|
||||
|
||||
|
||||
|
||||
|
1
integration/tests/data.bin
Normal file
1
integration/tests/data.bin
Normal file
@ -0,0 +1 @@
|
||||
Hello World!
|
1
integration/tests/delete.exit
Normal file
1
integration/tests/delete.exit
Normal file
@ -0,0 +1 @@
|
||||
0
|
3
integration/tests/delete.hurl
Normal file
3
integration/tests/delete.hurl
Normal file
@ -0,0 +1,3 @@
|
||||
DELETE http://localhost:8000/delete
|
||||
HTTP/1.0 200
|
||||
|
0
integration/tests/delete.out
Normal file
0
integration/tests/delete.out
Normal file
7
integration/tests/delete.py
Normal file
7
integration/tests/delete.py
Normal file
@ -0,0 +1,7 @@
|
||||
from tests import app
|
||||
|
||||
@app.route('/delete', methods=['DELETE'])
|
||||
def delete():
|
||||
return ''
|
||||
|
||||
|
1
integration/tests/empty.exit
Normal file
1
integration/tests/empty.exit
Normal file
@ -0,0 +1 @@
|
||||
0
|
0
integration/tests/empty.hurl
Normal file
0
integration/tests/empty.hurl
Normal file
0
integration/tests/empty.out
Normal file
0
integration/tests/empty.out
Normal file
1
integration/tests/encoding.exit
Normal file
1
integration/tests/encoding.exit
Normal file
@ -0,0 +1 @@
|
||||
0
|
14
integration/tests/encoding.hurl
Normal file
14
integration/tests/encoding.hurl
Normal file
@ -0,0 +1,14 @@
|
||||
GET http://localhost:8000/encoding/utf8
|
||||
HTTP/1.0 200
|
||||
Content-Type: text/html; charset=utf-8
|
||||
[Asserts]
|
||||
body equals "caf\u{e9}"
|
||||
|
||||
|
||||
GET http://localhost:8000/encoding/latin1
|
||||
HTTP/1.0 200
|
||||
Content-Type: text/html; charset=ISO-8859-1
|
||||
[Asserts]
|
||||
body equals "caf\u{e9}"
|
||||
|
||||
|
1
integration/tests/encoding.out
Normal file
1
integration/tests/encoding.out
Normal file
@ -0,0 +1 @@
|
||||
caf<EFBFBD>
|
22
integration/tests/encoding.py
Normal file
22
integration/tests/encoding.py
Normal file
@ -0,0 +1,22 @@
|
||||
from flask import request, make_response
|
||||
from tests import app
|
||||
from io import BytesIO
|
||||
|
||||
|
||||
@app.route("/encoding/utf8")
|
||||
def encoding_utf8():
|
||||
return 'café'
|
||||
|
||||
@app.route("/encoding/latin1")
|
||||
def encoding_latin1():
|
||||
result = BytesIO()
|
||||
result.write(b'\x63\x61\x66\xe9')
|
||||
data = result.getvalue()
|
||||
resp = make_response(data)
|
||||
resp.content_type = 'text/html; charset=ISO-8859-1'
|
||||
return resp
|
||||
|
||||
|
||||
|
||||
|
||||
|
7
integration/tests/error_assert_base64.err
Normal file
7
integration/tests/error_assert_base64.err
Normal file
@ -0,0 +1,7 @@
|
||||
[1;31merror[0m: Assert Body Value
|
||||
--> tests/error_assert_base64.hurl:12:8
|
||||
|
|
||||
12 | base64,bGluZTEKbGluZTIKbGluZTMK;
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^ actual value is <Bytes([6c, 69, 6e, 65, 31, a, 6c, 69, 6e, 65, 32, d, a, 6c, 69, 6e, 65, 33, a])>
|
||||
|
|
||||
|
1
integration/tests/error_assert_base64.exit
Normal file
1
integration/tests/error_assert_base64.exit
Normal file
@ -0,0 +1 @@
|
||||
4
|
13
integration/tests/error_assert_base64.hurl
Normal file
13
integration/tests/error_assert_base64.hurl
Normal file
@ -0,0 +1,13 @@
|
||||
# Test body response with line ending LF and CRLF.
|
||||
# We receive the text body "line1\nline2\r\nline3\n"
|
||||
# and not "line1\nline2\nline3\n"
|
||||
|
||||
#
|
||||
# $ printf "line1\nline2\nline3\n" | base64
|
||||
# bGluZTEKbGluZTIKbGluZTMK
|
||||
|
||||
|
||||
GET http://localhost:8000/assert-base64
|
||||
HTTP/1.0 200
|
||||
base64,bGluZTEKbGluZTIKbGluZTMK;
|
||||
|
5
integration/tests/error_assert_base64.py
Normal file
5
integration/tests/error_assert_base64.py
Normal file
@ -0,0 +1,5 @@
|
||||
from tests import app
|
||||
|
||||
@app.route("/error-assert-base64")
|
||||
def error_assert_base64():
|
||||
return 'line1\nline2\r\nline3\n'
|
7
integration/tests/error_assert_file.err
Normal file
7
integration/tests/error_assert_file.err
Normal file
@ -0,0 +1,7 @@
|
||||
[1;31merror[0m: Assert Body Value
|
||||
--> tests/error_assert_file.hurl:8:1
|
||||
|
|
||||
8 | file,hello.txt;
|
||||
| ^ actual value is <Bytes([48, 65, 6c, 6c, 6f])>
|
||||
|
|
||||
|
1
integration/tests/error_assert_file.exit
Normal file
1
integration/tests/error_assert_file.exit
Normal file
@ -0,0 +1 @@
|
||||
4
|
9
integration/tests/error_assert_file.hurl
Normal file
9
integration/tests/error_assert_file.hurl
Normal file
@ -0,0 +1,9 @@
|
||||
# Test body response with file assertion.
|
||||
# We receive the body "Hello" and not "Hello World!"
|
||||
|
||||
|
||||
|
||||
GET http://localhost:8000/error-assert-file
|
||||
HTTP/1.0 200
|
||||
file,hello.txt;
|
||||
|
5
integration/tests/error_assert_file.py
Normal file
5
integration/tests/error_assert_file.py
Normal file
@ -0,0 +1,5 @@
|
||||
from tests import app
|
||||
|
||||
@app.route("/error-assert-file")
|
||||
def error_assert_file():
|
||||
return 'Hello'
|
7
integration/tests/error_assert_header_not_found.err
Normal file
7
integration/tests/error_assert_header_not_found.err
Normal file
@ -0,0 +1,7 @@
|
||||
[1;31merror[0m: Header not Found
|
||||
--> tests/error_assert_header_not_found.hurl:3:1
|
||||
|
|
||||
3 | Custom: ???
|
||||
| ^^^^^^ This header has not been found in the response
|
||||
|
|
||||
|
1
integration/tests/error_assert_header_not_found.exit
Normal file
1
integration/tests/error_assert_header_not_found.exit
Normal file
@ -0,0 +1 @@
|
||||
4
|
3
integration/tests/error_assert_header_not_found.hurl
Normal file
3
integration/tests/error_assert_header_not_found.hurl
Normal file
@ -0,0 +1,3 @@
|
||||
GET http://localhost:8000/error-assert-header-not-found
|
||||
HTTP/1.0 200
|
||||
Custom: ???
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user