canaille-globuzma/tests/core/test_profile_settings.py
Éloi Rivard b4908d5e57
modals are HTML pages instead of JS elements
This will help providing the very same user experience for users with
and without javascript. We will still be able to re-enable javascript
modals in the future, but this should be done from the ground up, HTML
first and javascript after.
2023-07-18 18:34:10 +02:00

427 lines
13 KiB
Python

import datetime
from unittest import mock
from canaille.app import models
def test_edition(
testclient,
logged_user,
admin,
foo_group,
bar_group,
):
res = testclient.get("/profile/user/settings", status=200)
assert set(res.form["groups"].options) == {
(foo_group.id, True, "foo"),
(bar_group.id, False, "bar"),
}
assert logged_user.groups == [foo_group]
assert foo_group.members == [logged_user]
assert bar_group.members == [admin]
assert res.form["groups"].attrs["readonly"]
assert res.form["user_name"].attrs["readonly"]
res.form["user_name"] = "toto"
res = res.form.submit(name="action", value="edit")
assert res.flashes == [("success", "Profile updated successfully.")]
res = res.follow()
logged_user.reload()
assert logged_user.user_name == ["user"]
foo_group.reload()
bar_group.reload()
assert logged_user.groups == [foo_group]
assert foo_group.members == [logged_user]
assert bar_group.members == [admin]
assert logged_user.check_password("correct horse battery staple")[0]
logged_user.user_name = ["user"]
logged_user.save()
def test_profile_settings_edition_dynamic_validation(testclient, logged_admin):
res = testclient.get("/profile/admin/settings")
res = testclient.post(
"/profile/admin/settings",
{
"csrf_token": res.form["csrf_token"].value,
"password1": "short",
},
headers={
"HX-Request": "true",
"HX-Trigger-Name": "password1",
},
)
res.mustcontain("Field must be at least 8 characters long.")
def test_edition_without_groups(
testclient,
logged_user,
admin,
):
res = testclient.get("/profile/user/settings", status=200)
testclient.app.config["ACL"]["DEFAULT"]["READ"] = []
res = res.form.submit(name="action", value="edit")
assert res.flashes == [("success", "Profile updated successfully.")]
res = res.follow()
logged_user.reload()
assert logged_user.user_name == ["user"]
assert logged_user.check_password("correct horse battery staple")[0]
logged_user.user_name = ["user"]
logged_user.save()
def test_password_change(testclient, logged_user):
res = testclient.get("/profile/user/settings", status=200)
res.form["password1"] = "new_password"
res.form["password2"] = "new_password"
res = res.form.submit(name="action", value="edit").follow()
logged_user.reload()
assert logged_user.check_password("new_password")[0]
res = testclient.get("/profile/user/settings", status=200)
res.form["password1"] = "correct horse battery staple"
res.form["password2"] = "correct horse battery staple"
res = res.form.submit(name="action", value="edit")
assert ("success", "Profile updated successfully.") in res.flashes
res = res.follow()
logged_user.reload()
assert logged_user.check_password("correct horse battery staple")[0]
def test_password_change_fail(testclient, logged_user):
res = testclient.get("/profile/user/settings", status=200)
res.form["password1"] = "new_password"
res.form["password2"] = "other_password"
res = res.form.submit(name="action", value="edit", status=200)
logged_user.reload()
assert logged_user.check_password("correct horse battery staple")[0]
res = testclient.get("/profile/user/settings", status=200)
res.form["password1"] = "new_password"
res.form["password2"] = ""
res = res.form.submit(name="action", value="edit", status=200)
logged_user.reload()
assert logged_user.check_password("correct horse battery staple")[0]
def test_password_initialization_mail(smtpd, testclient, backend, logged_admin):
u = models.User(
formatted_name="Temp User",
family_name="Temp",
user_name="temp",
emails="john@doe.com",
)
u.save()
res = testclient.get("/profile/temp/settings", status=200)
res.mustcontain("This user does not have a password yet")
res.mustcontain("Send")
res = res.form.submit(name="action", value="password-initialization-mail")
assert (
"success",
"A password initialization link has been sent at the user email address. "
"It should be received within a few minutes.",
) in res.flashes
assert len(smtpd.messages) == 1
assert smtpd.messages[0]["X-RcptTo"] == "john@doe.com"
u.reload()
u.password = ["correct horse battery staple"]
u.save()
res = testclient.get("/profile/temp/settings", status=200)
res.mustcontain(no="This user does not have a password yet")
u.delete()
@mock.patch("smtplib.SMTP")
def test_password_initialization_mail_send_fail(
SMTP, smtpd, testclient, backend, logged_admin
):
SMTP.side_effect = mock.Mock(side_effect=OSError("unit test mail error"))
u = models.User(
formatted_name="Temp User",
family_name="Temp",
user_name="temp",
emails="john@doe.com",
)
u.save()
res = testclient.get("/profile/temp/settings", status=200)
res.mustcontain("This user does not have a password yet")
res.mustcontain("Send")
res = res.form.submit(
name="action", value="password-initialization-mail", expect_errors=True
)
assert (
"success",
"A password initialization link has been sent at the user email address. "
"It should be received within a few minutes.",
) not in res.flashes
assert ("error", "Could not send the password initialization email") in res.flashes
assert len(smtpd.messages) == 0
u.delete()
def test_password_initialization_invalid_user(smtpd, testclient, backend, logged_admin):
assert len(smtpd.messages) == 0
res = testclient.get("/profile/admin/settings")
testclient.post(
"/profile/invalid/settings",
{
"action": "password-initialization-mail",
"csrf_token": res.form["csrf_token"].value,
},
status=404,
)
assert len(smtpd.messages) == 0
def test_password_reset_invalid_user(smtpd, testclient, backend, logged_admin):
assert len(smtpd.messages) == 0
res = testclient.get("/profile/admin/settings")
testclient.post(
"/profile/invalid/settings",
{"action": "password-reset-mail", "csrf_token": res.form["csrf_token"].value},
status=404,
)
assert len(smtpd.messages) == 0
def test_delete_invalid_user(testclient, backend, logged_admin):
res = testclient.get("/profile/admin/settings")
testclient.post(
"/profile/invalid/settings",
{"action": "delete", "csrf_token": res.form["csrf_token"].value},
status=404,
)
def test_impersonate_invalid_user(testclient, backend, logged_admin):
testclient.get("/impersonate/invalid", status=404)
def test_invalid_form_request(testclient, logged_admin):
res = testclient.get("/profile/admin/settings")
res = res.form.submit(name="action", value="invalid-action", status=400)
def test_password_reset_email(smtpd, testclient, backend, logged_admin):
u = models.User(
formatted_name="Temp User",
family_name="Temp",
user_name="temp",
emails="john@doe.com",
password="correct horse battery staple",
)
u.save()
res = testclient.get("/profile/temp/settings", status=200)
res.mustcontain("If the user has forgotten his password")
res.mustcontain("Send")
res = res.form.submit(name="action", value="password-reset-mail")
assert (
"success",
"A password reset link has been sent at the user email address. "
"It should be received within a few minutes.",
) in res.flashes
assert len(smtpd.messages) == 1
assert smtpd.messages[0]["X-RcptTo"] == "john@doe.com"
u.delete()
@mock.patch("smtplib.SMTP")
def test_password_reset_email_failed(SMTP, smtpd, testclient, backend, logged_admin):
SMTP.side_effect = mock.Mock(side_effect=OSError("unit test mail error"))
u = models.User(
formatted_name="Temp User",
family_name="Temp",
user_name="temp",
emails="john@doe.com",
password=["correct horse battery staple"],
)
u.save()
res = testclient.get("/profile/temp/settings", status=200)
res.mustcontain("If the user has forgotten his password")
res.mustcontain("Send")
res = res.form.submit(
name="action", value="password-reset-mail", expect_errors=True
)
assert (
"success",
"A password reset link has been sent at the user email address. "
"It should be received within a few minutes.",
) not in res.flashes
assert ("error", "Could not send the password reset email") in res.flashes
assert len(smtpd.messages) == 0
u.delete()
def test_admin_bad_request(testclient, logged_admin):
testclient.post("/profile/admin/settings", {"action": "foobar"}, status=400)
testclient.get("/profile/foobar/settings", status=404)
def test_edition_permission(
testclient,
logged_user,
admin,
):
testclient.app.config["ACL"]["DEFAULT"]["PERMISSIONS"] = []
testclient.get("/profile/user/settings", status=404)
testclient.app.config["ACL"]["DEFAULT"]["PERMISSIONS"] = ["edit_self"]
testclient.get("/profile/user/settings", status=200)
def test_account_locking(
testclient,
backend,
logged_admin,
user,
):
res = testclient.get("/profile/user/settings")
assert not user.lock_date
assert not user.locked
res.mustcontain("Lock the account")
res.mustcontain(no="Unlock")
res = res.form.submit(name="action", value="confirm-lock")
res = res.form.submit(name="action", value="lock")
user = models.User.get(id=user.id)
assert user.lock_date <= datetime.datetime.now(datetime.timezone.utc)
assert user.locked
res.mustcontain("The account has been locked")
res.mustcontain(no="Lock the account")
res.mustcontain("Unlock")
res = res.form.submit(name="action", value="unlock")
user = models.User.get(id=user.id)
assert not user.lock_date
assert not user.locked
res.mustcontain("The account has been unlocked")
res.mustcontain("Lock the account")
res.mustcontain(no="Unlock")
def test_past_lock_date(
testclient,
backend,
logged_admin,
user,
):
res = testclient.get("/profile/user/settings", status=200)
assert not user.lock_date
assert not user.locked
expiration_datetime = datetime.datetime.now(datetime.timezone.utc).replace(
second=0, microsecond=0
) - datetime.timedelta(days=30)
res.form["lock_date"] = expiration_datetime.strftime("%Y-%m-%d %H:%M")
res = res.form.submit(name="action", value="edit")
assert res.flashes == [("success", "Profile updated successfully.")]
res = res.follow()
user = models.User.get(id=user.id)
assert user.lock_date == expiration_datetime
assert user.locked
def test_future_lock_date(
testclient,
backend,
logged_admin,
user,
):
res = testclient.get("/profile/user/settings", status=200)
assert not user.lock_date
assert not user.locked
expiration_datetime = datetime.datetime.now(datetime.timezone.utc).replace(
second=0, microsecond=0
) + datetime.timedelta(days=30)
res.form["lock_date"] = expiration_datetime.strftime("%Y-%m-%d %H:%M")
res = res.form.submit(name="action", value="edit")
assert res.flashes == [("success", "Profile updated successfully.")]
res = res.follow()
user = models.User.get(id=user.id)
assert user.lock_date == expiration_datetime
assert not user.locked
assert res.form["lock_date"].value == expiration_datetime.strftime("%Y-%m-%d %H:%M")
def test_empty_lock_date(
testclient,
backend,
logged_admin,
user,
):
expiration_datetime = datetime.datetime.now(datetime.timezone.utc).replace(
second=0, microsecond=0
) + datetime.timedelta(days=30)
user.lock_date = expiration_datetime
user.save()
res = testclient.get("/profile/user/settings", status=200)
res.form["lock_date"] = ""
res = res.form.submit(name="action", value="edit")
assert res.flashes == [("success", "Profile updated successfully.")]
res = res.follow()
user.reload()
assert not user.lock_date
def test_account_limit_values(
testclient,
backend,
logged_admin,
user,
):
res = testclient.get("/profile/user/settings", status=200)
assert not user.lock_date
assert not user.locked
expiration_datetime = datetime.datetime.max.replace(
microsecond=0, tzinfo=datetime.timezone.utc
)
res.form["lock_date"] = expiration_datetime.strftime("%Y-%m-%d %H:%M:%S")
res = res.form.submit(name="action", value="edit")
assert res.flashes == [("success", "Profile updated successfully.")]
res = res.follow()
user = models.User.get(id=user.id)
assert user.lock_date == expiration_datetime
assert not user.locked