Skip to content
Scripting language for testing JSON-based HTTP APIs
Racket Other
  1. Racket 99.3%
  2. Other 0.7%
Branch: master
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
examples Add some examples with relative JSON Pointers Jan 16, 2020
scribblings Describe timeouts Nov 19, 2019
.gitignore Ignore compiled/ subdirectory of scribblings/ Oct 15, 2018
LICENSE Update for 2019 Jul 28, 2019
Makefile Remove redundant stuff Nov 14, 2018
README.md Clarify a couple examples Sep 22, 2019
build.sh Simplify build process Sep 30, 2019
changelog.md Document change Jan 16, 2020
cmd.rkt Drop useless import Jan 14, 2020
expander.rkt Fix missing support for relative JSON Pointer Jan 16, 2020
grammar.rkt Fix missing support for relative JSON Pointer Jan 16, 2020
headers.rip Add examples Oct 13, 2019
info.rkt Bump for better relative JSON Pointer support Jan 16, 2020
json.rkt Require that equal-jsexprs? take jsexprs? Nov 18, 2019
login.rip Add examples Oct 13, 2019
main.rkt Support bang Nov 15, 2019
parameters.rip Add examples Oct 13, 2019
parameters.rkt Parameter for the current working directory Aug 29, 2019
parser.rkt Provide is-well-formed check Jan 14, 2020
reader.rkt Support a REPL Sep 30, 2019
respond.rkt Continue work on new tokenizer May 6, 2019
response.rkt Permit fetching the raw bytes of a response Sep 10, 2019
riposte.rkt When working on a directory, show the current file Jan 14, 2020
server.rkt Continue work on new tokenizer May 6, 2019
simple.rip Continue work on new tokenizer May 6, 2019
todo.org Add todo Jul 28, 2019
tokenizer.rkt Add tokenizer timeout tests Nov 18, 2019
util.rkt Move comment-out-lines functionality to util.rkt Jan 14, 2020

README.md

Riposte—Scripting Language for JSON-based HTTP APIs

Riposte is a language for testing JSON-bearing HTTP APIs. It comes with a commandline tool, riposte, which executes Riposte scripts. Using Riposte, one writes a sequence of commands—which amount to HTTP requests—and assertions, which require that the response meets certain conditions.

Examples:

# set a header, to be added to all requests going forward
# the # character is the to-end-of-line comment character

^Content-Type := "application/json"

# set a base URL; we will merge all URIs going forward
# with this
%base := https://api.mydomain.test/v1/

$uuid := @UUID # @ is how you access environment variables

Normal variables (here, $uuid) have the $ sigil in front of them. There are a handful of global variables which use % as their sigil (here, %base). Headers form their own kind of “namespace“ for variables; their sigil is ^ (here, ^Content-Type). In all cases, := is the way you assign a value to a variable.

# ----------------------------------------------------------------------
# Start sending requests
# ----------------------------------------------------------------------

GET checkout/cart/{uuid} responds with 2XX

Riposte builds on URI Template to express URIs. Here, we plug in the value of the uuid variable, which came from the environment. The 2XX means: we expect a 200-class response. The precise response code doesn't matter.

Riposte supports arbitrary HTTP commands, including the usual suspects (GET, HEAD, POST, OPTIONS, PATCH and DELETE). Just put HTTP methods in uppercase. Example use of a non-standard HTTP method:

CANCEL order/1 responds with 201

Let's keep going:

# ----------------------------------------------------------------------
# Now add something to the cart:
# ----------------------------------------------------------------------

$productId := 41966
$qty := 5

$payload := {
  "product_id": $productId, # we extend the JSON syntax here
  "qty": $qty # and you can add comments to JSON, too
}

POST $payload checkout/cart/{uuid}/items responds with 200

$itemId := /vendors/0/items/0/cart_item_id # extract the item ID

Here, we define some data and build a JSON object with three properties. We then submit that to (what works out to) https://api.mydomain.test/v1/checkout/cart/28f896e6-98df-497b-b95f-c34d39c2a368/items. (I just picked a random UUID.) Notice that we expect a concrete response code here. The response had better be 200. Anything else would be an error.

We also extract data from the response. Riposte builds on JSON Pointer, which is an IETF standard notation for referring to parts of JSON documents.

GET checkout/cart responds with 2XX

/tax_total exists

After making a request, you can make assertions about it. Here, with exists, we're asserting that (1) the response is a JSON object, and (2) it has a property, "tax_total". We don't care what that value is. (Riposte comes with a ton of assertions; exists is just one.)

Here's another Riposte script that logs in to an API by POSTing credentials (a JSON array) to a certain endpoint, which is supposed to give us back a key that we will attach to later requests:

$loginPayload := {
    "email": @EMAIL,
    "password": @PASSWORD
}

POST $loginPayload to auth/login responds with 2XX

^Apikey := /key # extract a value from the response body
# and use it as the value of an HTTP header, going forward

Welcome to Riposte.

Approach to testing

Riposte is intended to be a language for tests. As such, it has no concept of branching (“if-then-else”). The idea is that a Riposte script consists of a series of assertions, all of which must succeed. If, during evaluation, a command or assertion fails, evaluation will be aborted. There's no fallback.

Clarification The word “Riposte” might refer to the language, to the commandline program riposte, or to the intended semantics of the Riposte language, which is implemented in the riposte program. So when we say things like "Riposte allows this", "Riposte bails out", or "call Riposte this way", we have one of these possible meanings in mind. The context of the statement should make it clear how to disambiguate; if it is not clear what is meant, please file a bug ticket that asks for clarification.)

JSON-only

Riposte is JSON-only. It works with JSON values and assumes that the responses it receives (that is, the response bodies) are JSON.

Some HTTP responses (that is, their bodies) are empty. There's even an HTTP response code for this case (204). Riposte can handle empty response bodies. If, however, a non-empty response is returned, Riposte attempts to parse the body as JSON. If if can't, Riposte will die.

This means that Riposte (in its current incarnation) can't work with HTML, arbitrary plain text, images, and so on. If you request a URI and get a response that contains, say, HTML, Riposte will bail out (unless, by chance, the response turns out to be well-formed JSON!).

File extension

Use .rip.

(Valid Riposte files are plain UTF-8 text, and no check is made that the file has the extension .rip. You could just as well use .txt, .ribfest, .nsa…)

Commandline usage

There are two ways of executing a Riposte script: using riposte or directly executing them.

Using the interpreter

Do this:

$ riposte path/to/script.rip

Direct execution

Use a shebang. Start your Riposte script like this:

 #!/usr/local/bin/riposte
 #
 # lang riposte
 #
 GET whatever responds with 2XX

Make sure your Riposte script is executable. Then—assuming that Riposte really is at /usr/local/bin/riposte—the following should suffice:

$ ./let-er.rip
You can’t perform that action at this time.