Skip to content

Commit

Permalink
Merge pull request #26 from daleal/master
Browse files Browse the repository at this point in the history
Release version 0.2.0
  • Loading branch information
daleal authored Mar 21, 2021
2 parents 2360d71 + 5bae815 commit edbda29
Show file tree
Hide file tree
Showing 10 changed files with 223 additions and 14 deletions.
54 changes: 46 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,9 +128,36 @@ zum search mystring 3

The query will be exactly the same. **This means that the array tells `zum` in which order to interpret the CLI parameters**.

#### Headers

Just like the parameters, the headers get defined as an array:

```toml
[endpoints.restricted]
route = "/secret"
method = "get"
headers = ["Authorization"]
```

To run this endpoint, you just need to run:

```sh
zum restricted "Bearer super-secret-token"
```

This will send a `GET` request to `http://localhost:8000/entity` with the following headers:

```json
{
"Authorization": "Bearer super-secret-token"
}
```

**WARNING**: Notice that, for the first time, we surrounded something with quotes. The reason we did this is that, without the quotes, the console has no way of knowing if you want to pass a parameter with a space in the middle or if you want to pass multiple parameters, so it defaults to receiving the words as multiple parameters. To stop this from happening, you can surround the string in quotes, and now the whole string will be interpreted as only one parameter with the space in the middle of the string. This will be handy on future examples, so **keep it in mind**.

#### Request body

Just like the parameters, the request body gets defined as an array:
Just like the parameters and headers, the request body gets defined as an array:

```toml
[endpoints.create-entity]
Expand Down Expand Up @@ -219,25 +246,34 @@ body = [

Now, `parameter1` will be sent as a string, `parameter2` will be casted as a boolean and `parameter3` will be sent as a string.

#### What about both?
#### What about using all of the above?

Of course, sometimes you need to use both parameters **and** request bodies. For example, if you wanted to create a nested entity, you would need to use the parent's id as a parameter and the new entity data as a request body. Let's describe this situation!
Of course, sometimes you need to use parameters, headers **and** request bodies. For example, if you wanted to create a nested entity, you would need to use the parent's id as a parameter, the authorization headers and the new entity data as a request body. Let's describe this situation!

```toml
[endpoints.create-nested]
route = "/entity/{id}"
method = "post"
params = ["id"]
headers = ["Authorization"]
body = ["name", "city"]
```

Now, you can call the endpoint using:

```sh
zum create-nested 69 dani Santiago
zum create-nested 69 "Bearer super-secret-token" dani Santiago
```

This will call `POST /entity/69` with the following headers:

```json
{
"Authorization": "Bearer super-secret-token"
}
```

This will call `POST /entity/69` with the following request body:
And the following request body:

```json
{
Expand All @@ -246,10 +282,10 @@ This will call `POST /entity/69` with the following request body:
}
```

As you can probably tell, `zum` receives the `params` first on the CLI, and then the `body`. In _pythonic_ terms, what `zum` does is kind of _unpacks_ both arrays consecutively, something like the following:
As you can probably tell, `zum` receives the `params` first on the CLI, then the `headers` and then the `body`. In _pythonic_ terms, what `zum` does is kind of _unpacks_ the three arrays consecutively, something like the following:

```py
arguments = [*params, *body]
arguments = [*params, *headers, *body]
zum(arguments)
```

Expand All @@ -274,16 +310,18 @@ params = ["id", "query"]
[endpoints.create-entity]
route = "/entity"
method = "post"
headers = ["Authorization"]
body = ["name", "city"]

[endpoints.create-nested]
route = "/entity/{id}"
method = "post"
params = ["id"]
headers = ["Authorization"]
body = ["name", "city"]
```

With that config file (using a hypothetical existing API), you could `GET /entity/420` to get the entity with id `420`, `GET /entity/420?query=nice` to search for the appearances of the word `nice` on the model of the entity with id `420`, `POST /entity` with some request body to create a new entity and `POST /entity/69` with some request body to create a new nested entity, child of the entity with id `69`.
With that config file (using a hypothetical existing API), you could `GET /entity/420` to get the entity with id `420`, `GET /entity/420?query=nice` to search for the appearances of the word `nice` on the model of the entity with id `420`, `POST /entity` with an authorization header and some request body to create a new entity and `POST /entity/69` with an authorization header and some request body to create a new nested entity, child of the entity with id `69`.

## Developing

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "zum"
version = "0.1.0"
version = "0.2.0"
description = "Stop writing scripts to test your APIs. Call them as CLIs instead."
license = "MIT"
authors = ["Daniel Leal <[email protected]>"]
Expand Down
20 changes: 20 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
"""
Module to hold all the fixtures and stuff that needs to get auto-imported
by PyTest.
"""

import httpx
import pytest


@pytest.fixture
def patch_request(monkeypatch):
def mock_request(method, url, *args, **kwargs):
return {
"method": method,
"url": url,
"headers": kwargs["headers"],
"json": kwargs["json"],
}

monkeypatch.setattr(httpx, "request", mock_request)
4 changes: 4 additions & 0 deletions tests/requests/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,16 @@ def setup_method(self):
"route": "/example/{id}?query={query}",
"method": "post",
"params": ["id", "query"],
"headers": ["Authorization"],
"body": ["name", "city"],
}
self.params = {"id": 69, "query": "nais"}
self.headers = {"Authorization": "Bearer F"}
self.body = {"name": "Dani", "city": "Barcelona"}
self.arguments = [
self.params["id"],
self.params["query"],
self.headers["Authorization"],
self.body["name"],
self.body["city"],
]
Expand All @@ -28,5 +31,6 @@ def test_request_generation(self):
request = generate_request(self.raw_endpoint, self.arguments)
assert isinstance(request, Request)
assert request.params == self.params
assert request.headers == self.headers
assert request.body == self.body
assert request.route == self.expected_route
9 changes: 8 additions & 1 deletion tests/requests/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,18 @@

class TestRequestModel:
def setup_method(self):
self.simple = {"route": "/example", "method": "get", "params": {}, "body": {}}
self.simple = {
"route": "/example",
"method": "get",
"params": {},
"headers": {},
"body": {},
}
self.complex = {
"route": "/example/{id}?query={query}",
"method": "get",
"params": {"query": "nais", "id": 69},
"headers": {},
"body": {},
}
self.simple_route = "/example"
Expand Down
133 changes: 133 additions & 0 deletions tests/test_executor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import pytest

from zum.executor import execute
from zum.requests.models import Request


class TestExecute:
@pytest.fixture(autouse=True)
def patch_request(self, patch_request):
pass

def setup_method(self):
self.base_url = "http://localhost:8000"
self.get = {
"request": Request("/example", "get", {}, {}, {}),
"response": {
"method": "get",
"url": f"{self.base_url}/example",
"headers": {},
"json": {},
},
}
self.post = {
"request": Request("/example", "post", {}, {}, {}),
"response": {
"method": "post",
"url": f"{self.base_url}/example",
"headers": {},
"json": {},
},
}
self.only_params = {
"request": Request("/example/{id}", "get", {"id": "2"}, {}, {}),
"response": {
"method": "get",
"url": f"{self.base_url}/example/2",
"headers": {},
"json": {},
},
}
self.only_headers = {
"request": Request("/example", "get", {}, {"token": "2"}, {}),
"response": {
"method": "get",
"url": f"{self.base_url}/example",
"headers": {"token": "2"},
"json": {},
},
}
self.only_body = {
"request": Request("/example", "get", {}, {}, {"name": "dani"}),
"response": {
"method": "get",
"url": f"{self.base_url}/example",
"headers": {},
"json": {"name": "dani"},
},
}
self.params_and_headers = {
"request": Request("/example/{id}", "get", {"id": "2"}, {"token": "2"}, {}),
"response": {
"method": "get",
"url": f"{self.base_url}/example/2",
"headers": {"token": "2"},
"json": {},
},
}
self.params_and_body = {
"request": Request(
"/example/{id}", "get", {"id": "2"}, {}, {"name": "dani"}
),
"response": {
"method": "get",
"url": f"{self.base_url}/example/2",
"headers": {},
"json": {"name": "dani"},
},
}
self.headers_and_body = {
"request": Request("/example", "get", {}, {"token": "2"}, {"name": "dani"}),
"response": {
"method": "get",
"url": f"{self.base_url}/example",
"headers": {"token": "2"},
"json": {"name": "dani"},
},
}
self.params_and_headers_and_body = {
"request": Request(
"/example/{id}", "get", {"id": "2"}, {"token": "2"}, {"name": "dani"}
),
"response": {
"method": "get",
"url": f"{self.base_url}/example/2",
"headers": {"token": "2"},
"json": {"name": "dani"},
},
}

def test_simple_requests(self):
get_response = execute(self.base_url, self.get["request"])
assert get_response == self.get["response"]

post_response = execute(self.base_url, self.post["request"])
assert post_response == self.post["response"]

def test_only_params(self):
response = execute(self.base_url, self.only_params["request"])
assert response == self.only_params["response"]

def test_only_headers(self):
response = execute(self.base_url, self.only_headers["request"])
assert response == self.only_headers["response"]

def test_only_body(self):
response = execute(self.base_url, self.only_body["request"])
assert response == self.only_body["response"]

def test_params_and_headers(self):
response = execute(self.base_url, self.params_and_headers["request"])
assert response == self.params_and_headers["response"]

def test_params_and_body(self):
response = execute(self.base_url, self.params_and_body["request"])
assert response == self.params_and_body["response"]

def test_headers_and_body(self):
response = execute(self.base_url, self.headers_and_body["request"])
assert response == self.headers_and_body["response"]

def test_params_and_headers_and_body(self):
response = execute(self.base_url, self.params_and_headers_and_body["request"])
assert response == self.params_and_headers_and_body["response"]
2 changes: 1 addition & 1 deletion zum/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
Init file for the zum library.
"""

version_info = (0, 1, 0)
version_info = (0, 2, 0)
__version__ = ".".join([str(x) for x in version_info])
4 changes: 3 additions & 1 deletion zum/executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,6 @@
def execute(base_url: str, request: Request) -> httpx.Response:
"""Executes a request with the base URL."""
url = f"{base_url.rstrip('/')}{request.route}"
return httpx.request(request.method, url, json=request.body)
return httpx.request(
request.method, url, headers=request.headers, json=request.body
)
5 changes: 4 additions & 1 deletion zum/requests/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,8 @@ def generate_request(raw_endpoint: Dict[str, Any], arguments: List[str]) -> Requ
params, remaining_arguments = reduce_arguments(
raw_endpoint.get("params"), arguments
)
headers, remaining_arguments = reduce_arguments(
raw_endpoint.get("headers"), remaining_arguments
)
body, _ = reduce_arguments(raw_endpoint.get("body"), remaining_arguments)
return Request(raw_endpoint["route"], raw_endpoint["method"], params, body)
return Request(raw_endpoint["route"], raw_endpoint["method"], params, headers, body)
4 changes: 3 additions & 1 deletion zum/requests/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,14 @@ def __init__(
self,
route: str,
method: str,
params: Dict[str, Any],
params: Dict[str, str],
headers: Dict[str, str],
body: Dict[str, Any],
) -> None:
self._route = route
self.method = method
self.params = params
self.headers = headers
self.body = body

@property
Expand Down

0 comments on commit edbda29

Please sign in to comment.