Label edit functionality added
Build LinkLog Development Image / development-image (push) Successful in 10s
Build LinkLog Development Image / development-image (push) Successful in 10s
This commit is contained in:
+87
-10
@@ -11,7 +11,7 @@ from unittest.mock import MagicMock, patch
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from backend.app.main import app
|
||||
from backend.app.database import get_connection
|
||||
from backend.app.database import get_connection, hash_password
|
||||
from backend.app.services.email_service import get_smtp_settings
|
||||
from backend.app.services.login_throttle import clear_login_failures
|
||||
from backend.app.services.otp_service import current_code
|
||||
@@ -490,25 +490,102 @@ def test_admin_can_toggle_privileges_without_removing_last_admin():
|
||||
assert last_admin.status_code == 400
|
||||
|
||||
|
||||
def test_users_manage_owned_labels_and_admin_can_delete_any_label():
|
||||
alice_headers = login_headers('alice')
|
||||
created = client.post('/api/user/labels', headers=alice_headers, json={'name': 'My Label'})
|
||||
def test_users_manage_owned_labels_and_admin_can_edit_and_delete_any_label():
|
||||
alice_headers = login_headers('alice') # admin
|
||||
bob_headers = login_headers('bob') # non-admin
|
||||
|
||||
created = client.post('/api/user/labels', headers=bob_headers, json={'name': 'Bob Label'})
|
||||
assert created.status_code == 201
|
||||
label = created.json()
|
||||
assert label['name'] == '#My Label'
|
||||
assert label['name'] == '#Bob Label'
|
||||
|
||||
edited = client.put(f"/api/user/labels/{label['id']}", headers=alice_headers, json={'name': '#Renamed'})
|
||||
# Bob renames own label
|
||||
edited = client.put(f"/api/user/labels/{label['id']}", headers=bob_headers, json={'name': '#RenamedByBob'})
|
||||
assert edited.status_code == 200
|
||||
assert edited.json()['name'] == '#Renamed'
|
||||
assert edited.json()['name'] == '#RenamedByBob'
|
||||
|
||||
denied = client.put(f"/api/user/labels/{label['id']}", headers=login_headers('bob'), json={'name': '#Nope'})
|
||||
assert denied.status_code == 404
|
||||
assert client.delete(f"/api/user/labels/{label['id']}", headers=login_headers('bob')).status_code == 404
|
||||
# Non-owner Alice can edit it via admin endpoint, but NOT user endpoint
|
||||
user_denied = client.put(f"/api/user/labels/{label['id']}", headers=alice_headers, json={'name': '#NopeUser'})
|
||||
assert user_denied.status_code == 404
|
||||
|
||||
admin_edited = client.put(f"/api/admin/labels/{label['id']}", headers=alice_headers, json={'name': '#AdminRenamed'})
|
||||
assert admin_edited.status_code == 200
|
||||
assert admin_edited.json()['name'] == '#AdminRenamed'
|
||||
|
||||
# Non-admin Bob cannot access admin edit endpoint
|
||||
bob_admin_denied = client.put(f"/api/admin/labels/{label['id']}", headers=bob_headers, json={'name': '#NopeAdmin'})
|
||||
assert bob_admin_denied.status_code == 403
|
||||
|
||||
# Admin delete
|
||||
admin_delete = client.delete(f"/api/admin/labels/{label['id']}", headers=alice_headers)
|
||||
assert admin_delete.status_code == 200
|
||||
|
||||
|
||||
def test_label_visibility_isolation_and_grandfathering():
|
||||
alice_headers = login_headers('alice')
|
||||
bob_headers = login_headers('bob')
|
||||
|
||||
# Create non-admin user charlie
|
||||
with get_connection() as conn:
|
||||
conn.execute(
|
||||
'''INSERT OR IGNORE INTO users (id, username, email, password_hash, is_admin, email_verified)
|
||||
VALUES (?, ?, ?, ?, 0, 1)''',
|
||||
('user-3', 'charlie', 'charlie@example.com', hash_password('secret123')),
|
||||
)
|
||||
conn.commit()
|
||||
|
||||
charlie_headers = login_headers('charlie')
|
||||
|
||||
# Create Bob label (non-admin)
|
||||
created_bob = client.post('/api/user/labels', headers=bob_headers, json={'name': 'BobOnlyLabel'}).json()
|
||||
# Create Charlie label (non-admin)
|
||||
created_charlie = client.post('/api/user/labels', headers=charlie_headers, json={'name': 'CharlieOnlyLabel'}).json()
|
||||
|
||||
# Grandfathered label in DB with NULL created_by
|
||||
from uuid import uuid4
|
||||
grandfathered_id = str(uuid4())
|
||||
with get_connection() as conn:
|
||||
conn.execute('INSERT INTO tags (id, name, created_by) VALUES (?, ?, NULL)', (grandfathered_id, '#GrandfatheredLabel'))
|
||||
conn.commit()
|
||||
|
||||
# Bob views /api/user/labels: sees default tags, grandfathered tag, and Bob tag, NOT Charlie tag
|
||||
bob_labels = client.get('/api/user/labels', headers=bob_headers).json()
|
||||
bob_label_names = [l['name'] for l in bob_labels]
|
||||
assert '#BobOnlyLabel' in bob_label_names
|
||||
assert '#GrandfatheredLabel' in bob_label_names
|
||||
assert '#Cybersecurity' in bob_label_names
|
||||
assert '#CharlieOnlyLabel' not in bob_label_names
|
||||
|
||||
# Charlie views /api/user/labels: sees default tags, grandfathered tag, and Charlie tag, NOT Bob tag
|
||||
charlie_labels = client.get('/api/user/labels', headers=charlie_headers).json()
|
||||
charlie_label_names = [l['name'] for l in charlie_labels]
|
||||
assert '#CharlieOnlyLabel' in charlie_label_names
|
||||
assert '#GrandfatheredLabel' in charlie_label_names
|
||||
assert '#Cybersecurity' in charlie_label_names
|
||||
assert '#BobOnlyLabel' not in charlie_label_names
|
||||
|
||||
# Bob views /api/tags (authenticated): sees Bob tag & default/grandfathered, NOT Charlie tag
|
||||
bob_tags = client.get('/api/tags', headers=bob_headers).json()
|
||||
assert '#BobOnlyLabel' in bob_tags
|
||||
assert '#GrandfatheredLabel' in bob_tags
|
||||
assert '#CharlieOnlyLabel' not in bob_tags
|
||||
|
||||
# Anonymous views /api/tags: sees default/grandfathered, NOT Bob or Charlie tag
|
||||
anon_tags = client.get('/api/tags').json()
|
||||
assert '#GrandfatheredLabel' in anon_tags
|
||||
assert '#BobOnlyLabel' not in anon_tags
|
||||
assert '#CharlieOnlyLabel' not in anon_tags
|
||||
|
||||
# Bob cannot edit or delete grandfathered label
|
||||
assert client.put(f"/api/user/labels/{grandfathered_id}", headers=bob_headers, json={'name': '#RenamedGrandfathered'}).status_code == 404
|
||||
assert client.delete(f"/api/user/labels/{grandfathered_id}", headers=bob_headers).status_code == 404
|
||||
|
||||
# Clean up created labels
|
||||
client.delete(f"/api/user/labels/{created_bob['id']}", headers=bob_headers)
|
||||
client.delete(f"/api/user/labels/{created_charlie['id']}", headers=charlie_headers)
|
||||
client.delete(f"/api/admin/labels/{grandfathered_id}", headers=alice_headers)
|
||||
|
||||
|
||||
def test_labels_page_renders_authenticated_management_shell():
|
||||
page = client.get('/labels')
|
||||
assert page.status_code == 200
|
||||
|
||||
Reference in New Issue
Block a user