Skip to content

Commit

Permalink
feat: add api and crud testing
Browse files Browse the repository at this point in the history
  • Loading branch information
jordipuigbou committed Nov 16, 2023
0 parents commit 72f56ef
Show file tree
Hide file tree
Showing 30 changed files with 579 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.venv
.vscode

**/__pycache__
35 changes: 35 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
APIS_DIR = $(CURDIR)/apis
CRUD_DIR = $(CURDIR)/crud

.PHONY: help
help: ## Show a list of available commands
@fgrep -h "##" $(MAKEFILE_LIST) | fgrep -v fgrep | sed -e 's/\\$$//' | sed -e 's/##//'

.PHONY: build-apis
build-apis: ## build apis testing
docker compose -f $(APIS_DIR)/docker-compose.yml build

.PHONY: run-apis
run-apis: ## run apis testing
docker compose -f $(APIS_DIR)/docker-compose.yml up

.PHONY: clean-apis
clean-apis: ## clean apis
docker compose -f $(APIS_DIR)/docker-compose.yml down

.PHONY: build-crud
build-crud: ## build crud
docker compose -f $(CRUD_DIR)/docker-compose.yml build

.PHONY: run-crud
run-crud: ## run crud
docker compose -f $(CRUD_DIR)/docker-compose.yml run backend

.PHONY: run-crud-tests
run-crud-tests: ## run crud tests
docker compose -f $(CRUD_DIR)/docker-compose.yml down
docker compose -f $(CRUD_DIR)/docker-compose.yml run test

.PHONY: run-crud-clean
clean-crud: ## build run crud
docker compose -f $(CRUD_DIR)/docker-compose.yml down
Binary file added Prueba.zip
Binary file not shown.
83 changes: 83 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# Prueba Técnica - Desarrollador Backend

## Descripción

El código de este repositorio es una prueba técnica con el perfil de backend. El lenguaje elegido para realizar la prueba ha sido Python.

## Pruebas

Para facilitar la ejecución de las pruebas se creado un [Makefile](./Makefile) con los comandos necesarios.
Se puede comprobar el catálogo de comandos con:

```bash
make help
```

Como pre-requisito se necesita tener instalado [Docker](https://docs.docker.com/get-docker/) y [Docker Compose](https://docs.docker.com/compose/install/).

### Tratamiento de datos en API

El código Python que se ha desarrollado para la prueba de la `petstore` se encuentra en la carpeta [apis](./apis/).

- En la carpeta de [petstore](./apis/petstore) se encuentra el código para hacer las llamadas a la api de `petstore` con las distintas clases y métodos necesarios.
- El fichero [test.py](./apis/test.py) contiene el código con el flujo de llamadas requerido en el ejercicio.

Para ejecutar el código se puede hacer con los siguientes comandos:

```bash
make build-apis
make run-apis
```

Para terminar se puede limpiar el entorno con:

```bash
make clean-apis
```

### Desarrollo de una API (CRUD)

En la carpeta [crud](./crud/) se encuentra el código para el desarrollo de una API con las operaciones CRUD.
Concretamente, el código está en el fichero [backend.py](./crud/backend.py).
Para crear la API se ha utilizar [Flask](https://flask.palletsprojects.com/en/2.0.x/) y como base de datos se ha utilizado [MongoDB](https://www.mongodb.com/).

Para levantar el servicio se ha montado un entorno con [Docker Compose](https://docs.docker.com/compose/) que tiene los siguientes servicios:

- backend: servicio con la API desarrollada en Python.
- mongo: base de datos MongoDB.
- mongo-express: interfaz web para la base de datos MongoDB.
- test: servicio para ejecutar los tests de aceptación de la API con [Behave](https://behave.readthedocs.io/en/stable/).

Para levantar el entorno se puede hacer con los siguientes comandos:

```bash
make build-crud
make run-crud
```

Con docker-compose se expone la api en el puerto `8888` de localhost:

- `http://localhost:8888`

Los endpoints asociados son:

- GET /health
- GET /products
- GET /products/{id}
- POST /products
- PUT /products/{id}
- DELETE /products/{id}

Si se quiere ejecutar los tests de aceptación se puede hacer con:

```bash
make run-crud-tests
```

Finalmente, si se quiere limpiar el entorno se puede hacer con:

```bash
make clean-crud
```

De esta manera se ejecuta la feature [product.feature](./crud/features/product.feature) que contiene un set básico (happy path) de tests de aceptación de la API.
1 change: 1 addition & 0 deletions apis/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
**/__pycache__
7 changes: 7 additions & 0 deletions apis/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
FROM python:3.10.13-slim-bullseye

COPY ./requirements.txt /root/requirements.txt
RUN pip install -r /root/requirements.txt

COPY ./petstore /root/petstore
COPY ./test.py /root/test.py
9 changes: 9 additions & 0 deletions apis/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
version: '3.8'

services:
api:
image: api-test-image
build:
context: .
dockerfile: Dockerfile
command: ["python", "/root/test.py"]
Empty file added apis/petstore/__init__.py
Empty file.
Empty file.
2 changes: 2 additions & 0 deletions apis/petstore/config/api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
URL="https://petstore.swagger.io/"
API_VERSION="v2"
Empty file added apis/petstore/pet/__init__.py
Empty file.
80 changes: 80 additions & 0 deletions apis/petstore/pet/pet.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import requests
from ..config.api import URL, API_VERSION
from os import path



PET_ENDPOINT="pet"

def get_pet_by_status(status):
response = requests.get(path.join(URL,API_VERSION,PET_ENDPOINT,"findByStatus"), params={"status": status})
return response

def list_sold_pets():
response = get_pet_by_status("sold")
pet_tuple = {}
for pet in response.json():
try:
pet_tuple[pet["id"]] = pet["name"]
except KeyError:
print(f"Pet {pet['id']} does not have a name")
return pet_tuple

class SoldPets:
def __init__(self, sold_pets):
self.sold_pets = sold_pets

def count_repeated_pet_names(self):
pet_names = []
for pet in self.sold_pets:
pet_names.append(self.sold_pets[pet])
return {pet_name: pet_names.count(pet_name) for pet_name in pet_names}

class Pet:
def __init__(self, id, category, name, photoUrls, tags, status):
self.id = id
self.category = category
self.name = name
self.photoUrls = photoUrls
self.tags = tags
self.status = status

def __str__(self):
return f"Pet: {self.name}"

def __repr__(self):
return f"Pet: {self.name}"

def create_pet(self):
payload = {
"id": self.id,
"category": self.category,
"name": self.name,
"photoUrls": self.photoUrls,
"tags": self.tags,
"status": self.status
}
response = requests.post(path.join(URL,API_VERSION,PET_ENDPOINT), json=payload)
return response

def get_pet(self):
response = requests.get(path.join(URL,API_VERSION,PET_ENDPOINT,self.id))
return response



def update_pet(self):
payload = {
"id": self.id,
"category": self.category,
"name": self.name,
"photoUrls": self.photoUrls,
"tags": self.tags,
"status": self.status
}
response = requests.put(path.join(URL,API_VERSION,PET_ENDPOINT), json=payload)
return response

def delete_pet(self):
response = requests.delete(path.join(URL,API_VERSION,PET_ENDPOINT,self.id))

Empty file added apis/petstore/user/__init__.py
Empty file.
45 changes: 45 additions & 0 deletions apis/petstore/user/user.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import requests
from ..config.api import URL, API_VERSION
from os import path

USER_ENDPOINT="user"


class User:
def __init__(self, username, password, email, firstName, lastName, phone, userStatus):
self.username = username
self.password = password
self.email = email
self.firstName = firstName
self.lastName = lastName
self.phone = phone
self.userStatus = userStatus


def __str__(self):
return f"User: {self.username}"

def __repr__(self):
return f"User: {self.username}"

def create_user(self):
payload = {
"id": 0,
"username": self.username,
"firstName": self.firstName,
"lastName": self.lastName,
"email": self.email,
"password": self.password,
"phone": self.phone,
"userStatus": self.userStatus
}
# join paths URL, API_VERSION, USER_ENDPOINT
request_path = path.join(URL,API_VERSION,USER_ENDPOINT)

response = requests.post(request_path, json=payload)
return response

def get_user(self):
response = requests.get(path.join(URL,API_VERSION,USER_ENDPOINT,self.username))
return response

2 changes: 2 additions & 0 deletions apis/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
requests
pytest
49 changes: 49 additions & 0 deletions apis/test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
from petstore.user.user import User
from petstore.pet.pet import Pet, SoldPets, list_sold_pets


print(">>>>>>>> Create User >>>>>>>>")

user = User(username="test_user", password="password", email="[email protected]",
firstName="Test", lastName="User", phone="1234567890", userStatus=0)
response = user.create_user()
print(f"Create user response: {response.json()}")
assert response.status_code == 200

print(">>>>>>>> Get created user info >>>>>>>>")

response = user.get_user()
assert response.status_code == 200
assert response.json()["username"] == "test_user"
assert response.json()["firstName"] == "Test"
assert response.json()["lastName"] == "User"

print(f"User info: {response.json()}")


print(">>>>>>>> Create sold pets >>>>>>>>")
pet_a = Pet(id=0, category={"id": 0, "name": "TestJordi"}, name="TestJordi", photoUrls=["string"], tags=[{"id": 0, "name": "TestJordi"}], status="sold")
pet_b = Pet(id=0, category={"id": 0, "name": "TestJordi"}, name="TestJordi", photoUrls=["string"], tags=[{"id": 0, "name": "TestJordi"}], status="sold")
response = pet_a.create_pet()
assert response.status_code == 200
print(f"Create pet response: {response.json()}")
response = pet_b.create_pet()
assert response.status_code == 200
print(f"Create pet response: {response.json()}")

print(">>>>>>>> List sold pets >>>>>>>>")
sold_pets = list_sold_pets()

for pet in sold_pets.items():
print(f"Sold pet: {pet}")

print(">>>>>>>> Count pet repeated names >>>>>>>>")

sold_pets_object = SoldPets(sold_pets)
pet_repeated_names = sold_pets_object.count_repeated_pet_names()

print(f"Count of pet names: {pet_repeated_names}")




1 change: 1 addition & 0 deletions crud/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
**/__pycache__
10 changes: 10 additions & 0 deletions crud/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
FROM python:3.10.13-slim-bullseye

COPY ./requirements.txt /root/requirements.txt
RUN pip install -r /root/requirements.txt

COPY ./backend.py /root/backend.py
COPY ./endpoints /root/endpoints
WORKDIR /root

EXPOSE 8888
Empty file added crud/__init__.py
Empty file.
14 changes: 14 additions & 0 deletions crud/backend.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@

from flask import Flask
from flask_restful import Api
from endpoints.products import Products
from endpoints.health import Health

app = Flask(__name__)
api = Api(app)

api.add_resource(Products, '/products', '/products/<id>')
api.add_resource(Health, '/health')

if __name__ == '__main__':
app.run(host='0.0.0.0', port=8888, debug=True)
Loading

0 comments on commit 72f56ef

Please sign in to comment.