Files
TimeTracker/tests/test_client_notes_routes.py
Dries Peeters 90dde470da style: standardize code formatting and normalize line endings
- Normalize line endings from CRLF to LF across all files to match .editorconfig
- Standardize quote style from single quotes to double quotes
- Normalize whitespace and formatting throughout codebase
- Apply consistent code style across 372 files including:
  * Application code (models, routes, services, utils)
  * Test files
  * Configuration files
  * CI/CD workflows

This ensures consistency with the project's .editorconfig settings and
improves code maintainability.
2025-11-28 20:05:37 +01:00

402 lines
14 KiB
Python

"""
Test suite for client notes routes and endpoints.
Tests all client note CRUD operations and API endpoints.
"""
import pytest
import json
from app.models import ClientNote
from app import db
# ============================================================================
# Client Notes Routes Tests
# ============================================================================
@pytest.mark.integration
@pytest.mark.routes
@pytest.mark.smoke
def test_create_client_note(authenticated_client, test_client, user, app):
"""Test creating a client note."""
with app.app_context():
response = authenticated_client.post(
f"/clients/{test_client.id}/notes/create",
data={"content": "This is a test note", "is_important": "false"},
follow_redirects=False,
)
# Should redirect back to client view
assert response.status_code == 302
assert f"/clients/{test_client.id}" in response.location
# Verify note was created
note = ClientNote.query.filter_by(client_id=test_client.id).first()
assert note is not None
assert note.content == "This is a test note"
assert note.is_important is False
@pytest.mark.integration
@pytest.mark.routes
def test_create_important_client_note(authenticated_client, test_client, user, app):
"""Test creating an important client note."""
with app.app_context():
response = authenticated_client.post(
f"/clients/{test_client.id}/notes/create",
data={"content": "Important note", "is_important": "true"},
follow_redirects=False,
)
assert response.status_code == 302
# Verify note was created with important flag
note = ClientNote.query.filter_by(client_id=test_client.id).first()
assert note is not None
assert note.content == "Important note"
assert note.is_important is True
@pytest.mark.integration
@pytest.mark.routes
def test_create_note_empty_content_fails(authenticated_client, test_client, app):
"""Test that creating a note with empty content fails."""
with app.app_context():
response = authenticated_client.post(
f"/clients/{test_client.id}/notes/create",
data={"content": "", "is_important": "false"},
follow_redirects=True,
)
# Should show error and redirect back
assert response.status_code == 200
# Verify no note was created
note_count = ClientNote.query.filter_by(client_id=test_client.id).count()
assert note_count == 0
@pytest.mark.integration
@pytest.mark.routes
def test_create_note_invalid_client_fails(authenticated_client, app):
"""Test that creating a note for non-existent client fails."""
with app.app_context():
response = authenticated_client.post(
"/clients/99999/notes/create",
data={"content": "Test note", "is_important": "false"},
follow_redirects=False,
)
# Should return 404
assert response.status_code == 404
@pytest.mark.integration
@pytest.mark.routes
def test_edit_client_note_page(authenticated_client, test_client, user, app):
"""Test accessing the edit client note page."""
with app.app_context():
# Create a note
note = ClientNote(content="Original note", user_id=user.id, client_id=test_client.id)
db.session.add(note)
db.session.commit()
note_id = note.id
# Access edit page
response = authenticated_client.get(f"/clients/{test_client.id}/notes/{note_id}/edit")
assert response.status_code == 200
assert b"Edit Client Note" in response.data or b"edit" in response.data.lower()
@pytest.mark.integration
@pytest.mark.routes
def test_edit_client_note_submit(authenticated_client, test_client, user, app):
"""Test editing a client note."""
with app.app_context():
# Create a note
note = ClientNote(content="Original note", user_id=user.id, client_id=test_client.id, is_important=False)
db.session.add(note)
db.session.commit()
note_id = note.id
# Edit the note
response = authenticated_client.post(
f"/clients/{test_client.id}/notes/{note_id}/edit",
data={"content": "Updated note content", "is_important": "true"},
follow_redirects=False,
)
assert response.status_code == 302
assert f"/clients/{test_client.id}" in response.location
# Verify note was updated
updated_note = ClientNote.query.get(note_id)
assert updated_note.content == "Updated note content"
assert updated_note.is_important is True
@pytest.mark.integration
@pytest.mark.routes
def test_edit_note_permission_denied(authenticated_client, test_client, user, admin_user, app):
"""Test that users cannot edit notes they don't own (unless admin)."""
with app.app_context():
# Create a note by admin
note = ClientNote(content="Admin note", user_id=admin_user.id, client_id=test_client.id)
db.session.add(note)
db.session.commit()
note_id = note.id
# Regular user tries to edit (should fail if not the owner)
# This test assumes the route checks permissions
response = authenticated_client.post(
f"/clients/{test_client.id}/notes/{note_id}/edit", data={"content": "Hacked content"}, follow_redirects=True
)
# Note: This may pass if the authenticated_client is an admin
# For a proper test, we'd need a fixture for a non-admin authenticated client
@pytest.mark.integration
@pytest.mark.routes
def test_delete_client_note(authenticated_client, test_client, user, app):
"""Test deleting a client note."""
with app.app_context():
# Create a note
note = ClientNote(content="Note to delete", user_id=user.id, client_id=test_client.id)
db.session.add(note)
db.session.commit()
note_id = note.id
# Delete the note
response = authenticated_client.post(
f"/clients/{test_client.id}/notes/{note_id}/delete", follow_redirects=False
)
assert response.status_code == 302
assert f"/clients/{test_client.id}" in response.location
# Verify note was deleted
deleted_note = ClientNote.query.get(note_id)
assert deleted_note is None
@pytest.mark.integration
@pytest.mark.routes
def test_delete_nonexistent_note_fails(authenticated_client, test_client, app):
"""Test that deleting a non-existent note fails."""
with app.app_context():
response = authenticated_client.post(f"/clients/{test_client.id}/notes/99999/delete", follow_redirects=False)
# Should return 404
assert response.status_code == 404
@pytest.mark.integration
@pytest.mark.routes
@pytest.mark.api
def test_toggle_important_note(authenticated_client, test_client, user, app):
"""Test toggling the important flag on a note."""
with app.app_context():
# Create a note
note = ClientNote(content="Test note", user_id=user.id, client_id=test_client.id, is_important=False)
db.session.add(note)
db.session.commit()
note_id = note.id
# Toggle to important
response = authenticated_client.post(
f"/clients/{test_client.id}/notes/{note_id}/toggle-important", content_type="application/json"
)
assert response.status_code == 200
data = response.get_json()
assert data["success"] is True
assert data["is_important"] is True
# Verify in database
updated_note = ClientNote.query.get(note_id)
assert updated_note.is_important is True
# Toggle back to not important
response = authenticated_client.post(
f"/clients/{test_client.id}/notes/{note_id}/toggle-important", content_type="application/json"
)
assert response.status_code == 200
data = response.get_json()
assert data["is_important"] is False
# ============================================================================
# Client Notes API Tests
# ============================================================================
@pytest.mark.integration
@pytest.mark.routes
@pytest.mark.api
def test_list_client_notes_api(authenticated_client, test_client, user, app):
"""Test getting all notes for a client via API."""
with app.app_context():
# Create multiple notes
note1 = ClientNote(content="First note", user_id=user.id, client_id=test_client.id, is_important=False)
note2 = ClientNote(content="Second note", user_id=user.id, client_id=test_client.id, is_important=True)
db.session.add_all([note1, note2])
db.session.commit()
# Get notes via API
response = authenticated_client.get(f"/api/clients/{test_client.id}/notes")
assert response.status_code == 200
data = response.get_json()
assert data["success"] is True
assert len(data["notes"]) == 2
@pytest.mark.integration
@pytest.mark.routes
@pytest.mark.api
def test_list_client_notes_api_ordered_by_important(authenticated_client, test_client, user, app):
"""Test getting notes ordered by importance via API."""
with app.app_context():
# Create multiple notes
note1 = ClientNote(content="Regular note", user_id=user.id, client_id=test_client.id, is_important=False)
note2 = ClientNote(content="Important note", user_id=user.id, client_id=test_client.id, is_important=True)
db.session.add_all([note1, note2])
db.session.commit()
# Get notes ordered by importance
response = authenticated_client.get(f"/api/clients/{test_client.id}/notes?order_by_important=true")
assert response.status_code == 200
data = response.get_json()
assert data["success"] is True
# First note should be the important one
assert data["notes"][0]["is_important"] is True
@pytest.mark.integration
@pytest.mark.routes
@pytest.mark.api
def test_get_single_note_api(authenticated_client, test_client, user, app):
"""Test getting a single note via API."""
with app.app_context():
# Create a note
note = ClientNote(content="Test note", user_id=user.id, client_id=test_client.id)
db.session.add(note)
db.session.commit()
note_id = note.id
# Get note via API
response = authenticated_client.get(f"/api/client-notes/{note_id}")
assert response.status_code == 200
data = response.get_json()
assert data["success"] is True
assert data["note"]["id"] == note_id
assert data["note"]["content"] == "Test note"
@pytest.mark.integration
@pytest.mark.routes
@pytest.mark.api
def test_get_important_notes_api(authenticated_client, test_client, user, app):
"""Test getting all important notes via API."""
with app.app_context():
# Create notes
note1 = ClientNote(content="Regular note", user_id=user.id, client_id=test_client.id, is_important=False)
note2 = ClientNote(content="Important note 1", user_id=user.id, client_id=test_client.id, is_important=True)
note3 = ClientNote(content="Important note 2", user_id=user.id, client_id=test_client.id, is_important=True)
db.session.add_all([note1, note2, note3])
db.session.commit()
# Get important notes
response = authenticated_client.get("/api/client-notes/important")
assert response.status_code == 200
data = response.get_json()
assert data["success"] is True
assert len(data["notes"]) == 2
assert all(note["is_important"] for note in data["notes"])
@pytest.mark.integration
@pytest.mark.routes
@pytest.mark.api
def test_get_recent_notes_api(authenticated_client, test_client, user, app):
"""Test getting recent notes via API."""
with app.app_context():
# Create multiple notes
for i in range(5):
note = ClientNote(content=f"Note {i}", user_id=user.id, client_id=test_client.id)
db.session.add(note)
db.session.commit()
# Get recent notes with limit
response = authenticated_client.get("/api/client-notes/recent?limit=3")
assert response.status_code == 200
data = response.get_json()
assert data["success"] is True
assert len(data["notes"]) == 3
@pytest.mark.integration
@pytest.mark.routes
@pytest.mark.api
def test_get_user_notes_api(authenticated_client, test_client, user, app):
"""Test getting notes by a specific user via API."""
with app.app_context():
# Create notes by user
for i in range(3):
note = ClientNote(content=f"User note {i}", user_id=user.id, client_id=test_client.id)
db.session.add(note)
db.session.commit()
# Get user's notes
response = authenticated_client.get(f"/api/client-notes/user/{user.id}")
assert response.status_code == 200
data = response.get_json()
assert data["success"] is True
assert len(data["notes"]) == 3
# ============================================================================
# Client View Integration Tests
# ============================================================================
@pytest.mark.integration
@pytest.mark.routes
def test_client_view_shows_notes(authenticated_client, test_client, user, app):
"""Test that client view page shows notes."""
with app.app_context():
# Create a note
note = ClientNote(content="Visible note", user_id=user.id, client_id=test_client.id)
db.session.add(note)
db.session.commit()
# View client page
response = authenticated_client.get(f"/clients/{test_client.id}")
assert response.status_code == 200
# Check that notes section is present
assert b"Internal Notes" in response.data or b"notes" in response.data.lower()
@pytest.mark.integration
@pytest.mark.routes
def test_unauthenticated_user_cannot_access_notes(client, test_client, app):
"""Test that unauthenticated users cannot access note routes."""
with app.app_context():
# Try to create a note
response = client.post(
f"/clients/{test_client.id}/notes/create", data={"content": "Unauthorized note"}, follow_redirects=False
)
# Should redirect to login
assert response.status_code == 302
assert "login" in response.location.lower()