Adicionar testes unitários
This commit is contained in:
parent
88f0ff9800
commit
8e8d9237b7
|
@ -1,4 +1,4 @@
|
|||
__pycache__/
|
||||
venv/
|
||||
pyrightconfig.json
|
||||
database.db
|
||||
*.db
|
||||
|
|
|
@ -18,6 +18,12 @@ Inicie o servidor de desenvolvimento:
|
|||
flask run
|
||||
```
|
||||
|
||||
## Rodando Testes
|
||||
No diretório **raíz**:
|
||||
```
|
||||
python3 -m api.tests
|
||||
```
|
||||
|
||||
Por padrão o servidor tem sua interface em `http://localhost:5000`.
|
||||
|
||||
# Desenvolvimento
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
#!/bin/python3
|
||||
from flask import Flask
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
from flask_marshmallow import Marshmallow
|
||||
|
@ -10,6 +9,4 @@ db = SQLAlchemy(app)
|
|||
ma = Marshmallow(app)
|
||||
Migrate(app, db)
|
||||
|
||||
import api.models.trainer
|
||||
import api.models.pokemon_owned
|
||||
import api.routes.routes
|
||||
|
|
|
@ -3,15 +3,15 @@ from api.views import trainer, pokemon_owned, helper, errors
|
|||
from flask import request
|
||||
import asyncio
|
||||
|
||||
@app.route("/trainer/<int:trainerId>/", methods=["GET"])
|
||||
@app.route("/trainer/<int:trainerId>", methods=["GET"])
|
||||
def route_get_trainer(trainerId):
|
||||
return trainer.get_trainer(trainerId)
|
||||
|
||||
@app.route("/trainer/", methods=["GET"])
|
||||
@app.route("/trainer", methods=["GET"])
|
||||
def route_get_trainers():
|
||||
return trainer.get_trainers()
|
||||
|
||||
@app.route("/trainer/", methods=["POST"])
|
||||
@app.route("/trainer", methods=["POST"])
|
||||
def route_create_trainer():
|
||||
return trainer.post_trainer()
|
||||
|
||||
|
|
|
@ -0,0 +1,360 @@
|
|||
#!/bin/python3
|
||||
from api.app import app, db
|
||||
from api.models.trainer import Trainer
|
||||
from api.models.pokemon_owned import PokemonOwned
|
||||
from flask_testing import TestCase
|
||||
import unittest
|
||||
|
||||
class MainTestCase(TestCase):
|
||||
|
||||
def create_app(self):
|
||||
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///testing.db"
|
||||
db.drop_all()
|
||||
db.create_all()
|
||||
trainers = (
|
||||
Trainer(
|
||||
nickname="jose",
|
||||
first_name="José",
|
||||
last_name="da Silva",
|
||||
email="josedasilva.2021@gmail.com",
|
||||
password="1234",
|
||||
team="Team Valor"
|
||||
),
|
||||
Trainer(
|
||||
nickname="joao",
|
||||
first_name="João",
|
||||
last_name="Oliveira",
|
||||
email="joaooliveira@hotmail.com",
|
||||
password="senha",
|
||||
team="Team Instinct"
|
||||
),
|
||||
Trainer(
|
||||
nickname="ricardo",
|
||||
first_name="Ricardo",
|
||||
last_name="Teixeira",
|
||||
email="ricardo.teixeira@gmail.com",
|
||||
password="ricardo",
|
||||
team="Team Mystic"
|
||||
),
|
||||
)
|
||||
pokemons = (
|
||||
PokemonOwned(
|
||||
name="Fluffy",
|
||||
level=1,
|
||||
pokemon_id=12,
|
||||
trainer_id=1
|
||||
),
|
||||
PokemonOwned(
|
||||
name="Dinossauro",
|
||||
level=1,
|
||||
pokemon_id=2,
|
||||
trainer_id=1
|
||||
),
|
||||
PokemonOwned(
|
||||
name="Outro",
|
||||
level=1,
|
||||
pokemon_id=4,
|
||||
trainer_id=1
|
||||
),
|
||||
)
|
||||
db.session.add_all(trainers)
|
||||
db.session.add_all(pokemons)
|
||||
db.session.commit()
|
||||
self.client = app.test_client()
|
||||
return app
|
||||
|
||||
def test_post_trainer(self):
|
||||
trainer = {
|
||||
"nickname": "rodrigro",
|
||||
"first_name": "Ricardo",
|
||||
"last_name": "Lopes",
|
||||
"email": "rlopes@outlook.com",
|
||||
"password": "dummy",
|
||||
"team": "Team Mystic"
|
||||
}
|
||||
response = self.client.post("/trainer", json=trainer, follow_redirects=True)
|
||||
self.assert_status(response, 201)
|
||||
self.assertEqual(trainer["email"], response.get_json()["email"])
|
||||
|
||||
def test_post_trainer_duplicate(self):
|
||||
trainer = {
|
||||
"nickname": "julio",
|
||||
"first_name": "Julio",
|
||||
"last_name": "Sobreiro",
|
||||
"email": "julho.sob@yahoo.com",
|
||||
"password": "0987",
|
||||
"team": "Team Mystic"
|
||||
}
|
||||
self.client.post("/trainer", json=trainer, follow_redirects=True)
|
||||
response = self.client.post("/trainer", json=trainer, follow_redirects=True)
|
||||
self.assert_status(response, 409)
|
||||
|
||||
def test_post_trainer_invalid_team(self):
|
||||
trainer = {
|
||||
"nickname": "cesar",
|
||||
"first_name": "Cesar",
|
||||
"last_name": "Pereira",
|
||||
"email": "cesereira@gmail.com",
|
||||
"password": "04031994",
|
||||
"team": "Team Inventado"
|
||||
}
|
||||
response = self.client.post("/trainer", json=trainer, follow_redirects=True)
|
||||
self.assert_400(response)
|
||||
|
||||
def test_post_trainer_missing_fields(self):
|
||||
trainer = {}
|
||||
response = self.client.post("/trainer", json=trainer, follow_redirects=True)
|
||||
self.assert_400(response)
|
||||
|
||||
def test_authenticate(self):
|
||||
login = {
|
||||
"email": "josedasilva.2021@gmail.com",
|
||||
"password": "1234",
|
||||
}
|
||||
response = self.client.post("/trainer/authenticate", json=login, follow_redirects=True)
|
||||
self.assert_200(response)
|
||||
self.assertIn(b"id", response.data)
|
||||
self.assertIn(b"token", response.data)
|
||||
|
||||
def test_authenticate_wrong(self):
|
||||
login = {
|
||||
"email": "josedasilva.2021@gmail.com",
|
||||
"password": "wrong_password",
|
||||
}
|
||||
response = self.client.post("/trainer/authenticate", json=login, follow_redirects=True)
|
||||
self.assert_401(response)
|
||||
|
||||
def test_authenticate_not_found(self):
|
||||
login = {
|
||||
"email": "notrainer@withemail.com",
|
||||
"password": "dummy_password",
|
||||
}
|
||||
response = self.client.post("/trainer/authenticate", json=login, follow_redirects=True)
|
||||
self.assert_401(response)
|
||||
|
||||
def test_authenticate_no_login(self):
|
||||
login = {}
|
||||
response = self.client.post("/trainer/authenticate", json=login, follow_redirects=True)
|
||||
self.assert_401(response)
|
||||
|
||||
def test_get_trainers(self):
|
||||
response = self.client.get("/trainer", follow_redirects=True)
|
||||
self.assert_200(response)
|
||||
self.assertIn(b"jose", response.data)
|
||||
self.assertIn(b"joao", response.data)
|
||||
self.assertIn(b"ricardo", response.data)
|
||||
|
||||
def test_get_trainer_by_nickname(self):
|
||||
response = self.client.get("/trainer?nickname=jose", follow_redirects=True)
|
||||
self.assert_200(response)
|
||||
self.assertIn(b"jose", response.data)
|
||||
|
||||
def test_get_trainer_by_nickname_not_found(self):
|
||||
response = self.client.get("/trainer?nickname=somerandomnickname", follow_redirects=True)
|
||||
self.assert_200(response)
|
||||
self.assertEqual(response.data, b"[]")
|
||||
|
||||
def test_get_trainer_by_nickname_contains(self):
|
||||
response = self.client.get("/trainer?nickname_contains=jo", follow_redirects=True)
|
||||
self.assert_200(response)
|
||||
self.assertIn(b"jose", response.data)
|
||||
self.assertIn(b"joao", response.data)
|
||||
|
||||
def test_get_trainer_by_nickname_contains_limit(self):
|
||||
response = self.client.get("/trainer?nickname_contains=jo&limit=1", follow_redirects=True)
|
||||
self.assert_200(response)
|
||||
self.assertIn(b"jose", response.data)
|
||||
self.assertNotIn(b"joao", response.data)
|
||||
|
||||
def test_get_trainer_by_nickname_contains_offset(self):
|
||||
response = self.client.get("/trainer?nickname_contains=jo&offset=1", follow_redirects=True)
|
||||
self.assert_200(response)
|
||||
self.assertNotIn(b"jose", response.data)
|
||||
self.assertIn(b"joao", response.data)
|
||||
|
||||
def test_get_trainer_by_id(self):
|
||||
response = self.client.get("/trainer/1", follow_redirects=True)
|
||||
self.assert_200(response)
|
||||
self.assertIn(b"jose", response.data)
|
||||
self.assertNotIn(b"joao", response.data)
|
||||
|
||||
def test_get_trainer_by_id_not_found(self):
|
||||
response = self.client.get("/trainer/1000", follow_redirects=True)
|
||||
self.assertEqual(response.status_code, 404)
|
||||
|
||||
def test_get_pokemons(self):
|
||||
response = self.client.get("/trainer/1/pokemon", follow_redirects=True)
|
||||
self.assert_200(response)
|
||||
self.assertIn(b"Fluffy", response.data)
|
||||
self.assertIn(b"Dinossauro", response.data)
|
||||
self.assertIn(b"Outro", response.data)
|
||||
self.assertIn(b"pokemon_data", response.data)
|
||||
|
||||
def test_get_pokemons_trainer_not_found(self):
|
||||
response = self.client.get("/trainer/1000/pokemon", follow_redirects=True)
|
||||
self.assertEqual(response.status_code, 404)
|
||||
|
||||
def test_get_pokemons_limit(self):
|
||||
response = self.client.get("/trainer/1/pokemon?limit=1", follow_redirects=True)
|
||||
self.assert_200(response)
|
||||
self.assertIn(b"Fluffy", response.data)
|
||||
self.assertNotIn(b"Dinossauro", response.data)
|
||||
self.assertNotIn(b"Outro", response.data)
|
||||
|
||||
def test_get_pokemons_offset(self):
|
||||
response = self.client.get("/trainer/1/pokemon?offset=1", follow_redirects=True)
|
||||
self.assert_200(response)
|
||||
self.assertNotIn(b"Fluffy", response.data)
|
||||
self.assertIn(b"Dinossauro", response.data)
|
||||
self.assertIn(b"Outro", response.data)
|
||||
|
||||
def test_get_pokemon_by_id(self):
|
||||
response = self.client.get("/trainer/1/pokemon/2", follow_redirects=True)
|
||||
self.assert_200(response)
|
||||
self.assertNotIn(b"Fluffy", response.data)
|
||||
self.assertIn(b"Dinossauro", response.data)
|
||||
self.assertNotIn(b"Outro", response.data)
|
||||
self.assertIn(b"pokemon_data", response.data)
|
||||
|
||||
def test_get_pokemon_by_id_not_found(self):
|
||||
response = self.client.get("/trainer/1/pokemon/1000", follow_redirects=True)
|
||||
self.assertEqual(response.status_code, 404)
|
||||
|
||||
def test_post_pokemon_no_auth(self):
|
||||
data = {
|
||||
"name": "Dummy",
|
||||
"level": 9,
|
||||
"pokemon_id": 12
|
||||
}
|
||||
response = self.client.post("/trainer/2/pokemon", json=data, follow_redirects=True)
|
||||
self.assert_401(response)
|
||||
|
||||
def test_post_pokemon(self):
|
||||
login = {
|
||||
"email": "joaooliveira@hotmail.com",
|
||||
"password": "senha",
|
||||
}
|
||||
auth = self.client.post("/trainer/authenticate", json=login, follow_redirects=True)
|
||||
self.assert_200(auth)
|
||||
token = auth.get_json()["token"]
|
||||
data = {
|
||||
"name": "Dummy",
|
||||
"level": 2,
|
||||
"pokemon_id": 12
|
||||
}
|
||||
response = self.client.post("/trainer/2/pokemon", json=data, headers={"Authorization":token}, follow_redirects=True)
|
||||
self.assert_status(response, 201)
|
||||
self.assertIn(b"Dummy", response.data)
|
||||
self.assertIn(b"pokemon_data", response.data)
|
||||
|
||||
def test_post_pokemon_trainer_not_found(self):
|
||||
login = {
|
||||
"email": "joaooliveira@hotmail.com",
|
||||
"password": "senha",
|
||||
}
|
||||
auth = self.client.post("/trainer/authenticate", json=login, follow_redirects=True)
|
||||
self.assert_200(auth)
|
||||
token = auth.get_json()["token"]
|
||||
data = {
|
||||
"name": "Dummy",
|
||||
"level": 2,
|
||||
"pokemon_id": 12
|
||||
}
|
||||
response = self.client.post("/trainer/200/pokemon", json=data, headers={"Authorization":token}, follow_redirects=True)
|
||||
self.assert_403(response)
|
||||
|
||||
# adicionando pokemon pra outro trainer
|
||||
def test_post_pokemon_forbidden(self):
|
||||
login = {
|
||||
"email": "joaooliveira@hotmail.com",
|
||||
"password": "senha",
|
||||
}
|
||||
auth = self.client.post("/trainer/authenticate", json=login, follow_redirects=True)
|
||||
self.assert_200(auth)
|
||||
token = auth.get_json()["token"]
|
||||
data = {
|
||||
"name": "Dummy",
|
||||
"level": 2,
|
||||
"pokemon_id": 12
|
||||
}
|
||||
response = self.client.post("/trainer/1/pokemon", json=data, headers={"Authorization":token}, follow_redirects=True)
|
||||
self.assert_403(response)
|
||||
|
||||
def test_post_pokemon_no_species(self):
|
||||
login = {
|
||||
"email": "joaooliveira@hotmail.com",
|
||||
"password": "senha",
|
||||
}
|
||||
auth = self.client.post("/trainer/authenticate", json=login, follow_redirects=True)
|
||||
self.assert_200(auth)
|
||||
token = auth.get_json()["token"]
|
||||
data = {
|
||||
"name": "Dumb",
|
||||
"level": 2,
|
||||
"pokemon_id": 12000
|
||||
}
|
||||
response = self.client.post("/trainer/2/pokemon", json=data, headers={"Authorization":token}, follow_redirects=True)
|
||||
self.assert_404(response)
|
||||
|
||||
def test_delete_pokemon_trainer_not_found(self):
|
||||
login = {
|
||||
"email": "joaooliveira@hotmail.com",
|
||||
"password": "senha",
|
||||
}
|
||||
auth = self.client.post("/trainer/authenticate", json=login, follow_redirects=True)
|
||||
self.assert_200(auth)
|
||||
token = auth.get_json()["token"]
|
||||
response = self.client.delete("/trainer/200/pokemon/1", headers={"Authorization":token}, follow_redirects=True)
|
||||
self.assert_403(response)
|
||||
|
||||
def test_delete_pokemon_no_auth(self):
|
||||
login = {
|
||||
"email": "joaooliveira@hotmail.com",
|
||||
"password": "senha",
|
||||
}
|
||||
auth = self.client.post("/trainer/authenticate", json=login, follow_redirects=True)
|
||||
self.assert_200(auth)
|
||||
token = auth.get_json()["token"]
|
||||
data = {
|
||||
"name": "Dummier",
|
||||
"level": 2,
|
||||
"pokemon_id": 12
|
||||
}
|
||||
response = self.client.post("/trainer/2/pokemon", json=data, headers={"Authorization":token}, follow_redirects=True)
|
||||
self.assert_status(response, 201)
|
||||
response = self.client.delete("/trainer/2/pokemon/{}".format(response.get_json()["id"]), follow_redirects=True)
|
||||
self.assert_401(response)
|
||||
|
||||
def test_delete_pokemon_not_found(self):
|
||||
login = {
|
||||
"email": "joaooliveira@hotmail.com",
|
||||
"password": "senha",
|
||||
}
|
||||
auth = self.client.post("/trainer/authenticate", json=login, follow_redirects=True)
|
||||
self.assert_200(auth)
|
||||
token = auth.get_json()["token"]
|
||||
response = self.client.delete("/trainer/2/pokemon/1000", headers={"Authorization":token}, follow_redirects=True)
|
||||
self.assert_404(response)
|
||||
|
||||
def test_delete_pokemon(self):
|
||||
login = {
|
||||
"email": "joaooliveira@hotmail.com",
|
||||
"password": "senha",
|
||||
}
|
||||
auth = self.client.post("/trainer/authenticate", json=login, follow_redirects=True)
|
||||
self.assert_200(auth)
|
||||
token = auth.get_json()["token"]
|
||||
data = {
|
||||
"name": "Dummier",
|
||||
"level": 2,
|
||||
"pokemon_id": 12
|
||||
}
|
||||
response = self.client.post("/trainer/2/pokemon", json=data, headers={"Authorization":token}, follow_redirects=True)
|
||||
self.assert_status(response, 201)
|
||||
response = self.client.delete("/trainer/2/pokemon/{}".format(response.get_json()["id"]), headers={"Authorization":token}, follow_redirects=True)
|
||||
self.assert_200(response)
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
|
@ -21,4 +21,4 @@ def ForbiddenError(message):
|
|||
return error(5, "ForbiddenError", message, http_code=403)
|
||||
|
||||
def FetchError(message):
|
||||
return error(6, "FetchError", message, http_code=500)
|
||||
return error(6, "FetchError", message, http_code=404)
|
||||
|
|
|
@ -12,16 +12,17 @@ class HTTPError(Exception):
|
|||
self.message = message
|
||||
|
||||
class NotFound(Exception):
|
||||
pass
|
||||
def __init__(self, message):
|
||||
self.message = message
|
||||
|
||||
def get_or_not_found(callback):
|
||||
try:
|
||||
resource = callback()
|
||||
if resource is None:
|
||||
raise NotFound()
|
||||
raise NotFound("Resource not found")
|
||||
return resource
|
||||
except:
|
||||
raise NotFound()
|
||||
raise NotFound("Resource not found")
|
||||
|
||||
def get_trainer_fail(id):
|
||||
return get_or_not_found(lambda : Trainer.query.get(id))
|
||||
|
@ -50,21 +51,25 @@ def token_required(f):
|
|||
return f(trainer, *args, **kwargs)
|
||||
return decorated
|
||||
|
||||
# helpers internos
|
||||
def cant_fetch_error(pokemon):
|
||||
raise NotFound("Could not fetch data for pokemon with id {}".format(pokemon.pokemon_id))
|
||||
|
||||
# seguintes funções puxam informações da pokeapi
|
||||
def set_pokemon_data(pokemon):
|
||||
try:
|
||||
response = requests.get("https://pokeapi.co/api/v2/pokemon/{}".format(pokemon.pokemon_id))
|
||||
if response.status_code != 200:
|
||||
raise HTTPError("Could not fetch pokemon with id {}".format(pokemon.pokemon_id))
|
||||
cant_fetch_error(pokemon)
|
||||
pokemon.pokemon_data = json.loads(response.text)
|
||||
except:
|
||||
raise HTTPError("Could not fetch pokemon with id {}".format(pokemon.pokemon_id))
|
||||
cant_fetch_error(pokemon)
|
||||
|
||||
async def async_set_pokemon_data(session, pokemon):
|
||||
try:
|
||||
response = await session.get("https://pokeapi.co/api/v2/pokemon/{}".format(pokemon.pokemon_id))
|
||||
if response.status != 200:
|
||||
raise HTTPError("Could not fetch pokemon with id {}".format(pokemon.pokemon_id))
|
||||
cant_fetch_error(pokemon)
|
||||
pokemon.pokemon_data = json.loads(await response.text())
|
||||
except:
|
||||
raise HTTPError("Could not fetch pokemon with id {}".format(pokemon.pokemon_id))
|
||||
cant_fetch_error(pokemon)
|
||||
|
|
|
@ -4,7 +4,7 @@ from .parse_args import parse_limit, parse_offset, ParsingException, parse_json_
|
|||
from .errors import ParsingError, FetchError, ConflictingResources
|
||||
from .helper import *
|
||||
from aiohttp import ClientSession
|
||||
from asyncio import create_task, gather
|
||||
import asyncio
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
|
||||
def post_pokemon_owned(trainer_id):
|
||||
|
@ -30,7 +30,7 @@ def post_pokemon_owned(trainer_id):
|
|||
|
||||
db.session.add(pokemon)
|
||||
db.session.commit()
|
||||
except HTTPError as e:
|
||||
except NotFound as e:
|
||||
return FetchError(e.message)
|
||||
except IntegrityError:
|
||||
return ConflictingResources("Trainer already has another pokemon with the same name")
|
||||
|
@ -54,13 +54,21 @@ async def get_pokemons_owned(trainer_id):
|
|||
async with ClientSession() as session:
|
||||
tasks = []
|
||||
for pokemon in pokemons:
|
||||
task = create_task(async_set_pokemon_data(session, pokemon))
|
||||
task = asyncio.create_task(async_set_pokemon_data(session, pokemon))
|
||||
tasks.append(task)
|
||||
try:
|
||||
await gather(*tasks)
|
||||
except HTTPError as e:
|
||||
await asyncio.gather(*tasks)
|
||||
except NotFound as e:
|
||||
for task in tasks:
|
||||
task.cancel()
|
||||
return FetchError(e.message)
|
||||
|
||||
# workaround pra bug do aiohttp, que pode gerar avisos de conexões abertas
|
||||
# ver documentação: https://docs.aiohttp.org/en/stable/client_advanced.html#graceful-shutdown
|
||||
# será arrumado na versão 4.0.0, que ainda não saiu
|
||||
# issue no github: https://github.com/aio-libs/aiohttp/issues/1925
|
||||
await asyncio.sleep(0.250)
|
||||
|
||||
return pokemon_owned_schemas.dumps(pokemons)
|
||||
|
||||
def get_pokemon_owned(trainer_id, pokemon_id):
|
||||
|
@ -73,6 +81,8 @@ def get_pokemon_owned(trainer_id, pokemon_id):
|
|||
return "", 404
|
||||
except HTTPError as e:
|
||||
return FetchError(e.message)
|
||||
except NotFound as e:
|
||||
return ParsingError(e.message)
|
||||
|
||||
def delete_pokemon_owned(trainer, pokemon_id):
|
||||
try:
|
||||
|
|
|
@ -11,7 +11,10 @@ import jwt
|
|||
|
||||
def get_trainer(id):
|
||||
try:
|
||||
return trainer_schema.dump(Trainer.query.get(id))
|
||||
trainer = Trainer.query.get(id)
|
||||
if trainer is None:
|
||||
return ("", 404)
|
||||
return trainer_schema.dump(trainer)
|
||||
except:
|
||||
return ("", 404)
|
||||
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
Flask_SQLAlchemy==2.5.1
|
||||
requests==2.26.0
|
||||
aiohttp==3.7.4.post0
|
||||
Werkzeug==2.0.2
|
||||
Flask==2.0.2
|
||||
SQLAlchemy==1.4.25
|
||||
Flask_Migrate==3.1.0
|
||||
Flask_SQLAlchemy==2.5.1
|
||||
flask_marshmallow==0.14.0
|
||||
Flask_Migrate==3.1.0
|
||||
Flask==2.0.2
|
||||
requests==2.26.0
|
||||
Flask_Testing==0.8.1
|
||||
SQLAlchemy==1.4.25
|
||||
aiohttp==3.7.4.post0
|
||||
PyJWT==2.3.0
|
||||
|
|
Loading…
Reference in New Issue