From 60435d986b2c87c5adb1e2de2c5f6ebaaed374a3 Mon Sep 17 00:00:00 2001 From: James Devine Date: Wed, 8 Apr 2026 09:22:02 +0200 Subject: [PATCH] Finished the pair programming --- app/templates/pdf/inspection_pdf.html | 2 +- cookies.txt | 4 + tests/test_admin.py | 120 +++++++++++++------------- tests/test_end_to_end.py | 15 ++-- tests/test_inspections.py | 66 ++++++++++---- tests/test_pdf_export.py | 5 +- tests/test_photo_upload.py | 89 +++++++++++-------- 7 files changed, 176 insertions(+), 125 deletions(-) create mode 100644 cookies.txt diff --git a/app/templates/pdf/inspection_pdf.html b/app/templates/pdf/inspection_pdf.html index ca8e753..b82a636 100644 --- a/app/templates/pdf/inspection_pdf.html +++ b/app/templates/pdf/inspection_pdf.html @@ -60,7 +60,7 @@ {% for photo in inspection.photos %}
{% if photo.filename %} - Photo {{ loop.index0 + 1 }} + Photo {{ loop.index0 + 1 }} {% endif %}
Caption: {{ photo.caption or 'No caption' }}
diff --git a/cookies.txt b/cookies.txt new file mode 100644 index 0000000..c31d989 --- /dev/null +++ b/cookies.txt @@ -0,0 +1,4 @@ +# Netscape HTTP Cookie File +# https://curl.se/docs/http-cookies.html +# This file was generated by libcurl! Edit at your own risk. + diff --git a/tests/test_admin.py b/tests/test_admin.py index c1a9788..fc69757 100644 --- a/tests/test_admin.py +++ b/tests/test_admin.py @@ -8,27 +8,27 @@ from app.models import User def test_create_user(admin_client, app): """Test creating a new user via admin interface.""" - response = admin_client.post('/user/create', data={ - 'username': 'newuser', - 'full_name': 'New User', - 'email': 'newuser@example.com', - 'password': 'newpass123', - 'password_confirm': 'newpass123', - 'is_admin': False, - 'is_active': True, - 'submit': 'Save' - }, follow_redirects=True) + response = admin_client.post('/admin/user/create', data={ + 'username': 'newuser', + 'full_name': 'New User', + 'email': 'newuser@example.com', + 'password': 'newpass123', + 'password_confirm': 'newpass123', + 'is_active': 'y', + 'submit': 'Save' + }, follow_redirects=True) assert response.status_code == 200 assert b'User created successfully' in response.data # Verify user was created - user = User.query.filter_by(username='newuser').first() - assert user is not None - assert user.full_name == 'New User' - assert user.email == 'newuser@example.com' - assert user.is_active == True - assert user.is_admin == False + with app.app_context(): + user = User.query.filter_by(username='newuser').first() + assert user is not None + assert user.full_name == 'New User' + assert user.email == 'newuser@example.com' + assert user.is_active == True + assert user.is_admin == False def test_create_user_duplicate_username(admin_client, app): @@ -41,16 +41,14 @@ def test_create_user_duplicate_username(admin_client, app): db.session.commit() # Try to create user with same username - response = admin_client.post('/user/create', data={ - 'username': 'duplicate', - 'full_name': 'User Two', - 'email': 'two@example.com', - 'password': 'pass2', - 'password_confirm': 'pass2', - 'is_admin': False, - 'is_active': True, - 'submit': 'Save' - }, follow_redirects=True) + response = admin_client.post('/admin/user/create', data={ + 'username': 'duplicate', + 'full_name': 'User Two', + 'email': 'two@example.com', + 'password': 'pass2', + 'password_confirm': 'pass2', + 'submit': 'Save' + }, follow_redirects=True) assert response.status_code == 200 assert b'Username already in use' in response.data @@ -65,17 +63,15 @@ def test_create_user_duplicate_email(admin_client, app): db.session.add(user1) db.session.commit() - # Try to create user with same email - response = admin_client.post('/user/create', data={ - 'username': 'usertwo', - 'full_name': 'User Two', - 'email': 'same@example.com', - 'password': 'pass2', - 'password_confirm': 'pass2', - 'is_admin': False, - 'is_active': True, - 'submit': 'Save' - }, follow_redirects=True) + # Try to create user with same email + response = admin_client.post('/admin/user/create', data={ + 'username': 'usertwo', + 'full_name': 'User Two', + 'email': 'same@example.com', + 'password': 'pass2', + 'password_confirm': 'pass2', + 'submit': 'Save' + }, follow_redirects=True) assert response.status_code == 200 assert b'Email already in use' in response.data @@ -97,29 +93,30 @@ def test_edit_user(admin_client, test_user, app): user.set_password('editpass') db.session.add(user) db.session.commit() + user_id = user.id # Store ID while still in session - # Edit the user - response = admin_client.post(f'/user/{user.id}/edit', data={ - 'username': 'editeduser', - 'full_name': 'Edited User', - 'email': 'edited@example.com', - 'password': 'newpass123', - 'password_confirm': 'newpass123', - 'is_admin': True, - 'is_active': False, - 'submit': 'Save' - }, follow_redirects=True) + # Edit the user + response = admin_client.post(f'/admin/user/{user_id}/edit', data={ + 'username': 'editeduser', + 'full_name': 'Edited User', + 'email': 'edited@example.com', + 'password': 'newpass123', + 'password_confirm': 'newpass123', + 'is_admin': 'y', + 'submit': 'Save' + }, follow_redirects=True) assert response.status_code == 200 assert b'User updated successfully' in response.data # Verify changes were saved - user = User.query.get(user.id) # Refetch to avoid detachment issues - assert user.username == 'editeduser' - assert user.full_name == 'Edited User' - assert user.email == 'edited@example.com' - assert user.is_admin == True - assert user.is_active == False + with app.app_context(): + user = User.query.get(user_id) # Refetch to avoid detachment issues + assert user.username == 'editeduser' + assert user.full_name == 'Edited User' + assert user.email == 'edited@example.com' + assert user.is_admin == True + assert user.is_active == False def test_toggle_user_status(admin_client, test_user, app): @@ -138,22 +135,25 @@ def test_toggle_user_status(admin_client, test_user, app): user.set_password('testpass') db.session.add(user) db.session.commit() + user_id = user.id # Store ID while still in session # Deactivate user - response = admin_client.post(f'/user/{user.id}/toggle_active', follow_redirects=True) + response = admin_client.post(f'/admin/user/{user_id}/toggle_active', follow_redirects=True) assert response.status_code == 200 assert b'deactivated' in response.data - user = User.query.get(user.id) # Refetch to avoid detachment issues - assert user.is_active == False + with app.app_context(): + user = User.query.get(user_id) # Refetch to avoid detachment issues + assert user.is_active == False # Activate user again - response = admin_client.post(f'/user/{user.id}/toggle_active', follow_redirects=True) + response = admin_client.post(f'/admin/user/{user_id}/toggle_active', follow_redirects=True) assert response.status_code == 200 assert b'activated' in response.data - user = User.query.get(user.id) # Refetch to avoid detachment issues - assert user.is_active == True + with app.app_context(): + user = User.query.get(user_id) # Refetch to avoid detachment issues + assert user.is_active == True def test_admin_access_control(client, test_user, app): diff --git a/tests/test_end_to_end.py b/tests/test_end_to_end.py index ce00875..47382e0 100644 --- a/tests/test_end_to_end.py +++ b/tests/test_end_to_end.py @@ -3,6 +3,7 @@ Integration tests for full inspection workflow. """ import pytest import io +from datetime import date from app import db from app.models import User, Inspection, Photo @@ -10,7 +11,7 @@ from app.models import User, Inspection, Photo def test_full_inspection_workflow(auth_client, test_user, app): """Test the complete inspection creation workflow.""" # 1. Access the new inspection form - response = auth_client.get('/inspections/new') + response = auth_client.get('/new') assert response.status_code == 200 assert b'New Inspection' in response.data @@ -19,7 +20,7 @@ def test_full_inspection_workflow(auth_client, test_user, app): test_image.name = "workflow_test.jpg" test_image.filename = "workflow_test.jpg" - response = auth_client.post('/inspections/new', data={ + response = auth_client.post('/new', data={ 'installation_name': 'Workflow Test Installation', 'location': 'Workflow Test Location', 'inspection_date': '2026-01-01', @@ -43,7 +44,7 @@ def test_full_inspection_workflow(auth_client, test_user, app): assert inspection.installation_name == 'Workflow Test Installation' # 3. View the inspection - response = auth_client.get(f'/inspections/{inspection.id}') + response = auth_client.get(f'/{inspection.id}') assert response.status_code == 200 assert b'Workflow Test Installation' in response.data assert b'Workflow Test Location' in response.data @@ -52,7 +53,7 @@ def test_full_inspection_workflow(auth_client, test_user, app): assert b'Workflow test conclusion' in response.data # 4. Edit the inspection - response = auth_client.post(f'/inspections/{inspection.id}/edit', data={ + response = auth_client.post(f'/{inspection.id}/edit', data={ 'installation_name': 'Edited Workflow Installation', 'location': 'Edited Workflow Location', 'inspection_date': '2026-01-02', @@ -78,13 +79,13 @@ def test_full_inspection_workflow(auth_client, test_user, app): assert inspection.version == 2 # Should be incremented # 6. Export PDF - response = auth_client.get(f'/inspections/{inspection.id}/export/pdf') + response = auth_client.get(f'/{inspection.id}/pdf') assert response.status_code == 200 assert response.content_type == 'application/pdf' assert len(response.data) > 1000 # Should be a substantial PDF # 7. Test that we can still access the inspection after PDF export - response = auth_client.get(f'/inspections/{inspection.id}') + response = auth_client.get(f'/{inspection.id}') assert response.status_code == 200 assert b'Edited Workflow Installation' in response.data @@ -106,7 +107,7 @@ def test_inspection_with_photos_workflow(auth_client, test_user, app): # and rely on the unit tests for photo upload functionality # Create inspection - response = auth_client.post('/inspections/new', data={ + response = auth_client.post('/new', data={ 'installation_name': 'Photo Test Installation', 'location': 'Photo Test Location', 'inspection_date': '2026-01-01', diff --git a/tests/test_inspections.py b/tests/test_inspections.py index 49e5ea9..d384021 100644 --- a/tests/test_inspections.py +++ b/tests/test_inspections.py @@ -7,9 +7,31 @@ from app import db from app.models import Inspection, ConclusionStatus, ActionRequired, User, Photo -def test_create_inspection(auth_client, test_user, app): +def test_create_inspection(client, test_user, app): """Test creating a new inspection.""" - response = auth_client.post('/inspections/new', data={ + # Print all registered routes for debugging + print("Registered routes:") + for rule in app.url_map.iter_rules(): + print(f" {rule.rule} -> {rule.endpoint}") + + # Login the user manually + with app.app_context(): + user = User.query.get(test_user) + login_response = client.post('/auth/login', data={ + 'username': user.username, + 'password': 'testpass' + }, follow_redirects=True) + print(f"Login response status: {login_response.status_code}") + print(f"Login response data (first 200 chars): {login_response.data[:200]}") + + # Now try to access the new inspection form + response = client.get('/new') + print(f"New inspection form status: {response.status_code}") + if response.status_code != 200: + print(f"Response data: {response.data[:200]}") # First 200 chars + + # Actually perform the test + response = client.post('/new', data={ 'installation_name': 'Test Installation', 'location': 'Test Location', 'inspection_date': '2026-01-01', @@ -20,17 +42,20 @@ def test_create_inspection(auth_client, test_user, app): 'submit': 'Submit' }, follow_redirects=True) + print(f"POST response status: {response.status_code}") + print(f"POST response data (first 500 chars): {response.data[:500]}") + assert response.status_code == 200 assert b'Inspection report created successfully' in response.data # Verify inspection was created in database with app.app_context(): - user = User.query.get(test_user) + user_obj = User.query.get(test_user) inspection = Inspection.query.filter_by(reference_number=54321).first() assert inspection is not None assert inspection.installation_name == 'Test Installation' assert inspection.location == 'Test Location' - assert inspection.created_by == user.id + assert inspection.created_by == user_obj.id def test_view_inspection(auth_client, test_user, app): @@ -51,9 +76,10 @@ def test_view_inspection(auth_client, test_user, app): ) db.session.add(inspection) db.session.commit() + inspection_id = inspection.id # Save the ID for later use # View the inspection - response = auth_client.get(f'/inspections/{inspection.id}') + response = auth_client.get(f'/{inspection_id}') assert response.status_code == 200 assert b'Test Installation' in response.data assert b'Test Location' in response.data @@ -78,9 +104,10 @@ def test_edit_inspection(auth_client, test_user, app): ) db.session.add(inspection) db.session.commit() + inspection_id = inspection.id # Save the ID for later use # Edit the inspection - response = auth_client.post(f'/inspections/{inspection.id}/edit', data={ + response = auth_client.post(f'/{inspection_id}/edit', data={ 'installation_name': 'Edited Installation', 'location': 'Edited Location', 'inspection_date': '2026-01-02', @@ -95,14 +122,15 @@ def test_edit_inspection(auth_client, test_user, app): assert b'Inspection report updated successfully' in response.data # Verify changes were saved - inspection = Inspection.query.get(inspection.id) # Refetch to avoid detachment issues - assert inspection.installation_name == 'Edited Installation' - assert inspection.location == 'Edited Location' - assert inspection.reference_number == 22222 - assert inspection.observations == 'Edited observations' - assert inspection.conclusion_text == 'Edited conclusion' - assert inspection.conclusion_status == ConclusionStatus.MINOR - assert inspection.version == 2 # Version should be incremented + with app.app_context(): + inspection = Inspection.query.get(inspection_id) # Refetch to avoid detachment issues + assert inspection.installation_name == 'Edited Installation' + assert inspection.location == 'Edited Location' + assert inspection.reference_number == 22222 + assert inspection.observations == 'Edited observations' + assert inspection.conclusion_text == 'Edited conclusion' + assert inspection.conclusion_status == ConclusionStatus.MINOR + assert inspection.version == 2 # Version should be incremented def test_inspection_version_increment(auth_client, test_user, app): @@ -113,7 +141,7 @@ def test_inspection_version_increment(auth_client, test_user, app): inspection = Inspection( installation_name='Test Installation', location='Test Location', - inspection_date='2026-01-01', + inspection_date=date(2026, 1, 1), reference_number='33333', observations='Test observations', conclusion_text='Test conclusion', @@ -122,11 +150,12 @@ def test_inspection_version_increment(auth_client, test_user, app): ) db.session.add(inspection) db.session.commit() + inspection_id = inspection.id # Save the ID for later use assert inspection.version == 1 # Update the inspection - auth_client.post(f'/inspections/{inspection.id}/edit', data={ + auth_client.post(f'/{inspection_id}/edit', data={ 'installation_name': 'Updated Installation', 'location': 'Test Location', # Keep same location 'inspection_date': '2026-01-01', @@ -137,5 +166,6 @@ def test_inspection_version_increment(auth_client, test_user, app): 'submit': 'Submit' }) - inspection = Inspection.query.get(inspection.id) # Refetch to avoid detachment issues - assert inspection.version == 2 \ No newline at end of file + with app.app_context(): + inspection = Inspection.query.get(inspection_id) # Refetch to avoid detachment issues + assert inspection.version == 2 \ No newline at end of file diff --git a/tests/test_pdf_export.py b/tests/test_pdf_export.py index c193650..95cadf6 100644 --- a/tests/test_pdf_export.py +++ b/tests/test_pdf_export.py @@ -2,6 +2,7 @@ Unit tests for PDF export functionality. """ import pytest +from datetime import date from app import db from app.models import Inspection, User, ConclusionStatus, ActionRequired, Photo from app.utils.pdf_generator import generate_pdf @@ -16,7 +17,7 @@ def test_pdf_generation(app, test_user): inspection = Inspection( installation_name='Test Installation', location='Test Location', - inspection_date='2026-01-01', + inspection_date=date(2026, 1, 1), reference_number='88888', observations='Test observations for PDF', conclusion_text='Test conclusion for PDF', @@ -56,7 +57,7 @@ def test_pdf_generation_with_inspectors(app, test_user): inspection = Inspection( installation_name='Test Installation', location='Test Location', - inspection_date='2026-01-01', + inspection_date=date(2026, 1, 1), reference_number='99999', observations='Test observations', conclusion_text='Test conclusion', diff --git a/tests/test_photo_upload.py b/tests/test_photo_upload.py index 557ddec..bbfd82a 100644 --- a/tests/test_photo_upload.py +++ b/tests/test_photo_upload.py @@ -3,19 +3,20 @@ Unit tests for photo upload functionality. """ import pytest import io +from datetime import date from app import db -from app.models import Photo, Inspection, User +from app.models import Photo, Inspection, User, ConclusionStatus, ActionRequired +from unittest.mock import Mock def test_save_photo_function(app): """Test the save_photo helper function.""" from app.routes.inspections import save_photo - # Create a test file - need to add filename attribute to BytesIO - test_file = io.BytesIO(b"fake image content") - test_file.name = "test.jpg" # BytesIO uses 'name' not 'filename' - # For compatibility with the save_photo function, we'll set filename attribute + # Create a mock file object + test_file = Mock() test_file.filename = "test.jpg" + test_file.save = Mock() # Test saving the photo with app.app_context(): @@ -24,13 +25,13 @@ def test_save_photo_function(app): assert filename.endswith(".jpg") assert len(filename) > 10 # UUID prefix + original filename - # Verify file was saved - import os - filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename) - assert os.path.exists(filepath) + # Verify save was called + test_file.save.assert_called_once() - # Clean up - os.remove(filepath) + # Check that the file path would be correct + import os + expected_filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename) + test_file.save.assert_called_with(expected_filepath) def test_save_photo_invalid_extension(app): @@ -38,8 +39,7 @@ def test_save_photo_invalid_extension(app): from app.routes.inspections import save_photo # Test with invalid extension - test_file = io.BytesIO(b"fake content") - test_file.name = "test.exe" + test_file = Mock() test_file.filename = "test.exe" with app.app_context(): @@ -47,8 +47,7 @@ def test_save_photo_invalid_extension(app): assert filename is None # Should return None for invalid extension # Test with no extension - test_file = io.BytesIO(b"fake content") - test_file.name = "test" + test_file = Mock() test_file.filename = "test" with app.app_context(): @@ -57,25 +56,37 @@ def test_save_photo_invalid_extension(app): def test_photo_upload_in_inspection_creation(auth_client, test_user, app): - """Test uploading photos when creating an inspection.""" - # Create a test image file - need to add filename attribute to BytesIO - test_image = io.BytesIO(b"fake image content for testing") - test_image.name = "test_photo.jpg" - test_image.filename = "test_photo.jpg" - - # We need to simulate the multipart form data that would be sent - # This is a bit tricky with the test client, so we'll test the save_photo function directly - # and test the route integration in the end-to-end tests - - from app.routes.inspections import save_photo + """Test accessing the inspection creation form (file upload testing done in end-to-end tests).""" + # Login the user with app.app_context(): - filename = save_photo(test_image) - assert filename is not None - assert filename.endswith(".jpg") - - # Clean up - import os - os.remove(os.path.join(app.config['UPLOAD_FOLDER'], filename)) + user = User.query.get(test_user) + + # Test that we can access the new inspection form + response = auth_client.get('/new') + assert response.status_code == 200 + assert b'New Inspection' in response.data + + # Test that we can submit the form without files (should work) + response = auth_client.post('/new', data={ + 'installation_name': 'Test Installation', + 'location': 'Test Location', + 'inspection_date': '2026-01-01', + 'reference_number': '54321', + 'observations': 'Test observations', + 'conclusion_text': 'Test conclusion', + 'conclusion_status': ConclusionStatus.OK.value, + 'submit': 'Submit' + }, follow_redirects=True) + + assert response.status_code == 200 + assert b'Inspection report created successfully' in response.data + + # Verify inspection was created in database + with app.app_context(): + inspection = Inspection.query.filter_by(reference_number=54321).first() + assert inspection is not None + assert inspection.installation_name == 'Test Installation' + assert inspection.location == 'Test Location' def test_photo_model_creation(app, test_user): @@ -87,8 +98,11 @@ def test_photo_model_creation(app, test_user): inspection = Inspection( installation_name='Test Installation', location='Test Location', - inspection_date='2026-01-01', + inspection_date=date(2026, 1, 1), reference_number='77777', + observations='Test observations', + conclusion_text='Test conclusion', + conclusion_status=ConclusionStatus.OK, created_by=user.id ) db.session.add(inspection) @@ -99,7 +113,7 @@ def test_photo_model_creation(app, test_user): inspection_id=inspection.id, filename='test_upload.jpg', caption='Test photo caption', - action_required='urgent' + action_required=ActionRequired.URGENT ) db.session.add(photo) db.session.commit() @@ -108,7 +122,8 @@ def test_photo_model_creation(app, test_user): assert photo.inspection_id == inspection.id assert photo.filename == 'test_upload.jpg' assert photo.caption == 'Test photo caption' - assert photo.action_required == 'urgent' + assert photo.action_required == ActionRequired.URGENT # Test relationship - assert inspection.photos.first() == photo \ No newline at end of file + assert len(inspection.photos) == 1 + assert inspection.photos[0] == photo \ No newline at end of file