Initial config with email service test
Build LinkLog Development Image / development-image (push) Successful in 11s
Build LinkLog Development Image / development-image (push) Successful in 11s
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
## Copyright © 2026 Olaf Kolkman
|
||||
## SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
TEST_DATABASE_DIRECTORY = tempfile.TemporaryDirectory(prefix='linklog-tests-')
|
||||
TEST_DATABASE_PATH = os.path.join(TEST_DATABASE_DIRECTORY.name, 'linklog.db')
|
||||
os.environ['LINKLOG_DATABASE_PATH'] = TEST_DATABASE_PATH
|
||||
|
||||
|
||||
@pytest.fixture(scope='session', autouse=True)
|
||||
def test_users():
|
||||
from backend.app.database import get_connection, hash_password, init_db
|
||||
|
||||
init_db()
|
||||
with get_connection() as conn:
|
||||
conn.executemany(
|
||||
'''INSERT INTO users
|
||||
(id, username, email, password_hash, is_admin, email_verified)
|
||||
VALUES (?, ?, ?, ?, ?, 1)''',
|
||||
[
|
||||
('user-1', 'alice', 'alice@example.com', hash_password('secret123'), 1),
|
||||
('user-2', 'bob', 'bob@example.com', hash_password('secret123'), 0),
|
||||
],
|
||||
)
|
||||
conn.commit()
|
||||
yield
|
||||
TEST_DATABASE_DIRECTORY.cleanup()
|
||||
@@ -5,10 +5,13 @@ import json
|
||||
import threading
|
||||
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
||||
from uuid import uuid4
|
||||
from unittest.mock import patch
|
||||
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from backend.app.main import app
|
||||
from backend.app.database import get_connection
|
||||
from backend.app.services.password_reset import create_reset_token
|
||||
from backend.app.services.token_service import issue_token
|
||||
|
||||
|
||||
@@ -110,6 +113,49 @@ def test_email_verification_link_enables_login():
|
||||
assert client.get('/api/auth/verify-email', params={'token': verification_token}).status_code == 400
|
||||
|
||||
|
||||
def test_mistyped_password_sends_reset_link_without_changing_login_error():
|
||||
username = f'mistyped-{uuid4().hex}'
|
||||
admin_headers = {'Authorization': f"Bearer {issue_token('user-1', 'alice')['access_token']}"}
|
||||
created = client.post('/api/admin/users', headers=admin_headers, json={
|
||||
'username': username,
|
||||
'email': f'{username}@example.com',
|
||||
'password': 'secret123',
|
||||
})
|
||||
user_id = created.json()['id']
|
||||
with get_connection() as conn:
|
||||
conn.execute('UPDATE users SET email_verified = 1 WHERE id = ?', (user_id,))
|
||||
conn.commit()
|
||||
with patch('backend.app.api.auth.smtp_configured', return_value=True), \
|
||||
patch('backend.app.api.auth.create_reset_token', return_value='reset-token') as create_token, \
|
||||
patch('backend.app.api.auth.send_password_reset_email') as send_email:
|
||||
response = client.post('/api/auth/login', json={'username': username, 'password': 'wrong-password'})
|
||||
|
||||
assert response.status_code == 401
|
||||
assert response.json()['detail'] == 'Invalid username or password'
|
||||
create_token.assert_called_once_with(user_id)
|
||||
send_email.assert_called_once()
|
||||
assert send_email.call_args.args[2].endswith('/reset-password?token=reset-token')
|
||||
|
||||
|
||||
def test_password_reset_is_single_use_and_revokes_sessions():
|
||||
username = f'reset-owner-{uuid4().hex}'
|
||||
admin_headers = {'Authorization': f"Bearer {issue_token('user-1', 'alice')['access_token']}"}
|
||||
created = client.post('/api/admin/users', headers=admin_headers, json={
|
||||
'username': username,
|
||||
'email': f'{username}@example.com',
|
||||
'password': 'secret123',
|
||||
})
|
||||
user_id = created.json()['id']
|
||||
with get_connection() as conn:
|
||||
conn.execute('UPDATE users SET email_verified = 1 WHERE id = ?', (user_id,))
|
||||
conn.commit()
|
||||
token = create_reset_token(user_id)
|
||||
reset = client.post('/api/auth/reset-password', json={'token': token, 'password': 'new-secret123'})
|
||||
assert reset.status_code == 200
|
||||
assert client.post('/api/auth/reset-password', json={'token': token, 'password': 'another-secret'}).status_code == 400
|
||||
assert client.post('/api/auth/login', json={'username': username, 'password': 'new-secret123'}).status_code == 200
|
||||
|
||||
|
||||
def test_admin_can_remove_user_with_owned_data():
|
||||
headers = login_headers()
|
||||
username = f'data-owner-{uuid4().hex}'
|
||||
|
||||
@@ -10,7 +10,7 @@ def test_database_migrations_are_versioned_and_idempotent():
|
||||
connection = sqlite3.connect(':memory:')
|
||||
|
||||
apply_migrations(connection)
|
||||
assert get_schema_version(connection) == 6
|
||||
assert get_schema_version(connection) == 7
|
||||
tables = {
|
||||
row[0]
|
||||
for row in connection.execute(
|
||||
@@ -27,6 +27,6 @@ def test_database_migrations_are_versioned_and_idempotent():
|
||||
assert set(DEFAULT_TAGS) <= seeded_tags
|
||||
|
||||
apply_migrations(connection)
|
||||
assert get_schema_version(connection) == 6
|
||||
assert get_schema_version(connection) == 7
|
||||
|
||||
connection.close()
|
||||
Reference in New Issue
Block a user