tests started, docs rework started

This commit is contained in:
Tom Irgang 2023-04-24 21:03:56 +02:00
parent 8e96c18b7a
commit 2a1a24ced7
8 changed files with 200 additions and 15 deletions

View file

@ -4,6 +4,8 @@ RepApp ist eine Web App um Reparaturen im Rahmen eines Repair-Cafés zu organisi
## Benutzer
RepApp unterscheidet zwischen den folgenden Benutzer-Typen:
### Gast
Ein Gast ist ein Gerätebesitzer der ein oder mehrere Geräte zur Reparatur anmelden möchte.
@ -18,6 +20,9 @@ Ein Organisator ist Mitglied des Repair-Cafés das die Termine mit den Gästen o
## Anwendungsfälle
Die Anwendungsfälle sind in Meilensteine unterteilt.
Zu Meilenstein 1 (M1) gehören alle Anwendungsfälle die notwendig sind um RepApp "produktiv" für die Anmeldung von Geräten zu verwenden.
### Gast
- [x] M1: Als Gast möchte ich einen Termin für eine Reparatur vereinbaren. (A1)
@ -30,7 +35,9 @@ Ein Organisator ist Mitglied des Repair-Cafés das die Termine mit den Gästen o
- [ ] Als Gast möchte ich meine Daten ansehen können. (A21)
- [ ] Als Gast möchte ich meine Daten löschen können. (A22)
- [ ] Als Gast möchte ich eine Geräteanmeldung löschen können. (A23)
- [ ] M1: Als Gast möchte ausschließlich per eMail mit dem System interagieren können. (A30)
- [ ] M1: Als Gast möchte ich ausschließlich per eMail mit dem System interagieren können. (A30)
- [ ] M1: Als Gast möchte per eMail Fragen ans Team stellen können. (A33)
- [ ] M1: Als Gast möchte ich per eMail auf Rückfragen antworten können. (A34)
### Reparateur
@ -272,7 +279,7 @@ Die Seite enthält persönliche Daten und ist nur nach Anmeldung erreichbar.
### Anmeldung eines defekten Gerätes
#### Variante: Neuer Gast
#### Variante: Neuer Gast (F1)
Als neuer Gast der ein defekten Geräte anmelden möchte,
@ -282,7 +289,7 @@ Als neuer Gast der ein defekten Geräte anmelden möchte,
- Die nächste Seite (S5) bestätigt mir die Anmeldung meines Gerätes.
- In meinem eMail Posteingang finde ich ebenfalls eine Bestätigung der Geräteanmeldung.
#### Variante: Bekannter Gast (existierende eMail Adresse)
#### Variante: Bekannter Gast (existierende eMail Adresse) (F2)
Als bekannter Gast der ein defekten Geräte anmelden möchte,

View file

@ -163,7 +163,6 @@ AUTH_USER_MODEL = "repapp.CustomUser"
AUTHENTICATION_BACKENDS = [
"repapp.backends.EmailBackend",
"django.contrib.auth.backends.ModelBackend",
# "mozilla_django_oidc.auth.OIDCAuthenticationBackend",
"repapp.backends.KeycloakOIDCAB",
"repapp.backends.OneTimeLoginBackend",
]

View file

@ -5,7 +5,7 @@ from django.contrib import admin
from import_export.admin import ImportExportModelAdmin, ExportActionMixin
from import_export import resources
from .models import (Organisator, Cafe, Question, Device, CustomUser,
Reparateur, Appointment, Guest, Candidate, OneTimeLogin)
Reparateur, Appointment, Guest, Candidate, OneTimeLogin, Message)
class GuestResource(resources.ModelResource):
@ -96,13 +96,16 @@ class ExportAdmin(ImportExportModelAdmin, ExportActionMixin):
]
# Register all models of RepApp for the admin interface.
admin.site.register(Organisator)
admin.site.register(Cafe)
admin.site.register(Reparateur)
admin.site.register(CustomUser)
admin.site.register(OneTimeLogin)
admin.site.register(Message)
# Make the "data" models exportable.
admin.site.register(Question, ExportAdmin)
admin.site.register(Guest, ExportAdmin)
admin.site.register(Device, ExportAdmin)
admin.site.register(Reparateur)
admin.site.register(Appointment, ExportAdmin)
admin.site.register(Candidate, ExportAdmin)
admin.site.register(CustomUser)
admin.site.register(OneTimeLogin)

View file

@ -28,10 +28,10 @@ class EmailBackend(ModelBackend):
"""
def authenticate(self, request, username=None, password=None, **kwargs):
userModel = get_user_model()
user_model = get_user_model()
try:
user = userModel.objects.get(email=username)
except userModel.DoesNotExist:
user = user_model.objects.get(email=username)
except user_model.DoesNotExist:
return None
else:
if user.check_password(password):
@ -41,7 +41,8 @@ class EmailBackend(ModelBackend):
class KeycloakOIDCAB(OIDCAuthenticationBackend):
"""
KeycloakOIDCAB allows a login using Open ID Connect (with the Repair-Café Keycloak Single Sign On server)
KeycloakOIDCAB allows a login using Open ID Connect
(with the Repair-Café Keycloak Single Sign On server)
"""
def create_user(self, claims):

View file

@ -6,6 +6,9 @@ from django.core.exceptions import ValidationError
class HoneypotField(forms.BooleanField):
"""
Simple honeypot field.
"""
default_widget = forms.CheckboxInput(
{'style': 'display:none !important;', 'tabindex': '-1', 'autocomplete': 'off'})
@ -22,6 +25,9 @@ class HoneypotField(forms.BooleanField):
class RegisterDevice(forms.Form):
"""
Form for registering a device for a repair cafe.
"""
mail = forms.EmailField(
label="eMail Adresse",
help_text="Diese eMail-Adresse wird für Aktualisierungen"
@ -49,7 +55,8 @@ class RegisterDevice(forms.Form):
)
follow_up = forms.BooleanField(
label="Folgetermin",
help_text="Kreuzen Sie diese Kästchen an wenn sie mit diesem Gerät bereits bei einem Repair-Café Termin waren.",
help_text="Kreuzen Sie diese Kästchen an wenn sie mit diesem Gerät "
"bereits bei einem Repair-Café Termin waren.",
required=False
)
confirm_repair = forms.BooleanField(
@ -72,6 +79,9 @@ class RegisterDevice(forms.Form):
class RegisterGuest(forms.Form):
"""
Form for registering new guests.
"""
name = forms.CharField(
label="Name",
max_length=200

View file

@ -175,6 +175,9 @@ class Candidate(models.Model):
class CustomUser(AbstractUser):
"""
Custom user object with unique email.
"""
email = models.EmailField(unique=True, verbose_name=_("eMail Adresse"))
class Meta:

View file

@ -1 +1,137 @@
# Create your tests here.
"""
Tests for RepApp.
"""
from datetime import datetime, timedelta
import django.utils.timezone
from django.test import TestCase
from django.test import Client
from django.urls import reverse
from .backends import OneTimeLoginBackend, EmailBackend
from .models import OneTimeLogin, CustomUser, Cafe
class OneTimeLoginTest(TestCase):
"""
Tests for the one time login feature.
"""
def setUp(self):
user = CustomUser(
email="user@example.com",
)
user.save()
self.user = user
otl = OneTimeLogin(
secret="LetMeIn",
user=user,
url="/test/url",
)
otl.save()
self.otl = otl
def test_allowed_one_time_login(self):
"""
Test that one time login is allowed.
"""
backend = OneTimeLoginBackend()
result = backend.authenticate(None, self.otl.secret, None)
self.assertEqual(result, self.user)
def test_unknown_one_time_login(self):
"""
Test that one time login is not allowed for unknown secret.
"""
backend = OneTimeLoginBackend()
result = backend.authenticate(None, "other phrase", None)
self.assertEqual(result, None)
class EmailLoginTest(TestCase):
"""
Tests for the one time login feature.
"""
def setUp(self):
user = CustomUser(
email="user@example.com"
)
user.set_password("aPassword")
user.save()
self.user = user
def test_allowed_email_login(self):
"""
Test that email login is allowed.
"""
backend = EmailBackend()
result = backend.authenticate(None, self.user.email, "aPassword")
self.assertEqual(result, self.user)
def test_unknown_email_login(self):
"""
Test that email login is not allowed for unknown user.
"""
backend = EmailBackend()
result = backend.authenticate(
None, "other@example.com", "somePassword")
self.assertEqual(result, None)
def test_wrong_password_login(self):
"""
Test that email login is not allowed for wrong password.
"""
backend = EmailBackend()
result = backend.authenticate(
None, self.user.email, "wrongPassword")
self.assertEqual(result, None)
class UtilsTest(TestCase):
"""
Test for utility functions.
"""
pass
class ViewsTest(TestCase):
"""
Test for RepApp views.
"""
def setUp(self):
cafe = Cafe(location="neuer Ort", address="neue Adresse",
event_date=django.utils.timezone.now())
cafe.save()
self.cafe = cafe
date = datetime.now() - timedelta(days=7)
old_cafe = Cafe(location="alter Ort", address="alte Adresse",
event_date=date)
old_cafe.save()
self.old_cafe = old_cafe
def test_index(self):
"""
Test index page.
"""
client = Client(enforce_csrf_checks=True)
response = client.get(reverse('index'))
self.assertEqual(response.status_code, 200)
# ensure future cafe is displayed
self.assertContains(response, self.cafe.location)
self.assertContains(response, self.cafe.address)
# ensure old cafe is not displayed
self.assertNotContains(response, self.old_cafe.location)
self.assertNotContains(response, self.old_cafe.address)

View file

@ -103,6 +103,9 @@ class RegisterDeviceFormView(generic.edit.FormView):
class RegisterGuestFormView(generic.edit.FormView):
"""
View for registering a new guest.
"""
template_name = "repapp/register_guest.html"
form_class = RegisterGuest
@ -137,7 +140,8 @@ class RegisterGuestFormView(generic.edit.FormView):
user = CustomUser.objects.create_user(
username=name,
email=mail,
password=password)
)
user.set_password(password)
user.save()
send_guest_account_mail(guest, password, self.request)
@ -168,6 +172,9 @@ class RegisterGuestFormView(generic.edit.FormView):
def register_device_final(request, cafe, device_identifier):
"""
View to confirm device registration.
"""
cafe = get_object_or_404(Cafe, pk=cafe)
device = get_object_or_404(Device, identifier=device_identifier)
@ -180,6 +187,9 @@ def register_device_final(request, cafe, device_identifier):
@login_required
def device_view(request, device_identifier):
"""
View for showing device details.
"""
user = request.user
if not user:
raise PermissionDenied()
@ -200,6 +210,9 @@ def device_view(request, device_identifier):
@login_required
def profile(request):
"""
View for showing guest details.
"""
user = request.user
if not user:
raise PermissionDenied()
@ -216,6 +229,9 @@ def profile(request):
def member_login(request):
"""
Login page for repair cafe members, using OIDC.
"""
return render(
request,
"repapp/member_login.html"
@ -223,14 +239,24 @@ def member_login(request):
def cron(request):
"""
View to trigger automated regular tasks.
"""
pass
def process_mails(request):
"""
View to trigger processing of email in inbox.
"""
pass
def one_time_login(request, secret):
"""
View for one time login.
"""
# waste a little time as brute force protection
time.sleep(1)
otl = get_object_or_404(OneTimeLogin, secret=secret)