Skip to content

Commit

Permalink
test: extend integration tests (#133)
Browse files Browse the repository at this point in the history
  • Loading branch information
uniqueg committed Jun 25, 2022
1 parent 25cb245 commit 54c6929
Show file tree
Hide file tree
Showing 9 changed files with 320 additions and 135 deletions.
3 changes: 1 addition & 2 deletions .github/workflows/checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,8 @@ jobs:
run: |
export PY_VERSION=${{ matrix.py-version-img[0] }}
export PY_IMAGE=${{ matrix.py-version-img[1] }}
export TAG="petstore-py${PY_VERSION}"
docker build \
-t ${DOCKERHUB_ORG}/${REPO_NAME}:${TAG} \
-t ${DOCKERHUB_ORG}/${REPO_NAME}:petstore \
-f docker/Dockerfile \
--build-arg PY_IMAGE=${PY_IMAGE} \
.
Expand Down
3 changes: 1 addition & 2 deletions examples/petstore/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
### BASE IMAGE ###
ARG FOCA_IMAGE=3.10
FROM elixircloud/foca:petstore-py$FOCA_IMAGE
FROM elixircloud/foca:petstore

# Metadata
LABEL software="Petstore application"
Expand Down
70 changes: 41 additions & 29 deletions examples/petstore/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,15 @@ FOCA makes sure that
* FOCA configuration parameters are validated
* requests and responses sent to/from the API endpoints configured in the
[Petstore][app-specs] [OpenAPI][res-openapi] specification are validated
* a [MongoDB][res-mongo-db] collection to store pets in is registered with The
* a [MongoDB][res-mongo-db] collection to store pets in is registered with the
app
* [CORS][res-cors] is enabled
* handles exceptions (here only returns `500 / Internal Server Error` for all
problems)
* exceptions are handled according to the definitions in the `exceptions`
dictionary in module [`exceptions`][app-exceptions]

Apart from writing the configuration file, all that was left for us to do to
set up this example app was to write a _very_ simple app [entry point
module][app-entry-point], implement the [endpoint controller
module][app-entrypoint], implement the [endpoint controller
logic][app-controllers] and prepare the [`Dockerfile`][app-dockerfile] and
[Docker Compose][res-docker-compose] [configuration][app-docker-compose] for
easy shipping/installation!
Expand Down Expand Up @@ -53,10 +53,35 @@ Clone repository:
git clone https://github.com/elixir-cloud-aai/foca.git
```

Traverse to the repository root directory:

```bash
cd foca
```

Locally build the FOCA base image based on the current state of FOCA and based
on one of the supported Python versions (here: `3.9`):

```bash
docker build \
-t elixircloud/foca:petstore \
-f docker/Dockerfile \
.
```

> If you wish to modify FOCA code itself and then have those code changes
> reflected in the example app, don't forget to rebuild the FOCA base image
> before re-starting services via `docker-compose` in the next steps!
>
> You can also optionally build FOCA with another supported version of Python.
> In that case, add `--build-arg PY_IMAGE="PYTHON_IMAGE_TAG"` to the `docker
> build` command above, where `PYTHON_IMAGE_TAG` is the tag of the official
> Python image you would like to use.
Traverse to example app directory:

```bash
cd foca/examples/petstore
cd examples/petstore
```

Build and run services in detached/daemonized mode:
Expand All @@ -72,8 +97,9 @@ docker-compose up -d --build
> change the **first** of the two numbers listed below the corresponding
> `ports` keyword to some unused/open port. Note that if you change the mapped
> port for the `app` service you will need to manually append it to `localhost`
> when you access the API (or the Swagger UI) in subsequent steps, like, e.g.,
> so: `http://localhost:8080/`.
> when you access the API (or the Swagger UI) in subsequent steps. For example,
> assuming you would like to run the app on port `8080` instead of the default
> of `80`, then the app will be availabe via : `http://localhost:8080/`.
That's it, you can now visit the application's [Swagger UI][res-swagger-ui] in
your browser, e.g.,:
Expand All @@ -82,7 +108,7 @@ your browser, e.g.,:
firefox http://localhost/ui # or use your browser of choice
```

> Mac users may need to replace `localhost` with `0.0.0.0`.
> Note that Mac users may need to replace `localhost` with `0.0.0.0`.
## Explore app

Expand Down Expand Up @@ -116,15 +142,15 @@ Alternatively, you can access the API endpoints programmatically, e.g., via
```console
curl -X GET \
--header 'Accept: application/json' \
'http://localhost/pets/0'
'http://localhost/pets/0' # replace 0 with the the pet ID of choice
```

* To **delete a pet**: :-(

```console
curl -X DELETE \
--header 'Accept: application/json' \
'http://localhost/pets/0'
'http://localhost/pets/0' # replace 0 with the the pet ID of choice
```

## Modify app
Expand All @@ -133,32 +159,18 @@ You can make use of this example to create your own app. Just modify any or all
of the following:

* [FOCA configuration file][app-config]
* [Main application module][app-entrypoint]
* [API specification][app-specs]
* [Endpoint controller module][app-controllers]
* [Main application module][app-entrypoint]
* [Dockerfile][app-dockerfile]
* [Docker Compose configuration][app-docker-compose]

### Modifying FOCA

In case you want to change FOCA itself and want the code changes to be
reflected in the Petstore app, you will need to manually rebuild the FOCA
container image like so:

```bash
docker build -t elixircloud/foca:latest . # execute in the FOCA root directory
```

Then re-build and start the Petstore app as described before:

```bash
docker-compose up --build -d # execute in _this_ directory
```
* [Registered exceptions][app-exceptions]
* [`Dockerfile`][app-dockerfile]
* [`docker-compose` configuration][app-docker-compose]

[app-config]: config.yaml
[app-controllers]: controllers.py
[app-dockerfile]: Dockerfile
[app-docker-compose]: docker-compose.yaml
[app-exceptions]: exceptions.py
[app-entrypoint]: app.py
[app-specs]: petstore.yaml
[docs]: <https://foca.readthedocs.io/en/latest/>
Expand Down
5 changes: 5 additions & 0 deletions examples/petstore/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,8 @@ db:
collections:
pets:
indexes: null

exceptions:
required_members: [['message'], ['code']]
status_member: ['code']
exceptions: exceptions.exceptions
135 changes: 60 additions & 75 deletions examples/petstore/controllers.py
Original file line number Diff line number Diff line change
@@ -1,92 +1,77 @@
"""Petstore controllers."""

import logging

from flask import (current_app, make_response)
from werkzeug.exceptions import NotFound
from pymongo.collection import Collection

logger = logging.getLogger(__name__)
from exceptions import NotFound

error_response = {
'code': 500,
'message': 'Something went wrong.',
}
logger = logging.getLogger(__name__)


def findPets(limit=None, tags=None):
try:
db_collection = (
current_app.config.foca.db.dbs['petstore']
.collections['pets'].client
)
filter_dict = {} if tags is None else {'tag': {'$in': tags}}
if not limit:
limit = 0
records = db_collection.find(
filter_dict,
{'_id': False}
).sort([('$natural', -1)]).limit(limit)
return list(records)
except Exception as e:
logger.error(f"{type(e).__name__}: {e}")
return error_response
db_collection: Collection = (
current_app.config.foca.db.dbs['petstore']
.collections['pets'].client
)
filter_dict = {} if tags is None else {'tag': {'$in': tags}}
if not limit:
limit = 0
records = db_collection.find(
filter_dict,
{'_id': False}
).sort([('$natural', -1)]).limit(limit)
return list(records)


def addPet(pet):
try:
db_collection = (
current_app.config.foca.db.dbs['petstore']
.collections['pets'].client
)
counter = 0
ctr = db_collection.find({}).sort([('$natural', -1)])
if not ctr.count() == 0:
counter = ctr[0].get('id') + 1
record = {
"id": counter,
"name": pet['name'],
"tag": pet['tag']
}
db_collection.insert_one(record)
del record['_id']
return record
except Exception as e:
logger.error(f"{type(e).__name__}: {e}")
return error_response
db_collection: Collection = (
current_app.config.foca.db.dbs['petstore']
.collections['pets'].client
)
counter = 0
ctr = db_collection.find({}).sort([('$natural', -1)])
if not ctr.count() == 0:
counter = ctr[0].get('id') + 1
record = {
"id": counter,
"name": pet['name'],
"tag": pet['tag']
}
db_collection.insert_one(record)
del record['_id']
return record


def findPetById(id):
try:
db_collection = (
current_app.config.foca.db.dbs['petstore']
.collections['pets'].client
)
record = db_collection.find_one(
{"id": id},
{'_id': False},
)
if not record:
raise NotFound
return record
except Exception as e:
logger.error(f"{type(e).__name__}: {e}")
return error_response
db_collection: Collection = (
current_app.config.foca.db.dbs['petstore']
.collections['pets'].client
)
record = db_collection.find_one(
{"id": id},
{'_id': False},
)
if record is None:
raise NotFound
return record


def deletePet(id):
try:
db_collection = (
current_app.config.foca.db.dbs['petstore']
.collections['pets'].client
)
record = db_collection.find_one(
{"id": id},
{'_id': False},
)
if not record:
raise NotFound
db_collection.delete_one({"id": id})
response = make_response('', 204)
response.mimetype = current_app.config['JSONIFY_MIMETYPE']
return response
except Exception as e:
logger.error(f"{type(e).__name__}: {e}")
return error_response
db_collection: Collection = (
current_app.config.foca.db.dbs['petstore']
.collections['pets'].client
)
record = db_collection.find_one(
{"id": id},
{'_id': False},
)
if record is None:
raise NotFound
db_collection.delete_one(
{"id": id},
)
response = make_response('', 204)
response.mimetype = current_app.config['JSONIFY_MIMETYPE']
return response
2 changes: 0 additions & 2 deletions examples/petstore/docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ services:
build:
context: .
dockerfile: Dockerfile
args:
FOCA_IMAGE: ${PY_VERSION:-3.10}
restart: unless-stopped
links:
- mongodb
Expand Down
26 changes: 26 additions & 0 deletions examples/petstore/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
"""Petstore exceptions."""

from connexion.exceptions import BadRequestProblem
from werkzeug.exceptions import (
InternalServerError,
NotFound,
)

exceptions = {
Exception: {
"message": "An unexpected error occurred.",
"code": 500,
},
BadRequestProblem: {
"message": "The request is malformed.",
"code": 400,
},
NotFound: {
"message": "The requested resource wasn't found.",
"code": 404,
},
InternalServerError: {
"message": "An unexpected error occurred.",
"code": 500,
},
}

0 comments on commit 54c6929

Please sign in to comment.