Beispiel #1
0
def wsgi_factory():  # pragma: no cover
    morepath.autoscan()

    if os.getenv("RUN_ENV") == "production":
        ProductionApp.commit()
        app = ProductionApp()
    elif os.getenv("RUN_ENV") == "test":
        TestApp.commit()
        app = TestApp()
    else:
        App.commit()
        app = App()

    index = FileApp("build/index.html")
    static = DirectoryApp("build", index_page=None)

    setup_db(app)

    @webob.dec.wsgify
    def morepath_with_static_absorb(request):
        popped = request.path_info_pop()
        if popped == "api":
            return request.get_response(app)
        elif popped == "static":
            return request.get_response(static)
        else:
            return request.get_response(index)

    return morepath_with_static_absorb
Beispiel #2
0
def test_update_group():
    c = Client(App())

    response = c.post(
        "/login",
        json.dumps({
            "email": "*****@*****.**",
            "password": "******"
        }))

    headers = {"Authorization": response.headers["Authorization"]}

    updateted_group_json = json.dumps(
        {"users": ["*****@*****.**", "*****@*****.**"]})
    c.put("/groups/3", updateted_group_json, headers=headers)

    with db_session:
        g = Group[3]
        assert User[2] in g.users
        assert User[3] in g.users

    updateted_group_json = json.dumps({"name": "Moderator"})
    response = c.put("/groups/1",
                     updateted_group_json,
                     headers=headers,
                     status=409)
    assert response.json == {"validationError": "Group already exists"}

    updateted_group_json = json.dumps({"name": "Guru"})
    c.put("/groups/1", updateted_group_json, headers=headers)

    with db_session:
        assert Group[1].name == "Guru"
Beispiel #3
0
def test_send_reset_email(smtp):
    c = Client(App(), extra_environ=dict(REMOTE_ADDR="127.0.0.1"))

    new_user_json = json.dumps(
        {"nickname": "NewUser", "email": "*****@*****.**", "password": "******"}
    )
    c.post("/users", new_user_json, status=201)
    assert len(smtp.outbox) == 2

    response = c.post(
        "/reset", json.dumps({"email": "*****@*****.**"}), status=403
    )
    assert response.json == {"validationError": "Email not found"}

    response = c.post(
        "/reset", json.dumps({"email": "*****@*****.**"}), status=403
    )
    assert response.json == {
        "validationError": "Your email must be confirmed before resetting the password."
    }

    c.get(
        "/users/4/confirm/"
        + "Im5ld3VzZXJAZXhhbXBsZS5jb20i.C3dnsw.2meomRPK3wnYwB2AERt2ygjFaRE",
        status=302,
    )

    response = c.post("/reset", json.dumps({"email": "*****@*****.**"}))

    assert len(smtp.outbox) == 3
    message = smtp.outbox[2]
    assert message["subject"] == "Password Reset Requested"
    assert message["To"] == "*****@*****.**"
Beispiel #4
0
def test_sorted_user_collection():
    c = Client(App())

    with db_session:
        User[1].email_confirmed = True

    response = c.post(
        "/login", json.dumps({"email": "*****@*****.**", "password": "******"})
    )

    headers = {"Authorization": response.headers["Authorization"]}

    response = c.get("/users?sortby=nickname", headers=headers)

    assert response.json["users"][0]["nickname"] == "Jürgen"

    response = c.get("/users?sortby=email&sortdir=desc", headers=headers)

    assert response.json["users"][0]["email"] == "*****@*****.**"

    response = c.get("/users?sortby=emailConfirmed", headers=headers)

    assert response.json["users"][0]["nickname"] == "Mary"

    response = c.get("/users?sortby=lastLogin", headers=headers)

    assert response.json["users"][0]["nickname"] == "Mary"

    response = c.get("/users?sortby=registerIP", headers=headers)

    assert response.json["users"][0]["nickname"] == "Leader"
Beispiel #5
0
def test_reset_nonce():
    c = Client(App())

    response = c.get("/users/2/signout", status=403)
    assert response.body == (
        b"403 Forbidden\n\nAccess was denied to this resource.\n\n   "
    )

    response = c.post(
        "/login", json.dumps({"email": "*****@*****.**", "password": "******"})
    )

    headers = {"Authorization": response.headers["Authorization"]}

    with db_session:
        original_nonce = User[2].nonce

    c.get("/users/2/signout", headers=headers)

    with db_session:
        assert User[2].nonce != original_nonce

    response = c.post(
        "/login", json.dumps({"email": "*****@*****.**", "password": "******"})
    )

    headers = {"Authorization": response.headers["Authorization"]}

    with db_session:
        original_nonce = User[2].nonce

    c.get("/users/2/signout", headers=headers)

    with db_session:
        assert User[2].nonce != original_nonce
Beispiel #6
0
def test_add_group():
    c = Client(App())

    response = c.post(
        "/login",
        json.dumps({
            "email": "*****@*****.**",
            "password": "******"
        }))

    headers = {"Authorization": response.headers["Authorization"]}

    new_group_json = json.dumps({"name": "NewGroup"})

    response = c.post("/groups", new_group_json, headers=headers, status=201)
    assert response.json == {"@id": "/groups/4"}
    with db_session:
        assert Group.exists(name="NewGroup")

    response = c.post("/groups", new_group_json, headers=headers, status=409)
    assert response.json == {"validationError": "Group already exists"}

    with_users_json = json.dumps({
        "name":
        "UserGroup",
        "users":
        ["*****@*****.**", "*****@*****.**", "*****@*****.**"],
    })
    c.post("/groups", with_users_json, headers=headers)

    with db_session:
        assert Group.exists(name="UserGroup")
        assert User[1] in Group.get(name="UserGroup").users
        assert User[2] in Group.get(name="UserGroup").users
        assert User[3] in Group.get(name="UserGroup").users
def test_list():
    c = Client(App())
    response = c.get("/todos")
    todo_list = {
        "todos": [
            {
                "@id": "http://localhost/todos/0",
                "title": "Code",
                "completed": True
            },
            {
                "@id": "http://localhost/todos/1",
                "title": "Test",
                "completed": False
            },
            {
                "@id": "http://localhost/todos/2",
                "title": "Document",
                "completed": False,
            },
            {
                "@id": "http://localhost/todos/3",
                "title": "Release",
                "completed": False
            },
        ]
    }
    assert response.json == todo_list
def test_add_todo():
    c = Client(App())

    new_todo_json = json.dumps({"title": "Something else", "completed": False})
    response = c.post('/todos', new_todo_json)
    new_todo_response = {"@id": "http://localhost/todos/4",
                         "title": "Something else", "completed": False}
    assert response.json == new_todo_response
def test_todo():
    c = Client(App())
    response = c.get("/todos/0")
    assert response.json == {
        "@id": "http://localhost/todos/0",
        "title": "Code",
        "completed": True,
    }
Beispiel #10
0
def test_group():
    c = Client(App())
    response = c.get("/groups/1")
    group = {
        "@id": "/groups/1",
        "name": "Admin",
        "users": ["*****@*****.**"]
    }
    assert_dict_contains_subset(group, response.json)
def test_change_todo():
    c = Client(App())

    changed_todo_json = json.dumps(
        {"title": "Changed Test", "completed": True}
    )
    response = c.put('/todos/1', changed_todo_json)
    changed_todo_response = {"@id": "http://localhost/todos/1",
                             "title": "Changed Test", "completed": True}
    assert response.json == changed_todo_response
Beispiel #12
0
def test_reset_password(smtp):
    c = Client(App(), extra_environ=dict(REMOTE_ADDR="127.0.0.1"))

    new_user_json = json.dumps(
        {"nickname": "NewUser", "email": "*****@*****.**", "password": "******"}
    )
    c.post("/users", new_user_json, status=201)
    assert len(smtp.outbox) == 1

    response = c.get(
        "/users/4/reset/"
        + "Im5ld3VzZXJAZXhhbXBsZS5jb20i.WrongToken.JrUGhlAxao46VsQevI",
        status=302,
    )

    flash = (
        urlsafe_b64encode(b"The password reset link is invalid or has been expired")
        .replace(b"=", b"")
        .decode("utf-8")
    )
    assert "302 Found" in response.text
    assert "flash=" + flash in response.text
    assert "flashtype=error" in response.text

    response = c.get(
        "/users/4/reset/"
        + "Im5ld3VzZXJAZXhhbXBsZS5jb20i.C40RUQ.5JhlEE36_JrUGhlAxao46VsQevI",
        status=302,
    )

    flash = (
        urlsafe_b64encode(b"Your email must be confirmed before resetting the password")
        .replace(b"=", b"")
        .decode("utf-8")
    )
    assert "302 Found" in response.text
    assert "flash=" + flash in response.text
    assert "flashtype=error" in response.text

    c.get(
        "/users/4/confirm/"
        + "Im5ld3VzZXJAZXhhbXBsZS5jb20i.C3dnsw.2meomRPK3wnYwB2AERt2ygjFaRE",
        status=302,
    )

    response = c.get(
        "/users/4/reset/"
        + "Im5ld3VzZXJAZXhhbXBsZS5jb20i.C40RUQ.5JhlEE36_JrUGhlAxao46VsQevI",
        status=302,
    )

    assert response.location == (
        "http://localhost/newpassword?%40id=%2Fusers%2F4%2Freset%2F"
        + "Im5ld3VzZXJAZXhhbXBsZS5jb20i.C40RUQ.5JhlEE36_JrUGhlAxao46VsQevI"
    )
def test_delete_todo():
    c = Client(App())

    response = c.delete('/todos/2')
    todo_list = {"todos": [
        {"@id": "http://localhost/todos/0",
         "title": "Code", "completed": True},
        {"@id": "http://localhost/todos/1",
         "title": "Test", "completed": False},
        {"@id": "http://localhost/todos/3",
         "title": "Release", "completed": False}
    ]}
    assert response.json == todo_list
Beispiel #14
0
def test_root():
    app = App()
    c = Client(app)

    response = c.get("/")
    assert response.json == {
        "collections": {
            "users": {
                "@id": "/users"
            },
            "groups": {
                "@id": "/groups"
            }
        }
    }
Beispiel #15
0
def test_confirm_email():
    c = Client(App(), extra_environ=dict(REMOTE_ADDR="127.0.0.1"))

    new_user_json = json.dumps(
        {"nickname": "NewUser", "email": "*****@*****.**", "password": "******"}
    )
    c.post("/users", new_user_json, status=201)

    response = c.get("/users/4/confirm/" + "Im5ld.WrongToken.jFaRE", status=302)

    flash = (
        urlsafe_b64encode(b"The confirmation link is invalid or has been expired")
        .replace(b"=", b"")
        .decode("utf-8")
    )
    assert "302 Found" in response.text
    assert "flash=" + flash in response.text
    assert "flashtype=error" in response.text

    response = c.get(
        "/users/4/confirm/"
        + "Im5ld3VzZXJAZXhhbXBsZS5jb20i.C3dnsw.2meomRPK3wnYwB2AERt2ygjFaRE",
        status=302,
    )

    flash = (
        urlsafe_b64encode(b"Thank you for confirming your email address")
        .replace(b"=", b"")
        .decode("utf-8")
    )
    assert "302 Found" in response.text
    assert "flash=" + flash in response.text
    assert "flashtype=success" in response.text

    response = c.get(
        "/users/4/confirm/"
        + "Im5ld3VzZXJAZXhhbXBsZS5jb20i.C3dnsw.2meomRPK3wnYwB2AERt2ygjFaRE",
        status=302,
    )

    flash = (
        urlsafe_b64encode(b"Your email is already confirmed. Please log in.")
        .replace(b"=", b"")
        .decode("utf-8")
    )
    assert "302 Found" in response.text
    assert "flash=" + flash in response.text
    assert "flashtype=info" in response.text
Beispiel #16
0
def test_delete_user():
    c = Client(App())

    response = c.post(
        "/login", json.dumps({"email": "*****@*****.**", "password": "******"})
    )

    headers = {"Authorization": response.headers["Authorization"]}

    with db_session:
        assert User.exists(nickname="Mary")

    c.delete("/users/2", headers=headers)

    with db_session:
        assert not User.exists(nickname="Mary")
Beispiel #17
0
def test_user_collection_combined_query():
    c = Client(App())

    response = c.post(
        "/login", json.dumps({"email": "*****@*****.**", "password": "******"})
    )

    headers = {"Authorization": response.headers["Authorization"]}

    response = c.get(
        "/users?search=example.com&sortby=nickname&page=1&pagesize=2", headers=headers
    )

    assert len(response.json["users"]) == 2
    assert response.json["pages"] == 2
    assert response.json["users"][0]["nickname"] == "Jürgen"
Beispiel #18
0
def test_update_password(smtp):
    c = Client(App(), extra_environ=dict(REMOTE_ADDR="127.0.0.1"))

    new_user_json = json.dumps(
        {"nickname": "NewUser", "email": "*****@*****.**", "password": "******"}
    )
    c.post("/users", new_user_json, status=201)
    assert len(smtp.outbox) == 1

    update_password_json = json.dumps({"password": "******"})
    response = c.put(
        "/users/4/reset/"
        + "Im5ld3VzZXJAZXhhbXBsZS5jb20i.WrongToken.JrUGhlAxao46VsQevI",
        update_password_json,
        status=403,
    )

    assert response.json == {
        "validationError": "The password reset link is invalid or has been expired"
    }

    response = c.put(
        "/users/4/reset/"
        + "Im5ld3VzZXJAZXhhbXBsZS5jb20i.C40RUQ.5JhlEE36_JrUGhlAxao46VsQevI",
        update_password_json,
        status=403,
    )

    assert response.json == {
        "validationError": "Your email must be confirmed before resetting the password"
    }

    c.get(
        "/users/4/confirm/"
        + "Im5ld3VzZXJAZXhhbXBsZS5jb20i.C3dnsw.2meomRPK3wnYwB2AERt2ygjFaRE",
        status=302,
    )

    response = c.put(
        "/users/4/reset/"
        + "Im5ld3VzZXJAZXhhbXBsZS5jb20i.C40RUQ.5JhlEE36_JrUGhlAxao46VsQevI",
        update_password_json,
    )

    ph = PasswordHasher()
    with db_session:
        assert ph.verify(User[4].password, "new_secret")
Beispiel #19
0
def test_refresh_token():
    app = App()
    c = Client(app)

    response = c.post(
        "/login", json.dumps({"email": "*****@*****.**", "password": "******"})
    )

    headers = {"Authorization": response.headers["Authorization"]}

    response = c.get("/refresh", headers=headers)

    jwtauth_settings = app.settings.jwtauth.__dict__.copy()
    identity_policy = JWTIdentityPolicy(**jwtauth_settings)

    authtype, token = response.headers["Authorization"].split(" ", 1)
    claims_set_decoded = identity_policy.decode_jwt(token)

    assert identity_policy.get_userid(claims_set_decoded) == "*****@*****.**"

    with db_session:
        # set new nonce to invalid current tokens for this user
        User[2].nonce = uuid4().hex

    response = c.get("/refresh", headers=headers, status=403)
    assert response.json == {"validationError": "Could not refresh your token"}

    now = timegm(datetime.utcnow().utctimetuple())

    with db_session:
        nonce = User.get(email="*****@*****.**").nonce

    claims_set = {
        "sub": "*****@*****.**",
        "uid": "/users/2",
        "refresh_until": now - 3,
        "nonce": nonce,
        "exp": now + 3,
    }

    token = identity_policy.encode_jwt(claims_set)
    headers = {"Authorization": "JWT " + token}

    response = c.get("/refresh", headers=headers, status=403)
    assert response.json == {"validationError": "Your session has expired"}
Beispiel #20
0
def test_paginated_user_collection():
    c = Client(App())

    response = c.post(
        "/login", json.dumps({"email": "*****@*****.**", "password": "******"})
    )

    headers = {"Authorization": response.headers["Authorization"]}

    response = c.get("/users?page=1&pagesize=2", headers=headers)

    assert len(response.json["users"]) == 2
    assert response.json["pages"] == 2

    response = c.get("/users?page=2&pagesize=2", headers=headers)

    assert len(response.json["users"]) == 1
    assert response.json["pages"] == 2
Beispiel #21
0
def test_delete_group():
    c = Client(App())

    response = c.post(
        "/login",
        json.dumps({
            "email": "*****@*****.**",
            "password": "******"
        }))

    headers = {"Authorization": response.headers["Authorization"]}

    with db_session:
        assert Group.exists(name="Moderator")

    c.delete("/groups/2", headers=headers)

    with db_session:
        assert not Group.exists(name="Moderator")
Beispiel #22
0
def test_groups_collection():
    c = Client(App())

    response = c.post(
        "/login",
        json.dumps({
            "email": "*****@*****.**",
            "password": "******"
        }))

    headers = {"Authorization": response.headers["Authorization"]}

    response = c.get("/groups", headers=headers)
    group_1 = {
        "@id": "/groups/1",
        "name": "Admin",
        "users": ["*****@*****.**"]
    }
    assert_dict_contains_subset(group_1, response.json["groups"][0])
Beispiel #23
0
def test_login():
    app = App()
    c = Client(app)

    response = c.post(
        "/login",
        json.dumps({"email": "*****@*****.**", "password": "******"}),
        status=403,
    )
    assert response.json == {"validationError": "Invalid email or password"}

    response = c.post(
        "/login",
        json.dumps({"email": "*****@*****.**", "password": "******"}),
        status=403,
    )
    assert response.json == {"validationError": "Invalid email or password"}

    response = c.post(
        "/login",
        json.dumps({"email": "test@example", "password": "******"}),
        status=403,
    )
    assert response.json == {"validationError": "Invalid email or password"}

    response = c.post(
        "/login",
        json.dumps({"email": "*****@*****.**", "password": "******"}),
        status=422,
    )
    assert response.json == {"password": ["min length is 5"]}

    response = c.post(
        "/login", json.dumps({"email": "*****@*****.**", "password": "******"})
    )

    jwtauth_settings = app.settings.jwtauth.__dict__.copy()
    identity_policy = JWTIdentityPolicy(**jwtauth_settings)

    authtype, token = response.headers["Authorization"].split(" ", 1)
    claims_set_decoded = identity_policy.decode_jwt(token)

    assert identity_policy.get_userid(claims_set_decoded) == "*****@*****.**"
Beispiel #24
0
def test_user_collection():
    c = Client(App())

    response = c.post(
        "/login", json.dumps({"email": "*****@*****.**", "password": "******"})
    )

    headers = {"Authorization": response.headers["Authorization"]}

    response = c.get("/users", headers=headers)
    user_1 = {
        "@id": "/users/1",
        "nickname": "Leader",
        "email": "*****@*****.**",
        "emailConfirmed": False,
        "isAdmin": True,
        "registerIP": "",
    }

    assert_dict_contains_subset(user_1, response.json["users"][0])

    user_2 = {
        "@id": "/users/2",
        "nickname": "Mary",
        "email": "*****@*****.**",
        "emailConfirmed": False,
        "isAdmin": False,
        "registerIP": "",
    }

    assert_dict_contains_subset(user_2, response.json["users"][1])

    user_3 = {
        "@id": "/users/3",
        "nickname": "Jürgen",
        "email": "*****@*****.**",
        "emailConfirmed": False,
        "isAdmin": False,
        "registerIP": "",
    }

    assert_dict_contains_subset(user_3, response.json["users"][2])
Beispiel #25
0
def test_user():
    c = Client(App())

    response = c.post(
        "/login", json.dumps({"email": "*****@*****.**", "password": "******"})
    )

    headers = {"Authorization": response.headers["Authorization"]}

    response = c.get("/users/2", headers=headers)
    user = {
        "@id": "/users/2",
        "nickname": "Mary",
        "email": "*****@*****.**",
        "isAdmin": False,
        "emailConfirmed": False,
        "registerIP": "",
    }

    assert_dict_contains_subset(user, response.json)
Beispiel #26
0
def test_search_user_collection():
    c = Client(App())

    response = c.post(
        "/login", json.dumps({"email": "*****@*****.**", "password": "******"})
    )

    headers = {"Authorization": response.headers["Authorization"]}

    response = c.get("/users?search=" + urllib.parse.quote("jürgen"), headers=headers)

    assert len(response.json["users"]) == 1
    assert response.json["users"][0]["nickname"] == "Jürgen"

    response = c.get("/[email protected]", headers=headers)

    assert len(response.json["users"]) == 1
    assert response.json["users"][0]["nickname"] == "Leader"

    response = c.get("/users?search=example.com", headers=headers)

    assert len(response.json["users"]) == 3
Beispiel #27
0
def test_add_user(smtp):
    assert len(smtp.outbox) == 0

    c = Client(App(), extra_environ=dict(REMOTE_ADDR="127.0.0.1"))

    new_user_json = json.dumps(
        {"nickname": "NewUser", "email": "*****@*****.**", "password": "******"}
    )

    response = c.post("/users", new_user_json, status=201)
    with db_session:
        assert User.exists(nickname="NewUser")
        assert User.get(nickname="NewUser").register_ip == "127.0.0.1"

    assert len(smtp.outbox) == 1
    message = smtp.outbox[0]
    assert message["subject"] == "Confirm Your Email Address"
    assert message["To"] == "*****@*****.**"

    response = c.post("/users", new_user_json, status=409)
    assert response.json == {"validationError": "Email already exists"}

    assert len(smtp.outbox) == 1

    with db_session:
        new_editor_json = json.dumps(
            {
                "nickname": "NewEditor",
                "email": "*****@*****.**",
                "password": "******",
                "groups": ["Editor"],
            }
        )
        c.post("/users", new_editor_json)

        assert User.exists(nickname="NewEditor")
        assert Group.get(name="Editor") in User.get(nickname="NewEditor").groups
        assert User.get(nickname="NewEditor").email == "*****@*****.**"

    assert len(smtp.outbox) == 2
    message = smtp.outbox[1]
    assert message["subject"] == "Confirm Your Email Address"
    assert message["To"] == "*****@*****.**"

    new_user_json = json.dumps(
        {
            "nickname": "NewUser",
            "email": "*****@*****.**",
            "password": "******",
        }
    )

    response = c.post("/users", new_user_json, status=422)
    assert response.json == {"email": ["Email could not be delivered"]}

    new_user_json = json.dumps(
        {"nickname": "NewUser", "email": "newuser@example", "password": "******"}
    )

    response = c.post("/users", new_user_json, status=422)
    assert response.json == {"email": ["Not valid email"]}

    new_user_json = json.dumps(
        {
            "nickname": 123,
            "email": "*****@*****.**",
            "password": "******",
        }
    )

    response = c.post("/users", new_user_json, status=422)
    assert response.json == {"nickname": ["must be of string type"]}
def test_root():
    c = Client(App())

    response = c.get("/")
    assert response.json == {"collection": "http://localhost/todos"}
Beispiel #29
0
from server import App, BaseView
import utils
import asyncio
import os
import datetime
import serializers
from bson import ObjectId
from settings import db, host, port

app = App()


@app.route('/login/')
class LoginView(BaseView):
    @asyncio.coroutine
    def post(self):
        '''do the login :)'''
        email = self.request.data.get('email')
        password = self.request.data.get('password')

        user = yield from db.users.find_one({'email': email})

        if user:
            password_hash = yield from utils.get_password_hash(
                user['salt'], password.encode())
            if user['password'] == password_hash:
                user['last_login'] = datetime.datetime.now()
                yield from db.users.save(user)

                user['token'] = yield from utils.generate_token(user)
                yield from serializers.user(user)
Beispiel #30
0
from server import App, BaseView
import utils
import asyncio
import os
import datetime
import serializers
from bson import ObjectId
from settings import db, host, port


app = App()


@app.route('/login/')
class LoginView(BaseView):
    @asyncio.coroutine
    def post(self):
        '''do the login :)'''
        email = self.request.data.get('email')
        password = self.request.data.get('password')

        user = yield from db.users.find_one({
            'email': email
        })

        if user:
            password_hash = yield from utils.get_password_hash(
                user['salt'],
                password.encode()
            )
            if user['password'] == password_hash:
Beispiel #31
0
def test_update_user(smtp):
    c = Client(App())

    response = c.post(
        "/login", json.dumps({"email": "*****@*****.**", "password": "******"})
    )

    headers = {"Authorization": response.headers["Authorization"]}

    update_user_json = json.dumps({"nickname": "Guru"})
    c.put("/users/1", update_user_json, headers=headers)

    with db_session:
        assert User[1].nickname == "Guru"

    update_user_json = json.dumps({"nickname": "Guru"})
    c.put("/users/1", update_user_json, headers=headers)

    with db_session:
        assert User[1].nickname == "Guru"

    update_user_json = json.dumps({"email": "guru@example"})
    response = c.put("/users/1", update_user_json, headers=headers, status=422)
    assert response.json == {"email": ["Not valid email"]}

    update_user_json = json.dumps({"email": "*****@*****.**"})
    c.put("/users/2", update_user_json, headers=headers)

    with db_session:
        assert User[2].email == "*****@*****.**"

    assert len(smtp.outbox) == 1
    message = smtp.outbox[0]
    assert message["subject"] == "Confirm Your Email Address"
    assert message["To"] == "*****@*****.**"

    response = c.put("/users/2", update_user_json, headers=headers, status=409)
    assert response.json == {"validationError": "Email already exists"}
    assert len(smtp.outbox) == 1

    update_user_json = json.dumps({"password": "******"})
    c.put("/users/1", update_user_json, headers=headers)

    ph = PasswordHasher()
    with db_session:
        assert ph.verify(User[1].password, "secret0")

    update_user_json = json.dumps({"groups": ["Moderator"]})
    c.put("/users/3", update_user_json, headers=headers)

    with db_session:
        u = User[3]
        editor = Group.get(name="Editor")
        moderator = Group.get(name="Moderator")

        assert editor not in u.groups
        assert moderator in u.groups

    update_user_json = json.dumps({"groups": []})
    c.put("/users/3", update_user_json, headers=headers)

    with db_session:
        u = User[3]
        editor = Group.get(name="Editor")
        moderator = Group.get(name="Moderator")

        assert editor not in u.groups
        assert moderator not in u.groups