more tests

This commit is contained in:
Tom Irgang 2023-04-28 11:54:13 +02:00
parent beb795dfb1
commit 4dbcee4417
19 changed files with 343 additions and 47 deletions

View file

@ -17,7 +17,8 @@
"KeycloakOIDCAB", "KeycloakOIDCAB",
"Keycloak", "Keycloak",
"tabindex", "tabindex",
"onetimelogin" "onetimelogin",
"Ammeldelink"
], ],
"ignoreWords": [], "ignoreWords": [],
"import": [] "import": []

View file

@ -88,7 +88,6 @@ TEMPLATES = [
WSGI_APPLICATION = "rc_hip.wsgi.application" WSGI_APPLICATION = "rc_hip.wsgi.application"
# Database # Database
# https://docs.djangoproject.com/en/4.2/ref/settings/#databases # https://docs.djangoproject.com/en/4.2/ref/settings/#databases

View file

@ -7,9 +7,9 @@ from hashlib import sha256
from django.contrib.staticfiles.testing import StaticLiveServerTestCase from django.contrib.staticfiles.testing import StaticLiveServerTestCase
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from selenium.webdriver.common.by import By from selenium.webdriver.common.by import By
# from selenium.webdriver.chrome.webdriver import WebDriver from selenium.webdriver.chrome.webdriver import WebDriver
from selenium.webdriver.firefox.webdriver import WebDriver from selenium.webdriver import ActionChains, Keys
from .models import CustomUser, Guest, OneTimeLogin from .models import CustomUser, OneTimeLogin
class WorkflowTests(StaticLiveServerTestCase): class WorkflowTests(StaticLiveServerTestCase):
@ -22,7 +22,8 @@ class WorkflowTests(StaticLiveServerTestCase):
def setUpClass(cls): def setUpClass(cls):
super().setUpClass() super().setUpClass()
cls.selenium = WebDriver() cls.selenium = WebDriver()
cls.selenium.implicitly_wait(10) cls.selenium.implicitly_wait(50)
cls.selenium.maximize_window()
@classmethod @classmethod
def tearDownClass(cls): def tearDownClass(cls):
@ -32,6 +33,8 @@ class WorkflowTests(StaticLiveServerTestCase):
def setUp(self): def setUp(self):
super().setUp() super().setUp()
self.action_chains = ActionChains(self.selenium)
self.password = "ATestPassword" self.password = "ATestPassword"
user = CustomUser.objects.get(pk=3) user = CustomUser.objects.get(pk=3)
user.set_password(self.password) user.set_password(self.password)
@ -62,7 +65,7 @@ class WorkflowTests(StaticLiveServerTestCase):
self.selenium.get(f"{self.live_server_url}/") self.selenium.get(f"{self.live_server_url}/")
# Click on a register link # Click on a register link
self.selenium.find_element(By.CLASS_NAME, "register_link").click() self.selenium.find_element(By.CLASS_NAME, "register_link").click()
time.sleep(1) time.sleep(2)
# Enter new device # Enter new device
# Enter guest mail address # Enter guest mail address
mail = self.selenium.find_element(By.NAME, "mail") mail = self.selenium.find_element(By.NAME, "mail")
@ -76,6 +79,10 @@ class WorkflowTests(StaticLiveServerTestCase):
# Enter a error description # Enter a error description
error = self.selenium.find_element(By.NAME, "error") error = self.selenium.find_element(By.NAME, "error")
error.send_keys("A description of the issue\nof the device.") error.send_keys("A description of the issue\nof the device.")
# Scroll to the bottom of the page
self.selenium.find_element(
By.TAG_NAME, "body").send_keys(Keys.CONTROL, Keys.END)
time.sleep(1)
# Skip device picture and device type plate # Skip device picture and device type plate
# No follow up repair # No follow up repair
# Accept repair conditions # Accept repair conditions
@ -121,6 +128,10 @@ class WorkflowTests(StaticLiveServerTestCase):
# Enter a error description # Enter a error description
error = self.selenium.find_element(By.NAME, "error") error = self.selenium.find_element(By.NAME, "error")
error.send_keys("Another issue description.") error.send_keys("Another issue description.")
# Scroll to the bottom of the page
self.selenium.find_element(
By.TAG_NAME, "body").send_keys(Keys.CONTROL, Keys.END)
time.sleep(1)
# Add device picture # Add device picture
test_script_folder = Path(__file__).resolve().parent test_script_folder = Path(__file__).resolve().parent
fixtures_folder = os.path.join(test_script_folder, "fixtures") fixtures_folder = os.path.join(test_script_folder, "fixtures")

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

File diff suppressed because one or more lines are too long

View file

@ -1,6 +1,6 @@
{% load static %}
<!DOCTYPE html> <!DOCTYPE html>
<html lang="de"> {% load static %}
<html lang="en">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="description" <meta name="description"
@ -10,18 +10,65 @@
<meta name="viewport" <meta name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no" /> content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<link rel="stylesheet" href="{% static 'repapp/css/bootstrap.min.css' %}" /> <link rel="stylesheet" href="{% static 'repapp/css/bootstrap.min.css' %}" />
<script src="{% static 'repapp/css/bootstrap.min.js' %}"></script> <title>RepApp -
<title>RepApp
{% block title %} {% block title %}
{% endblock title %} {% endblock title %}
</title> </title>
<link rel="stylesheet" href="{% static 'repapp/css/bootstrap.min.css' %}" />
{% block head %} {% block head %}
{% endblock head %} {% endblock head %}
</head> </head>
<body> <body>
<div class="container">
<header class="d-flex flex-wrap justify-content-center py-3 mb-4 border-bottom">
<a href="{% url 'index' %}"
class="d-flex align-items-center mb-3 mb-md-0 me-md-auto text-dark text-decoration-none">
<img src="{% static 'repapp/images/logo.jpg' %}"
height="50px"
width="168px"
alt="Repair-Café Logo">
</a>
<ul class="nav nav-pills">
<li class="nav-item">
<a href="https://www.repaircafe-hilpoltstein.de/" class="nav-link">Repair-Café</a>
</li>
<li class="nav-item">
<a href="{% url 'index' %}" class="nav-link">Anmeldung</a>
</li>
{% if user.is_authenticated %}
<li class="nav-item">
<a href="{% url 'logout' %}" class="nav-link">Abmelden</a>
</li>
{% else %}
<li class="nav-item">
<a href="{% url 'login' %}" class="nav-link">Gast Anmeldung</a>
</li>
<li class="nav-item">
<a href="{% url 'member_login' %}" class="nav-link">Mitglieder Anmeldung</a>
</li>
{% endif %}
</ul>
</header>
</div>
<div class="position-relative overflow-hidden p-3 p-md-5 m-md-3 text-center"
style="background-image: url({% static 'repapp/images/header_background.jpg' %});
background-repeat: no-repeat;
background-size: auto 100%;
background-position: center">
<div class="col-md-5 p-lg-5 mx-auto my-5">
<h1 class="display-4 font-weight-normal"
style="font-size: 60px;
font-weight: 700;
text-shadow: 1px 1px 2px rgba(0, 0, 0, 1);
color: white">
{% block page_title %}
{% endblock page_title %}
</h1>
</div>
</div>
<div class="container"> <div class="container">
<div class="row justify-content-center"> <div class="row justify-content-center">
<div class="col-8"> <div class="col-12">
{% if messages %} {% if messages %}
{% for message in messages %}<div class="alert alert-primary" role="alert">{{ message }}</div>{% endfor %} {% for message in messages %}<div class="alert alert-primary" role="alert">{{ message }}</div>{% endfor %}
{% endif %} {% endif %}
@ -30,14 +77,18 @@
</div> </div>
</div> </div>
</div> </div>
<footer> <div class="container">
{% if user.is_authenticated %} <footer class="d-flex flex-wrap justify-content-between align-items-center py-3 my-4 border-top">
<a href="{% url 'logout' %}">Gast Logout</a> <p class="col-md-4 mb-0 text-muted">© 2023 Repair-Café Hilpoltstein</p>
<a href="{% url 'member_login' %}">Mitarbeiter Logout</a> <ul class="nav col-md-4 justify-content-end">
{% else %} <li class="nav-item">
<a href="{% url 'login' %}">Gast Login</a> <a href="https://www.repaircafe-hilpoltstein.de/impressum"
<a href="{% url 'member_login' %}">Mitarbeiter Login</a> class="nav-link px-2 text-muted">Impressum & Datenschutz</a>
{% endif %} </li>
</footer> </ul>
</footer>
</div>
<script src="{% static 'repapp/js/jquery-3.6.4.min.js' %}"></script>
<script src="{% static 'repapp/js/bootstrap.min.js' %}"></script>
</body> </body>
</html> </html>

View file

@ -0,0 +1,81 @@
<!DOCTYPE html>
{% load static %}
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="description"
content="{% block description %} {% endblock description %}" />
<meta name="keywords"
content="{% block keywords %} {% endblock keywords %}" />
<meta name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<link rel="stylesheet" href="{% static 'repapp/css/bootstrap.min.css' %}" />
<title>RepApp -
{% block title %}
Bootstrap
{% endblock title %}
</title>
<link rel="stylesheet" href="{% static 'repapp/css/bootstrap.min.css' %}" />
{% block head %}
{% endblock head %}
</head>
<body>
<div class="container">
<header class="d-flex flex-wrap justify-content-center py-3 mb-4 border-bottom">
<a href="{% url 'index' %}"
class="d-flex align-items-center mb-3 mb-md-0 me-md-auto text-dark text-decoration-none">
<img src="{% static 'repapp/images/logo.jpg' %}"
height="50px"
width="168px"
alt="Repair-Café Logo">
</a>
<ul class="nav nav-pills">
<li class="nav-item">
<a href="https://www.repaircafe-hilpoltstein.de/" class="nav-link">Repair-Café</a>
</li>
<li class="nav-item">
<a href="{% url 'index' %}" class="nav-link">Anmeldung</a>
</li>
{% if user.is_authenticated %}
<li class="nav-item">
<a href="{% url 'logout' %}" class="nav-link">Abmelden</a>
</li>
{% else %}
<li class="nav-item">
<a href="{% url 'login' %}" class="nav-link">Gast Anmeldung</a>
</li>
<li class="nav-item">
<a href="{% url 'member_login' %}" class="nav-link">Mitglieder Anmeldung</a>
</li>
{% endif %}
</ul>
</header>
</div>
<div class="position-relative overflow-hidden p-3 p-md-5 m-md-3 text-center"
style="background-image: url({% static 'repapp/images/header_background.jpg' %});
background-repeat: no-repeat;
background-size: auto 100%;
background-position: center">
<div class="col-md-5 p-lg-5 mx-auto my-5">
<h1 class="display-4 font-weight-normal"
style="font-size: 60px;
font-weight: 700;
text-shadow: 1px 1px 2px rgba(0, 0, 0, 1);
color: white">Anmeldung</h1>
</div>
</div>
<div class="container">
<div class="row justify-content-center">
<div class="col-8">
{% if messages %}
{% for message in messages %}<div class="alert alert-primary" role="alert">{{ message }}</div>{% endfor %}
{% endif %}
{% block content %}
{% endblock content %}
</div>
</div>
</div>
<script src="{% static 'repapp/js/jquery-3.6.4.min.js' %}"></script>
<script src="{% static 'repapp/js/bootstrap.min.js' %}"></script>
</body>
</html>

View file

@ -2,9 +2,10 @@
{% block title %} {% block title %}
- {{ device.device }} - {{ device.device }}
{% endblock title %} {% endblock title %}
{% block page_title %}
Gerät: {{ device.device }}
{% endblock page_title %}
{% block content %} {% block content %}
<h1 class="mt-2">Gerät: {{ device.device }}</h1>
<hr class="mt-0 mb-4">
<table class="table"> <table class="table">
<tbody> <tbody>
<tr> <tr>

View file

@ -1,10 +1,11 @@
{% extends "repapp/base.html" %} {% extends "repapp/base.html" %}
{% block title %} {% block title %}
- Gast Daten - Ihre Daten
{% endblock title %} {% endblock title %}
{% block page_title %}
Ihre Daten
{% endblock page_title %}
{% block content %} {% block content %}
<h1 class="mt-2">Ihre Daten</h1>
<hr class="mt-0 mb-4">
<table class="table"> <table class="table">
<tbody> <tbody>
<tr> <tr>

View file

@ -282,8 +282,11 @@
} }
</style> </style>
{% endblock head %} {% endblock head %}
{% block page_title %}
Anmeldung
{% endblock page_title %}
{% block content %} {% block content %}
<h1 class="mt-2">Repair-Cafés</h1> <h1 class="mt-2">Repair-Café auswählen</h1>
<hr class="mt-0 mb-4"> <hr class="mt-0 mb-4">
<ul class="list-group"> <ul class="list-group">
{% for cafe in object_list %} {% for cafe in object_list %}

View file

@ -1,17 +1,16 @@
{% extends "repapp/base.html" %} {% extends "repapp/base.html" %}
{% block title %} {% block title %}
- Mitarbeiter Login Mitarbeiter Anmeldung
{% endblock title %} {% endblock title %}
{% block page_title %}
Mitarbeiter
{% if user.is_authenticated %}
Anmelden
{% else %}
Abmelden
{% endif %}
{% endblock page_title %}
{% block content %} {% block content %}
<h1 class="mt-2">
Mitarbeiter
{% if user.is_authenticated %}
Logout
{% else %}
Login
{% endif %}
</h1>
<hr class="mt-0 mb-4">
<div> <div>
{% if user.is_authenticated %} {% if user.is_authenticated %}
<p>Angemeldeter Benutzer: {{ user.email }}</p> <p>Angemeldeter Benutzer: {{ user.email }}</p>

View file

@ -3,9 +3,10 @@
{% block title %} {% block title %}
- Gerät anmelden - Gerät anmelden
{% endblock title %} {% endblock title %}
{% block page_title %}
Gerät anmelden
{% endblock page_title %}
{% block content %} {% block content %}
<h1 class="mt-2">Gerät anmelden</h1>
<hr class="mt-0 mb-4">
<form action="{% url 'register_device' cafe.id %}" <form action="{% url 'register_device' cafe.id %}"
method="post" method="post"
enctype="multipart/form-data"> enctype="multipart/form-data">

View file

@ -1,9 +1,10 @@
{% extends "repapp/base.html" %} {% extends "repapp/base.html" %}
{% block title %} {% block title %}
{% endblock title %} {% endblock title %}
{% block page_title %}
Anmeldung erfolgreich!
{% endblock page_title %}
{% block content %} {% block content %}
<h1 class="mt-2">Anmeldung erfolgreich!</h1>
<hr class="mt-0 mb-4">
<p> <p>
Vielen Dank für die Anmeldung des Geräts {{ device.device }} - {{ device.manufacturer }} zur Reparatur im Repair-Café am {{ cafe.event_date|date }}. Vielen Dank für die Anmeldung des Geräts {{ device.device }} - {{ device.manufacturer }} zur Reparatur im Repair-Café am {{ cafe.event_date|date }}.
</p> </p>

View file

@ -3,9 +3,10 @@
{% block title %} {% block title %}
- Gast anmelden - Gast anmelden
{% endblock title %} {% endblock title %}
{% block page_title %}
Ihre Daten
{% endblock page_title %}
{% block content %} {% block content %}
<h1 class="mt-2">Ihre Daten</h1>
<hr class="mt-0 mb-4">
<form action="{% url 'register_guest' cafe.id device.identifier mail %}" <form action="{% url 'register_guest' cafe.id device.identifier mail %}"
method="post"> method="post">
{% csrf_token %} {% csrf_token %}

View file

@ -1,17 +1,25 @@
""" """
Tests for RepApp. Tests for RepApp.
""" """
import time
from hashlib import sha256 from hashlib import sha256
from datetime import datetime, timedelta from datetime import datetime, timedelta
import django.utils.timezone import django.utils.timezone
from django.test import TestCase, RequestFactory
from django.test import TestCase
from django.test import Client from django.test import Client
from django.urls import reverse from django.urls import reverse
from django.core import mail
from django.contrib.messages.storage.fallback import FallbackStorage
from .backends import OneTimeLoginBackend, EmailBackend, create_repapp_user from .backends import OneTimeLoginBackend, EmailBackend, create_repapp_user
from .models import (OneTimeLogin, CustomUser, Cafe, Organisator, Reparateur) from .models import (OneTimeLogin, CustomUser, Cafe,
Organisator, Reparateur, Device, Guest)
from .views import (send_one_time_login_mail,
send_confirmation_mails,
send_guest_account_mail,
is_member,
create_one_time_login)
class OneTimeLoginTest(TestCase): class OneTimeLoginTest(TestCase):
@ -192,6 +200,8 @@ class ViewsTest(TestCase):
fixtures = ["cafe-data.json"] fixtures = ["cafe-data.json"]
def setUp(self): def setUp(self):
self.factory = RequestFactory()
cafe = Cafe(location="neuer Ort", address="neue Adresse", cafe = Cafe(location="neuer Ort", address="neue Adresse",
event_date=django.utils.timezone.now()) event_date=django.utils.timezone.now())
cafe.save() cafe.save()
@ -281,3 +291,114 @@ class ViewsTest(TestCase):
self.assertContains(response, "Abmelden") self.assertContains(response, "Abmelden")
# ensure user is displayed # ensure user is displayed
self.assertContains(response, "testuser@example.com") self.assertContains(response, "testuser@example.com")
def test_send_one_time_login_mail(self):
"""
Test that a mail is sent with the right link included.
"""
secret = "A_SECRET"
mail_address = "guest@example.com"
request = self.factory.get('/')
setattr(request, 'session', 'session')
setattr(request, '_messages', FallbackStorage(request))
send_one_time_login_mail(secret, mail_address, request)
self.assertEqual(len(mail.outbox), 1)
self.assertTrue("Einmal-Ammeldelink" in mail.outbox[0].subject)
self.assertEqual(mail.outbox[0].to, [mail_address])
self.assertTrue(f'/onetimelogin/{secret}/' in mail.outbox[0].body)
def test_send_confirmation_mails(self):
"""
Test that the confirmation mails are sent.
"""
device = Device.objects.get(pk=2)
guest = Guest.objects.get(pk=4)
cafe = Cafe.objects.get(pk=1)
organisator = Organisator(
name="Orga",
mail="orga@example.com"
)
organisator.save()
request = self.factory.get('/')
setattr(request, 'session', 'session')
setattr(request, '_messages', FallbackStorage(request))
send_confirmation_mails(device, guest, cafe, request)
self.assertEqual(len(mail.outbox), 2)
self.assertEqual(mail.outbox[0].subject,
f"Neues Gerät { device.device }")
self.assertEqual(mail.outbox[0].to, [organisator.mail])
self.assertTrue("Geräteanmeldung" in mail.outbox[1].subject)
self.assertEqual(mail.outbox[1].to, [guest.mail])
self.assertTrue("Geräteanmeldung" in mail.outbox[1].body)
def test_send_guest_account_mail(self):
"""
Test that an account creation mail was sent.
"""
password = "A_SECRET"
guest = Guest.objects.get(pk=4)
request = self.factory.get('/')
setattr(request, 'session', 'session')
setattr(request, '_messages', FallbackStorage(request))
send_guest_account_mail(guest, password, request)
self.assertEqual(len(mail.outbox), 1)
self.assertTrue("Benutzerkonto" in mail.outbox[0].subject)
self.assertEqual(mail.outbox[0].to, [guest.mail])
self.assertTrue(f'{password}' in mail.outbox[0].body)
self.assertTrue(f'{guest.mail}' in mail.outbox[0].body)
def test_is_member(self):
"""
Test that members are detected and not guests.
"""
organisator = Organisator(
name="Orga",
mail="orga@example.com"
)
organisator.save()
organisator_user = CustomUser(
username="orga",
email=organisator.mail
)
organisator_user.save()
reparateur = Reparateur(
name="Reparateur",
mail="repa@example.com"
)
reparateur.save()
reparateur_user = CustomUser(
username="repa",
email=reparateur.mail
)
reparateur_user.save()
guest = CustomUser.objects.get(pk=3)
self.assertTrue(is_member(organisator_user))
self.assertTrue(is_member(reparateur_user))
self.assertFalse(is_member(guest))
def test_create_one_time_login(self):
"""
Test one time login creation.
"""
user = CustomUser.objects.get(pk=3)
url = "/test/url/"
secret = create_one_time_login(user, url)
secret_hash = sha256(secret.encode('utf-8')).hexdigest()
otl = OneTimeLogin.objects.get(secret=secret_hash)
self.assertEqual(otl.url, url)

View file

@ -27,6 +27,7 @@ urlpatterns = [
path("member/login/", path("member/login/",
views.member_login, name="member_login"), views.member_login, name="member_login"),
path("cron", views.cron, name="cron"), path("cron", views.cron, name="cron"),
path("bootstrap", views.bootstrap, name="bootstrap"),
path("process_mails", views.process_mails, name="process_mails"), path("process_mails", views.process_mails, name="process_mails"),
] ]

View file

@ -26,6 +26,9 @@ from .forms import RegisterDevice, RegisterGuest
def send_one_time_login_mail(secret, mail, request): def send_one_time_login_mail(secret, mail, request):
"""
Send a mail with a one time login link.
"""
url = request.build_absolute_uri( url = request.build_absolute_uri(
f'/onetimelogin/{secret}/') f'/onetimelogin/{secret}/')
subject = render_to_string( subject = render_to_string(
@ -52,6 +55,9 @@ def send_one_time_login_mail(secret, mail, request):
def send_confirmation_mails(device, guest, cafe, request): def send_confirmation_mails(device, guest, cafe, request):
"""
Send confirmation mails for device registration.
"""
organizers = [] organizers = []
for organizer in Organisator.objects.all(): for organizer in Organisator.objects.all():
organizers.append(organizer.mail) organizers.append(organizer.mail)
@ -110,6 +116,9 @@ def send_confirmation_mails(device, guest, cafe, request):
def send_guest_account_mail(guest, password, request): def send_guest_account_mail(guest, password, request):
"""
Send guest user account created mail.
"""
url = request.build_absolute_uri('/guest/profile/') url = request.build_absolute_uri('/guest/profile/')
subject = render_to_string( subject = render_to_string(
'repapp/mail/mail_new_guest_subject.html').replace('\n', '') 'repapp/mail/mail_new_guest_subject.html').replace('\n', '')
@ -139,6 +148,9 @@ def send_guest_account_mail(guest, password, request):
def is_member(user): def is_member(user):
"""
Test is a user is a Repair-Café member.
"""
organisator = Organisator.objects.filter(mail=user.email).first() organisator = Organisator.objects.filter(mail=user.email).first()
reparateur = Reparateur.objects.filter(mail=user.email).first() reparateur = Reparateur.objects.filter(mail=user.email).first()
return organisator or reparateur return organisator or reparateur
@ -146,7 +158,7 @@ def is_member(user):
def create_one_time_login(user, url) -> str: def create_one_time_login(user, url) -> str:
""" """
create_one_time_login create a one time login object for user logins. create_one_time_login creates a one time login object for guest user logins.
""" """
secret = sha256( secret = sha256(
f'{user.email}{url}{datetime.datetime.now()}{random.randint(0,9999999)}'.encode( f'{user.email}{url}{datetime.datetime.now()}{random.randint(0,9999999)}'.encode(
@ -435,3 +447,13 @@ def one_time_login(request, secret: str):
else: else:
messages.add_message(request, messages.ERROR, 'Login fehlgeschlagen.') messages.add_message(request, messages.ERROR, 'Login fehlgeschlagen.')
return HttpResponseRedirect(reverse_lazy('index')) return HttpResponseRedirect(reverse_lazy('index'))
def bootstrap(request):
"""
Bootstrap design test page.
"""
return render(
request,
"repapp/bootstrap.html"
)