149 lines
4.3 KiB
Python
149 lines
4.3 KiB
Python
from uuid import uuid4
|
|
|
|
import pytest
|
|
from sqlalchemy.exc import IntegrityError
|
|
|
|
from app import create_app
|
|
from app.auth.models import User
|
|
from app.auth.services import create_user
|
|
from app.extensions import db as database
|
|
from app.tasks.services import add_subtask, configure_focus, create_task
|
|
|
|
|
|
@pytest.fixture()
|
|
def admin_app():
|
|
application = create_app("testing", {"FEATURE_ADMIN": True})
|
|
with application.app_context():
|
|
database.create_all()
|
|
yield application
|
|
database.session.remove()
|
|
database.drop_all()
|
|
|
|
|
|
@pytest.fixture()
|
|
def admin_client(admin_app):
|
|
return admin_app.test_client()
|
|
|
|
|
|
def login(client, email: str) -> None:
|
|
client.post(
|
|
"/auth/login",
|
|
data={"email": email, "password": "secure password"},
|
|
)
|
|
|
|
|
|
def user_with_role(username: str, role: str) -> User:
|
|
user = create_user(
|
|
username,
|
|
f"{username}@example.com",
|
|
"secure password",
|
|
)
|
|
user.role = role
|
|
database.session.commit()
|
|
return user
|
|
|
|
|
|
def test_admin_feature_is_absent_by_default(app, client):
|
|
endpoints = {rule.endpoint for rule in app.url_map.iter_rules()}
|
|
|
|
assert "admin.index" not in endpoints
|
|
assert client.get("/admin/").status_code == 404
|
|
|
|
|
|
def test_enabled_admin_redirects_anonymous_users(admin_client):
|
|
response = admin_client.get("/admin/")
|
|
|
|
assert response.status_code == 302
|
|
assert "/auth/login" in response.headers["Location"]
|
|
|
|
|
|
@pytest.mark.parametrize("role", ["user", "viewer", "coach"])
|
|
def test_non_admin_roles_receive_calm_forbidden_page(admin_client, role):
|
|
user = user_with_role(f"non_admin_{role}", role)
|
|
login(admin_client, user.email)
|
|
|
|
response = admin_client.get("/admin/")
|
|
|
|
assert response.status_code == 403
|
|
assert b"not available for your role" in response.data
|
|
|
|
|
|
def test_admin_can_open_data_free_placeholder(admin_client):
|
|
user = user_with_role("admin_user", "admin")
|
|
login(admin_client, user.email)
|
|
|
|
response = admin_client.get("/admin/")
|
|
|
|
assert response.status_code == 200
|
|
assert b"User management" in response.data
|
|
assert b"Analytics" in response.data
|
|
assert response.data.count(b"Not implemented") == 2
|
|
assert b'href="/admin/"' in response.data
|
|
|
|
|
|
def test_database_rejects_unknown_roles(admin_app):
|
|
user = User(
|
|
username="invalid_role",
|
|
email="invalid-role@example.com",
|
|
role="superuser",
|
|
)
|
|
user.set_password("secure password")
|
|
database.session.add(user)
|
|
|
|
with pytest.raises(IntegrityError):
|
|
database.session.commit()
|
|
database.session.rollback()
|
|
|
|
|
|
def test_viewer_can_read_but_every_task_mutation_is_forbidden(client, db):
|
|
viewer = user_with_role("readonly_user", "viewer")
|
|
task = create_task(
|
|
viewer.id,
|
|
title="Readable task",
|
|
description=None,
|
|
status="not_started",
|
|
priority="normal",
|
|
due_date=None,
|
|
)
|
|
subtask = add_subtask(task, "Readable step")
|
|
configure_focus(task, timer_minutes=5, context=None, chunk_minutes=[5])
|
|
login(client, viewer.email)
|
|
|
|
assert client.get("/tasks/").status_code == 200
|
|
assert client.get(f"/tasks/{task.id}").status_code == 200
|
|
assert client.get("/tasks/export/json").status_code == 200
|
|
|
|
operations = [
|
|
("get", "/tasks/new"),
|
|
("get", f"/tasks/{task.id}/edit"),
|
|
("post", f"/tasks/{task.id}/delete"),
|
|
("post", f"/tasks/{task.id}/subtasks"),
|
|
("post", f"/tasks/{task.id}/subtasks/{subtask.id}/toggle"),
|
|
("post", f"/tasks/{task.id}/focus-settings"),
|
|
("post", f"/tasks/{task.id}/chunks/0/toggle"),
|
|
("post", f"/tasks/{task.id}/complete"),
|
|
("post", "/tasks/import/markdown"),
|
|
("post", "/tasks/import/json"),
|
|
("post", "/tasks/completed/clear"),
|
|
("post", f"/tasks/completed/undo/{uuid4()}"),
|
|
]
|
|
for method, path in operations:
|
|
response = getattr(client, method)(path)
|
|
assert response.status_code == 403, path
|
|
|
|
|
|
def test_coach_retains_owned_task_write_access(client, db):
|
|
coach = user_with_role("coach_user", "coach")
|
|
login(client, coach.email)
|
|
|
|
response = client.post(
|
|
"/tasks/new",
|
|
data={
|
|
"title": "Coach-owned task",
|
|
"status": "not_started",
|
|
"priority": "normal",
|
|
"due_date": "",
|
|
},
|
|
)
|
|
|
|
assert response.status_code == 302
|