mirror of
https://github.com/almet/copanier.git
synced 2025-04-28 19:42:37 +02:00
Basic auth management
This commit is contained in:
parent
8bb0dce145
commit
ab1710d1e4
21 changed files with 228 additions and 56 deletions
102
kaba/__init__.py
102
kaba/__init__.py
|
@ -1,7 +1,9 @@
|
|||
import csv
|
||||
from datetime import timedelta
|
||||
from pathlib import Path
|
||||
from time import perf_counter
|
||||
|
||||
import jwt
|
||||
import ujson as json
|
||||
import hupper
|
||||
import minicli
|
||||
|
@ -9,7 +11,7 @@ from jinja2 import Environment, PackageLoader, select_autoescape
|
|||
from roll import Roll, Response
|
||||
from roll.extensions import cors, options, traceback, simple_server, static
|
||||
|
||||
from . import config, reports
|
||||
from . import config, reports, session, utils, emails
|
||||
from .models import Delivery, Order, Person, Product, ProductOrder
|
||||
|
||||
|
||||
|
@ -59,6 +61,42 @@ cors(app, methods="*", headers="*")
|
|||
options(app)
|
||||
|
||||
|
||||
def auth_required(view):
|
||||
async def redirect(request, response, *a, **k):
|
||||
# FIXME do not return a view when Roll allows it.
|
||||
response.redirect = f"/sésame?next={request.path}"
|
||||
|
||||
def wrapper(request, response, *args, **kwargs):
|
||||
token = request.cookies.get("token")
|
||||
email = None
|
||||
if token:
|
||||
decoded = read_token(token)
|
||||
email = decoded.get("sub")
|
||||
if not email:
|
||||
return redirect(request, response, *args, **kwargs)
|
||||
user = Person(email=email)
|
||||
request["user"] = user
|
||||
session.user.set(user)
|
||||
return view(request, response, *args, **kwargs)
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
def create_token(email):
|
||||
return jwt.encode(
|
||||
{"sub": str(email), "exp": utils.utcnow() + timedelta(days=7)},
|
||||
config.SECRET,
|
||||
config.JWT_ALGORITHM,
|
||||
)
|
||||
|
||||
|
||||
def read_token(token):
|
||||
try:
|
||||
return jwt.decode(token, config.SECRET, algorithms=[config.JWT_ALGORITHM])
|
||||
except (jwt.DecodeError, jwt.ExpiredSignatureError):
|
||||
return {}
|
||||
|
||||
|
||||
@app.listen("request")
|
||||
async def attach_request(request, response):
|
||||
response.request = request
|
||||
|
@ -69,17 +107,49 @@ async def on_startup():
|
|||
configure()
|
||||
|
||||
|
||||
@app.route("/sésame", methods=["GET"])
|
||||
async def sesame(request, response):
|
||||
response.html("sesame.html")
|
||||
|
||||
|
||||
@app.route("/sésame", methods=["POST"])
|
||||
async def send_sesame(request, response):
|
||||
email = request.form.get("email")
|
||||
token = create_token(email)
|
||||
emails.send(
|
||||
email,
|
||||
"Sésame Panio",
|
||||
emails.ACCESS_GRANTED.format(hostname=request.host, token=token.decode()),
|
||||
)
|
||||
response.message(f"Un sésame vous a été envoyé à l'adresse '{email}'")
|
||||
response.redirect = "/"
|
||||
|
||||
|
||||
@app.route("/sésame/{token}", methods=["GET"])
|
||||
async def set_sesame(request, response, token):
|
||||
decoded = read_token(token)
|
||||
if not decoded:
|
||||
response.message("Sésame invalide :(", status="error")
|
||||
else:
|
||||
response.message("Yay! Le sésame a fonctionné. Bienvenue à bord! :)")
|
||||
response.cookies.set(name="token", value=token)
|
||||
response.redirect = "/"
|
||||
|
||||
|
||||
@app.route("/", methods=["GET"])
|
||||
@auth_required
|
||||
async def home(request, response):
|
||||
response.html("home.html", deliveries=Delivery.all())
|
||||
|
||||
|
||||
@app.route("/livraison/new", methods=["GET"])
|
||||
@auth_required
|
||||
async def new_delivery(request, response):
|
||||
response.html("edit_delivery.html", delivery={})
|
||||
|
||||
|
||||
@app.route("/livraison/new", methods=["POST"])
|
||||
@auth_required
|
||||
async def create_delivery(request, response):
|
||||
form = request.form
|
||||
data = {}
|
||||
|
@ -93,6 +163,7 @@ async def create_delivery(request, response):
|
|||
|
||||
|
||||
@app.route("/livraison/{id}/importer/produits", methods=["POST"])
|
||||
@auth_required
|
||||
async def import_products(request, response, id):
|
||||
delivery = Delivery.load(id)
|
||||
delivery.products = []
|
||||
|
@ -107,12 +178,14 @@ async def import_products(request, response, id):
|
|||
|
||||
|
||||
@app.route("/livraison/{id}/edit", methods=["GET"])
|
||||
@auth_required
|
||||
async def edit_delivery(request, response, id):
|
||||
delivery = Delivery.load(id)
|
||||
response.html("edit_delivery.html", {"delivery": delivery})
|
||||
|
||||
|
||||
@app.route("/livraison/{id}/edit", methods=["POST"])
|
||||
@auth_required
|
||||
async def post_delivery(request, response, id):
|
||||
delivery = Delivery.load(id)
|
||||
form = request.form
|
||||
|
@ -125,30 +198,40 @@ async def post_delivery(request, response, id):
|
|||
|
||||
|
||||
@app.route("/livraison/{id}", methods=["GET"])
|
||||
@auth_required
|
||||
async def view_delivery(request, response, id):
|
||||
delivery = Delivery.load(id)
|
||||
response.html("delivery.html", {"delivery": delivery})
|
||||
|
||||
|
||||
@app.route("/livraison/{id}/commander", methods=["GET"])
|
||||
@auth_required
|
||||
async def order_form(request, response, id):
|
||||
delivery = Delivery.load(id)
|
||||
email = request.query.get("email")
|
||||
email = request.query.get("email", None)
|
||||
if not email:
|
||||
user = session.user.get(None)
|
||||
if user:
|
||||
email = user.email
|
||||
if email:
|
||||
order = delivery.orders.get(email) or Order()
|
||||
response.html(
|
||||
"place_order.html", {"delivery": delivery, "person": email, "order": order}
|
||||
)
|
||||
else:
|
||||
response.message("Impossible de comprendre pour qui passer commande…", "error")
|
||||
response.redirect = request.path
|
||||
|
||||
|
||||
@app.route("/livraison/{id}/émargement", methods=["GET"])
|
||||
async def signing_list(request, response, id):
|
||||
@auth_required
|
||||
async def signing_sheet(request, response, id):
|
||||
delivery = Delivery.load(id)
|
||||
response.html(
|
||||
"signing_list.html", {"delivery": delivery}
|
||||
)
|
||||
response.html("signing_sheet.html", {"delivery": delivery})
|
||||
|
||||
|
||||
@app.route("/livraison/{id}/commander", methods=["POST"])
|
||||
@auth_required
|
||||
async def place_order(request, response, id):
|
||||
delivery = Delivery.load(id)
|
||||
email = request.query.get("email")
|
||||
|
@ -167,6 +250,7 @@ async def place_order(request, response, id):
|
|||
|
||||
|
||||
@app.route("/livraison/{id}/importer/commande", methods=["POST"])
|
||||
@auth_required
|
||||
async def import_commande(request, response, id):
|
||||
email = request.form.get("email")
|
||||
order = Order()
|
||||
|
@ -185,20 +269,22 @@ async def import_commande(request, response, id):
|
|||
|
||||
|
||||
@app.route("/livraison/{id}/rapport.xlsx", methods=["GET"])
|
||||
@auth_required
|
||||
async def xls_report(request, response, id):
|
||||
delivery = Delivery.load(id)
|
||||
response.body = reports.summary(delivery)
|
||||
mimetype = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
||||
response.headers["Content-Disposition"] = f'attachment; filename="export.xlsx"'
|
||||
response.headers["Content-Disposition"] = f'attachment; filename="epinamap.xlsx"'
|
||||
response.headers["Content-Type"] = f"{mimetype}; charset=utf-8"
|
||||
|
||||
|
||||
@app.route("/livraison/{id}/rapport-complet.xlsx", methods=["GET"])
|
||||
@auth_required
|
||||
async def xls_full_report(request, response, id):
|
||||
delivery = Delivery.load(id)
|
||||
response.body = reports.full(delivery)
|
||||
mimetype = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
||||
response.headers["Content-Disposition"] = f'attachment; filename="export.xlsx"'
|
||||
response.headers["Content-Disposition"] = f'attachment; filename="epinamap.xlsx"'
|
||||
response.headers["Content-Type"] = f"{mimetype}; charset=utf-8"
|
||||
|
||||
|
||||
|
|
|
@ -5,10 +5,11 @@ getenv = os.environ.get
|
|||
|
||||
DATA_ROOT = Path(__file__).parent.parent / "db"
|
||||
SECRET = "sikretfordevonly"
|
||||
# JWT_ALGORITHM = "HS256"
|
||||
JWT_ALGORITHM = "HS256"
|
||||
SEND_EMAILS = False
|
||||
SMTP_HOST = "smtp.gmail.com"
|
||||
SMTP_HOST = "mail.gandi.net"
|
||||
SMTP_PASSWORD = ""
|
||||
SMTP_LOGIN = ""
|
||||
FROM_EMAIL = "contact@epinamap.org"
|
||||
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ Voici le sésame:
|
|||
|
||||
https://{hostname}/sésame/{token}
|
||||
|
||||
Les gentils gens d'Épinamap
|
||||
Les gentils copains d'Épinamap
|
||||
"""
|
||||
|
||||
|
||||
|
@ -24,7 +24,7 @@ def send(to, subject, body):
|
|||
return print("Sending email", str(msg))
|
||||
try:
|
||||
server = smtplib.SMTP_SSL(config.SMTP_HOST)
|
||||
server.login(config.FROM_EMAIL, config.SMTP_PASSWORD)
|
||||
server.login(config.SMTP_LOGIN, config.SMTP_PASSWORD)
|
||||
server.send_message(msg)
|
||||
except smtplib.SMTPException:
|
||||
raise RuntimeError
|
||||
|
|
|
@ -8,7 +8,7 @@ from typing import List, Dict
|
|||
|
||||
import yaml
|
||||
|
||||
from . import config, utils
|
||||
from . import config
|
||||
|
||||
|
||||
class DoesNotExist(ValueError):
|
||||
|
|
|
@ -5,7 +5,7 @@ from openpyxl.writer.excel import save_virtual_workbook
|
|||
def summary(delivery):
|
||||
wb = Workbook()
|
||||
ws = wb.active
|
||||
ws.title = f"Commande Epinamap - {delivery.producer} - {delivery.when.date()}"
|
||||
ws.title = f"{delivery.producer} {delivery.when.date()}"
|
||||
ws.append(["ref", "produit", "prix", "unités", "total"])
|
||||
for product in delivery.products:
|
||||
wanted = delivery.product_wanted(product)
|
||||
|
@ -25,7 +25,7 @@ def summary(delivery):
|
|||
def full(delivery):
|
||||
wb = Workbook()
|
||||
ws = wb.active
|
||||
ws.title = f"Epinamap - {delivery.producer} - {delivery.when.date()}"
|
||||
ws.title = f"{delivery.producer} {delivery.when.date()}"
|
||||
headers = ["ref", "produit", "prix"] + [e for e in delivery.orders] + ["total"]
|
||||
ws.append(headers)
|
||||
for product in delivery.products:
|
||||
|
|
3
kaba/session.py
Normal file
3
kaba/session.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
import contextvars
|
||||
|
||||
user = contextvars.ContextVar("user")
|
|
@ -157,6 +157,10 @@ nav {
|
|||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.logged-in {
|
||||
text-decoration: underline;
|
||||
font-variant: small-caps;
|
||||
}
|
||||
main {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
@ -164,7 +168,7 @@ main {
|
|||
button,
|
||||
a.button,
|
||||
input[type=submit] {
|
||||
display: flex;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: auto;
|
||||
|
@ -377,6 +381,9 @@ hr {
|
|||
.notification.success {
|
||||
background-color: #0f8796;
|
||||
}
|
||||
.notification.error {
|
||||
background-color: #e10055;
|
||||
}
|
||||
.notification i {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
|
|
@ -20,6 +20,9 @@
|
|||
<h1><a href="/">Panio</a> <small>Les paniers piano d'Épinamap</small></h1>
|
||||
<nav>
|
||||
<a href="/livraison/new">Nouvelle livraison</a>
|
||||
{% if request["user"] %}
|
||||
⚫ {{ request["user"].email }}</span>
|
||||
{% endif %}
|
||||
</nav>
|
||||
</section>
|
||||
</header>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block body %}
|
||||
<h3>{{ delivery.producer }}</h3>
|
||||
<h3>{{ delivery.producer }} <a class="button" href="/livraison/{{ delivery.id }}/commander">Ma commande</a></h3>
|
||||
{% include "includes/delivery_head.html" %}
|
||||
<table class="delivery">
|
||||
<tbody>
|
||||
|
@ -52,17 +52,14 @@
|
|||
<a href="/livraison/{{ delivery.id }}/émargement" target="_blank"><i class="icon-document"></i> Liste d'émargement</a>
|
||||
</li>
|
||||
<li>
|
||||
<label for="import-command" class="toggle-label"><i class="icon-paperclip"></i> Importer une commande</label>
|
||||
<input type="checkbox" id="import-command" class="toggle">
|
||||
<div class="toggle-container">
|
||||
<label for="import-command" class="toggle-label">Fermer</label>
|
||||
<p>Colonnes: ref*, wanted*</p>
|
||||
<form action="/livraison/{{ delivery.id }}/importer/commande" method="post" enctype="multipart/form-data">
|
||||
<input type="file" name="data">
|
||||
<input type="email" name="email" placeholder="email">
|
||||
<input type="submit" name="Importer une commande">
|
||||
</form>
|
||||
</div>
|
||||
{% with unique_id="import-command" %}
|
||||
{% include "includes/modal_import_command.html" %}
|
||||
{% endwith %}
|
||||
</li>
|
||||
<li>
|
||||
{% with unique_id="add-command" %}
|
||||
{% include "includes/modal_add_command.html" %}
|
||||
{% endwith %}
|
||||
</li>
|
||||
</ul>
|
||||
{% endblock body %}
|
||||
|
|
|
@ -6,24 +6,24 @@
|
|||
<form method="post">
|
||||
<label>
|
||||
<h5>Producteur</h5>
|
||||
<input type="text" name="producer" value="{{ delivery.producer or '' }}">
|
||||
<input type="text" name="producer" value="{{ delivery.producer or '' }}" required>
|
||||
</label>
|
||||
<label>
|
||||
<h5>Description</h5>
|
||||
<input type="text" name="description" value="{{ delivery.description or '' }}">
|
||||
<h5>Description des produits</h5>
|
||||
<input type="text" name="description" value="{{ delivery.description or '' }}" required>
|
||||
</label>
|
||||
<label>
|
||||
<h5>Lieu</h5>
|
||||
<input type="text" name="where" value="{{ delivery.where or '' }}">
|
||||
<input type="text" name="where" value="{{ delivery.where or '' }}" required>
|
||||
</label>
|
||||
<label>
|
||||
<h5>Date de livraison</h5>
|
||||
<input type="date" name="when" value="{{ delivery.when.date() if delivery.when else '' }}">
|
||||
<input type="time" name="when_time" value="{{ delivery.when.time() if delivery.when else '' }}">
|
||||
<input type="date" name="when" value="{{ delivery.when.date() if delivery.when else '' }}" required>
|
||||
<input type="time" name="when_time" value="{{ delivery.when.time() if delivery.when else '' }}" required>
|
||||
</label>
|
||||
<label>
|
||||
<h5>Date de limite de commande</h5>
|
||||
<input type="date" name="order_before" value="{{ delivery.order_before.date() if delivery.order_before else '' }}">
|
||||
<input type="date" name="order_before" value="{{ delivery.order_before.date() if delivery.order_before else '' }}" required>
|
||||
</label>
|
||||
<div>
|
||||
<input type="submit" name="submit" value="Valider">
|
||||
|
|
|
@ -6,11 +6,6 @@
|
|||
<li>
|
||||
<h3><a href="/livraison/{{ delivery.id }}"><i class="icon-hotairballoon"></i> {{ delivery.producer }}</a></h3>
|
||||
{% include "includes/delivery_head.html" %}
|
||||
<!--form action="/livraison/{{ delivery.id }}/commander">
|
||||
<label for="email">Commander</label>
|
||||
<input type="email" name="email" placeholder="Mon courriel">
|
||||
<input type="submit" value="Commander">
|
||||
</form-->
|
||||
</li>
|
||||
<hr>
|
||||
{% endfor %}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<ul class="delivery-head">
|
||||
<li><i class="icon-basket"></i> {{ delivery.description }}</li>
|
||||
<li><i class="icon-basket"></i> <strong>Produits</strong> {{ delivery.description }}</li>
|
||||
<li><i class="icon-map-pin"></i> <strong>Lieu</strong> {{ delivery.where }}</li>
|
||||
<li><i class="icon-clock"></i> <strong>Date de livraison</strong> <time datetime="{{ delivery.when }}">{{ delivery.when }}</time></li>
|
||||
<li><i class="icon-hourglass"></i> {% if delivery.is_open %}<strong>Date limite de commande</strong> <time datetime="{{ delivery.order_before.date() }}">{{ delivery.order_before.date() }}</time>{% else %}<strong>Fermée</strong>{% endif %}</li>
|
||||
|
|
8
kaba/templates/includes/modal.html
Normal file
8
kaba/templates/includes/modal.html
Normal file
|
@ -0,0 +1,8 @@
|
|||
<label for="modal-{{ unique_id }}" class="toggle-label">{% block modal_label %}{% endblock %}</label>
|
||||
<input type="checkbox" id="modal-{{ unique_id }}" class="toggle">
|
||||
<label for="modal-{{ unique_id }}" class="toggle-background"></label>
|
||||
<div class="toggle-container">
|
||||
{% block modal_body %}
|
||||
{% endblock modal_body %}
|
||||
<label for="modal-{{ unique_id }}" class="toggle-label">Fermer</label>
|
||||
</div>
|
10
kaba/templates/includes/modal_add_command.html
Normal file
10
kaba/templates/includes/modal_add_command.html
Normal file
|
@ -0,0 +1,10 @@
|
|||
{% extends "includes/modal.html" %}
|
||||
|
||||
{% block modal_label %}<i class="icon-hazardous"></i> Ajouter une commande{% endblock modal_label %}
|
||||
{% block modal_body %}
|
||||
<form action="/livraison/{{ delivery.id }}/commander">
|
||||
<h4>Ajouter une commande pour quelqu'un d'autre</h4>
|
||||
<input type="email" name="email" placeholder="Courriel" required>
|
||||
<input type="submit" value="Commander">
|
||||
</form>
|
||||
{% endblock modal_body %}
|
12
kaba/templates/includes/modal_import_command.html
Normal file
12
kaba/templates/includes/modal_import_command.html
Normal file
|
@ -0,0 +1,12 @@
|
|||
{% extends "includes/modal.html" %}
|
||||
|
||||
{% block modal_label %}<i class="icon-paperclip"></i> Importer une commande{% endblock modal_label %}
|
||||
{% block modal_body %}
|
||||
<h4>Importer une commande</h4>
|
||||
<p>Colonnes: ref*, wanted*</p>
|
||||
<form action="/livraison/{{ delivery.id }}/importer/commande" method="post" enctype="multipart/form-data">
|
||||
<input type="file" name="data">
|
||||
<input type="email" name="email" placeholder="email">
|
||||
<input type="submit" name="Importer">
|
||||
</form>
|
||||
{% endblock modal_body %}
|
9
kaba/templates/includes/modal_product.html
Normal file
9
kaba/templates/includes/modal_product.html
Normal file
|
@ -0,0 +1,9 @@
|
|||
{% extends "includes/modal.html" %}
|
||||
|
||||
{% block modal_label %}(Détails){% endblock modal_label %}
|
||||
{% block modal_body %}
|
||||
<p>{{ product.description }}</p>
|
||||
<p>{% if product.img %}
|
||||
<img src="{{ product.img }}">
|
||||
{% endif %}</p>
|
||||
{% endblock modal_body %}
|
|
@ -11,16 +11,9 @@
|
|||
<tr>
|
||||
<th class="product">{{ product.name }}
|
||||
{% if product.description or product.img %}
|
||||
<label for="toggleControl{{ loop.index }}" class="toggle-label">(Détails)</label>
|
||||
<input type="checkbox" id="toggleControl{{ loop.index }}" class="toggle">
|
||||
<label for="toggleControl{{ loop.index }}" class="toggle-background"></label>
|
||||
<div class="toggle-container">
|
||||
<p>{{ product.description }}</p>
|
||||
<p>{% if product.img %}
|
||||
<img src="{{ product.img }}">
|
||||
{% endif %}</p>
|
||||
<label for="toggleControl{{ loop.index }}" class="toggle-label">Fermer</label>
|
||||
</div>
|
||||
{% with unique_id=loop.index %}
|
||||
{% include "includes/modal_product.html" %}
|
||||
{% endwith %}
|
||||
{% endif %}</p>
|
||||
</th>
|
||||
<td>{{ product.price }} €</td><td class="with-input"><input type="number" name="{{ product.ref }}" value="{{ order.get_quantity(product) }}"></td></tr>
|
||||
|
|
7
kaba/templates/sesame.html
Normal file
7
kaba/templates/sesame.html
Normal file
|
@ -0,0 +1,7 @@
|
|||
{% extends "base.html" %}
|
||||
{% block body %}
|
||||
<form method="post">
|
||||
<input type="email" name="email" placeholder="Mon courriel" required>
|
||||
<input type="submit" value="Envoyez-moi un sésame">
|
||||
</form>
|
||||
{% endblock body %}
|
|
@ -3,9 +3,11 @@ from datetime import datetime, timedelta
|
|||
|
||||
import pytest
|
||||
from roll.extensions import traceback
|
||||
from roll.testing import Client as BaseClient
|
||||
|
||||
from kaba import app as kaba_app
|
||||
from kaba import config as kconfig
|
||||
from kaba import create_token
|
||||
from kaba.models import Delivery, Person
|
||||
|
||||
|
||||
|
@ -20,6 +22,39 @@ def pytest_runtest_setup(item):
|
|||
path.unlink()
|
||||
|
||||
|
||||
class Client(BaseClient):
|
||||
headers = {}
|
||||
|
||||
async def request(
|
||||
self, path, method="GET", body=b"", headers=None, content_type=None
|
||||
):
|
||||
# TODO move this to Roll upstream?
|
||||
headers = headers or {}
|
||||
for key, value in self.headers.items():
|
||||
headers.setdefault(key, value)
|
||||
return await super().request(path, method, body, headers, content_type)
|
||||
|
||||
def login(self, email="foo@bar.org"):
|
||||
token = create_token(email)
|
||||
self.headers["Cookie"] = f"token={token.decode()}"
|
||||
|
||||
def logout(self):
|
||||
try:
|
||||
del self.headers["Cookie"]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def client(app, event_loop):
|
||||
app.loop = event_loop
|
||||
app.loop.run_until_complete(app.startup())
|
||||
client = Client(app)
|
||||
client.login()
|
||||
yield client
|
||||
app.loop.run_until_complete(app.shutdown())
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def app(): # Requested by Roll testing utilities.
|
||||
traceback(kaba_app)
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import pytest
|
||||
|
||||
|
||||
pytestmark = pytest.mark.asyncio
|
||||
|
||||
|
||||
|
@ -14,3 +13,10 @@ async def test_home_should_list_active_delivery(client, delivery):
|
|||
resp = await client.get('/')
|
||||
assert resp.status == 200
|
||||
assert delivery.producer in resp.body.decode()
|
||||
|
||||
|
||||
async def test_home_should_redirect_to_login_if_not_logged(client):
|
||||
client.logout()
|
||||
resp = await client.get('/')
|
||||
assert resp.status == 302
|
||||
assert resp.headers["Location"] == "/sésame?next=/"
|
||||
|
|
Loading…
Reference in a new issue