From 5d4099422cc2ae7b0b1b4567a84b40b6db096ac3 Mon Sep 17 00:00:00 2001 From: Dries Peeters Date: Sun, 25 Jan 2026 08:59:47 +0100 Subject: [PATCH 1/2] fix: resolve migration multiple heads and flake8 F821/F823 - Add merge migration 116_merge_three_heads to join heads 090_add_push_subscriptions, 100_gantt_colors_modules, and 115_add_exclude_weekends so 'flask db upgrade' runs cleanly. - Fix undefined-name and scope issues for CI code-quality: - api_v1: import InvalidOperation in amount_paid Decimal block - custom_reports, invoice_approvals, reports, scheduled_reports, tasks: add current_app to Flask imports - integrations: set user_integration before POST branches so update_config path can use it - invoices: use _ign for unpacking in export_invoice_ubl to avoid shadowing gettext _ (F823) - data_import, excel_export: add module logger (logging.getLogger) --- app/routes/api_v1.py | 2 +- app/routes/custom_reports.py | 2 +- app/routes/integrations.py | 2 ++ app/routes/invoice_approvals.py | 2 +- app/routes/invoices.py | 4 +-- app/routes/reports.py | 2 +- app/routes/scheduled_reports.py | 2 +- app/routes/tasks.py | 2 +- app/utils/data_import.py | 3 ++ app/utils/excel_export.py | 3 ++ migrations/versions/116_merge_three_heads.py | 29 ++++++++++++++++++++ 11 files changed, 45 insertions(+), 8 deletions(-) create mode 100644 migrations/versions/116_merge_three_heads.py diff --git a/app/routes/api_v1.py b/app/routes/api_v1.py index d0852a4a..8aa03e97 100644 --- a/app/routes/api_v1.py +++ b/app/routes/api_v1.py @@ -1536,7 +1536,7 @@ def update_invoice(invoice_id): current_app.logger.warning(f"Invalid tax_rate value in invoice update: {data.get('tax_rate')} - {e}") if "amount_paid" in data: try: - from decimal import Decimal + from decimal import Decimal, InvalidOperation update_kwargs["amount_paid"] = Decimal(str(data["amount_paid"])) except (ValueError, TypeError, InvalidOperation) as e: diff --git a/app/routes/custom_reports.py b/app/routes/custom_reports.py index 7e3a3d42..9f46317c 100644 --- a/app/routes/custom_reports.py +++ b/app/routes/custom_reports.py @@ -2,7 +2,7 @@ Routes for custom report builder. """ -from flask import Blueprint, render_template, request, redirect, url_for, flash, jsonify +from flask import Blueprint, render_template, request, redirect, url_for, flash, jsonify, current_app from flask_babel import gettext as _ from flask_login import login_required, current_user from app import db diff --git a/app/routes/integrations.py b/app/routes/integrations.py index 9683ae37..c55361bd 100644 --- a/app/routes/integrations.py +++ b/app/routes/integrations.py @@ -317,6 +317,8 @@ class MinimalConnector: # Per-user integration integration = Integration.query.filter_by(provider=provider, user_id=current_user.id, is_global=False).first() + user_integration = None if is_global else integration + # Handle POST (OAuth credential updates - admin only for global integrations) if request.method == "POST": if is_global and not current_user.is_admin: diff --git a/app/routes/invoice_approvals.py b/app/routes/invoice_approvals.py index 070d9cd6..42caa1c4 100644 --- a/app/routes/invoice_approvals.py +++ b/app/routes/invoice_approvals.py @@ -2,7 +2,7 @@ Routes for invoice approval workflow. """ -from flask import Blueprint, render_template, request, redirect, url_for, flash, jsonify +from flask import Blueprint, render_template, request, redirect, url_for, flash, jsonify, current_app from flask_babel import gettext as _ from flask_login import login_required, current_user from app.models import Invoice, InvoiceApproval, User diff --git a/app/routes/invoices.py b/app/routes/invoices.py index fa4896c7..61db731f 100644 --- a/app/routes/invoices.py +++ b/app/routes/invoices.py @@ -1073,8 +1073,8 @@ def export_invoice_ubl(invoice_id): svc = PeppolService() sender = svc._get_sender_party() - recipient_party, _, _ = svc._get_recipient_party(invoice) - ubl_xml, _ = build_peppol_ubl_invoice_xml(invoice=invoice, supplier=sender, customer=recipient_party) + recipient_party, _ign, _ign = svc._get_recipient_party(invoice) + ubl_xml, _ign = build_peppol_ubl_invoice_xml(invoice=invoice, supplier=sender, customer=recipient_party) fn = f"invoice_{invoice.invoice_number}.xml" return Response(ubl_xml, mimetype="application/xml", headers={"Content-Disposition": f"attachment; filename={fn}"}) except ValueError as e: diff --git a/app/routes/reports.py b/app/routes/reports.py index bb17fbec..c7b872f5 100644 --- a/app/routes/reports.py +++ b/app/routes/reports.py @@ -1,4 +1,4 @@ -from flask import Blueprint, render_template, request, redirect, url_for, flash, send_file, jsonify +from flask import Blueprint, render_template, request, redirect, url_for, flash, send_file, jsonify, current_app from flask_login import login_required, current_user from flask_babel import _ from app import db, log_event, track_event diff --git a/app/routes/scheduled_reports.py b/app/routes/scheduled_reports.py index 80264a57..7e76c103 100644 --- a/app/routes/scheduled_reports.py +++ b/app/routes/scheduled_reports.py @@ -2,7 +2,7 @@ Routes for scheduled reports management. """ -from flask import Blueprint, render_template, request, redirect, url_for, flash, jsonify +from flask import Blueprint, render_template, request, redirect, url_for, flash, jsonify, current_app from flask_babel import gettext as _ from flask_login import login_required, current_user from app.models import SavedReportView, ReportEmailSchedule diff --git a/app/routes/tasks.py b/app/routes/tasks.py index 614b4c04..fb124741 100644 --- a/app/routes/tasks.py +++ b/app/routes/tasks.py @@ -1,6 +1,6 @@ import re -from flask import Blueprint, render_template, request, redirect, url_for, flash, jsonify, make_response, Response +from flask import Blueprint, render_template, request, redirect, url_for, flash, jsonify, make_response, Response, current_app from flask_babel import gettext as _ from flask_login import login_required, current_user import app as app_module diff --git a/app/utils/data_import.py b/app/utils/data_import.py index e00c48ba..648c75e8 100644 --- a/app/utils/data_import.py +++ b/app/utils/data_import.py @@ -4,6 +4,7 @@ import json import csv +import logging import requests from datetime import datetime, timedelta from io import StringIO @@ -12,6 +13,8 @@ from app.models import User, Project, TimeEntry, Task, Client, Expense, ExpenseCategory, Contact from app.utils.db import safe_commit +logger = logging.getLogger(__name__) + class ImportError(Exception): """Custom exception for import errors""" diff --git a/app/utils/excel_export.py b/app/utils/excel_export.py index 87c5fbff..7ca4ade8 100644 --- a/app/utils/excel_export.py +++ b/app/utils/excel_export.py @@ -1,12 +1,15 @@ """Excel export utilities for reports and data export""" import io +import logging from datetime import datetime from openpyxl import Workbook from openpyxl.styles import Font, Alignment, PatternFill, Border, Side from openpyxl.utils import get_column_letter from app.utils.timezone import convert_app_datetime_to_user +logger = logging.getLogger(__name__) + def create_time_entries_excel(entries, filename_prefix="timetracker_export"): """Create Excel file from time entries diff --git a/migrations/versions/116_merge_three_heads.py b/migrations/versions/116_merge_three_heads.py new file mode 100644 index 00000000..e2b1e42b --- /dev/null +++ b/migrations/versions/116_merge_three_heads.py @@ -0,0 +1,29 @@ +"""Merge three migration heads into one + +Revision ID: 116_merge_three_heads +Revises: 090_add_push_subscriptions, 100_gantt_colors_modules, 115_add_exclude_weekends +Create Date: 2026-01-25 + +Merge revision to resolve multiple heads: +- 090_add_push_subscriptions +- 100_gantt_colors_modules +- 115_add_exclude_weekends +""" +from alembic import op + + +# revision identifiers, used by Alembic. +revision = '116_merge_three_heads' +down_revision = ('090_add_push_subscriptions', '100_gantt_colors_modules', '115_add_exclude_weekends') +branch_labels = None +depends_on = None + + +def upgrade(): + """No schema changes - merge only.""" + pass + + +def downgrade(): + """No schema changes - merge only.""" + pass From d1b7e47835d512f6954b066228aad27bbf1634d3 Mon Sep 17 00:00:00 2001 From: Dries Peeters Date: Sun, 25 Jan 2026 08:59:54 +0100 Subject: [PATCH 2/2] fix(tests): resolve smoke test failures (audit, session, PDF) - Audit: remove session.flush() from after_flush handler to avoid 'Session is already flushing' when logging creates - Audit smoke tests: make assertions robust to fixture-created logs (filter by user_id/action, expect >= counts where appropriate) - PDF preview: add id and client to mock invoice SimpleNamespace and use getattr(invoice, 'id', None) in exception logging - PDF layout tests: assert custom_css is contained in saved CSS (app normalizes with @page); create template before preview test - Session: add password to login data in smoke tests that use the login endpoint (admin_users, permissions_routes, tasks_templates, time_entry_resume, invoices) so sessions persist across requests --- app/routes/admin.py | 9 ++++++--- app/utils/audit.py | 3 --- tests/test_admin_users.py | 24 ++++++++++++------------ tests/test_audit_trail_smoke.py | 23 +++++++++++++++-------- tests/test_invoices.py | 6 +++--- tests/test_pdf_layout.py | 20 +++++++++++++++----- tests/test_permissions_routes.py | 26 +++++++++++++------------- tests/test_tasks_templates.py | 12 ++++++++---- tests/test_time_entry_resume.py | 2 +- 9 files changed, 73 insertions(+), 52 deletions(-) diff --git a/app/routes/admin.py b/app/routes/admin.py index 7d49d4f5..1d16db23 100644 --- a/app/routes/admin.py +++ b/app/routes/admin.py @@ -2148,6 +2148,7 @@ def pdf_layout_preview(): from datetime import date invoice = SimpleNamespace( + id=None, invoice_number="0000", issue_date=date.today(), due_date=date.today(), @@ -2155,6 +2156,7 @@ def pdf_layout_preview(): client_name="Sample Client", client_email="", client_address="", + client=SimpleNamespace(name="Sample Client", email="", address=""), project=SimpleNamespace(name="Sample Project", description=""), items=[], extra_goods=[], @@ -2209,16 +2211,17 @@ def pdf_layout_preview(): pass # Copy relationship attributes (project, client) + _invoice_id = getattr(invoice, "id", None) try: invoice_wrapper.project = invoice.project except (AttributeError, RuntimeError) as e: - current_app.logger.debug(f"Could not access invoice.project for invoice {invoice.id}: {e}") + current_app.logger.debug(f"Could not access invoice.project for invoice {_invoice_id}: {e}") invoice_wrapper.project = SimpleNamespace(name="Sample Project", description="") try: - invoice_wrapper.client = invoice.client + invoice_wrapper.client = getattr(invoice, "client", None) except (AttributeError, RuntimeError) as e: - current_app.logger.debug(f"Could not access invoice.client for invoice {invoice.id}: {e}") + current_app.logger.debug(f"Could not access invoice.client for invoice {_invoice_id}: {e}") invoice_wrapper.client = None # Convert items from Query to list diff --git a/app/utils/audit.py b/app/utils/audit.py index aa92fcad..30a54f9e 100644 --- a/app/utils/audit.py +++ b/app/utils/audit.py @@ -408,9 +408,6 @@ def receive_after_flush(session, flush_context): request_path=request_path, ) - if pending: - session.flush() - except Exception as e: logger.error(f"Error in audit logging (after_flush): {e}", exc_info=True) diff --git a/tests/test_admin_users.py b/tests/test_admin_users.py index 54a94d9d..77fc64e5 100644 --- a/tests/test_admin_users.py +++ b/tests/test_admin_users.py @@ -22,7 +22,7 @@ class TestAdminUserList: def test_list_users_as_admin(self, client, admin_user): """Test that admin can view user list.""" # Login as admin using the login endpoint - client.post("/login", data={"username": admin_user.username}, follow_redirects=True) + client.post("/login", data={"username": admin_user.username, "password": "password123"}, follow_redirects=True) response = client.get(url_for("admin.list_users")) assert response.status_code == 200 @@ -32,7 +32,7 @@ def test_list_users_as_admin(self, client, admin_user): def test_list_users_as_regular_user_denied(self, client, user): """Test that regular users cannot access user list.""" # Login as regular user using the login endpoint - client.post("/login", data={"username": user.username}, follow_redirects=True) + client.post("/login", data={"username": user.username, "password": "password123"}, follow_redirects=True) response = client.get(url_for("admin.list_users")) # Should redirect or show error @@ -538,7 +538,7 @@ def test_delete_user_cascades_to_project_costs(self, client, admin_user, user, t def test_user_list_shows_delete_button_for_other_users(self, client, admin_user, user): """Test that the user list shows delete button for other users.""" # Login as admin using the login endpoint - client.post("/login", data={"username": admin_user.username}, follow_redirects=True) + client.post("/login", data={"username": admin_user.username, "password": "password123"}, follow_redirects=True) response = client.get(url_for("admin.list_users")) assert response.status_code == 200 @@ -550,7 +550,7 @@ def test_user_list_shows_delete_button_for_other_users(self, client, admin_user, def test_user_list_hides_delete_button_for_current_user(self, client, admin_user): """Test that the user list doesn't show delete button for current user.""" # Login as admin using the login endpoint - client.post("/login", data={"username": admin_user.username}, follow_redirects=True) + client.post("/login", data={"username": admin_user.username, "password": "password123"}, follow_redirects=True) response = client.get(url_for("admin.list_users")) assert response.status_code == 200 @@ -581,7 +581,7 @@ def test_admin_can_delete_user_without_data(self, client, admin_user, app): user_id = clean_user.id # Login as admin using the login endpoint - client.post("/login", data={"username": admin_user.username}, follow_redirects=True) + client.post("/login", data={"username": admin_user.username, "password": "password123"}, follow_redirects=True) # Delete the user response = client.post(url_for("admin.delete_user", user_id=user_id), follow_redirects=True) @@ -612,7 +612,7 @@ def test_cannot_delete_user_with_time_entries(self, client, admin_user, user, te user_id = user.id # Login as admin using the login endpoint - client.post("/login", data={"username": admin_user.username}, follow_redirects=True) + client.post("/login", data={"username": admin_user.username, "password": "password123"}, follow_redirects=True) # Try to delete response = client.post(url_for("admin.delete_user", user_id=user_id), follow_redirects=True) @@ -634,7 +634,7 @@ def test_cannot_delete_user_with_time_entries(self, client, admin_user, user, te def test_cannot_delete_last_admin(self, client, admin_user, app): """SMOKE: System prevents deletion of the last administrator.""" # Login as admin using the login endpoint - client.post("/login", data={"username": admin_user.username}, follow_redirects=True) + client.post("/login", data={"username": admin_user.username, "password": "password123"}, follow_redirects=True) # Try to delete the only admin response = client.post(url_for("admin.delete_user", user_id=admin_user.id), follow_redirects=True) @@ -651,7 +651,7 @@ def test_cannot_delete_last_admin(self, client, admin_user, app): def test_user_list_accessible_to_admin(self, client, admin_user): """SMOKE: Admin can access user list page.""" # Login as admin using the login endpoint - client.post("/login", data={"username": admin_user.username}, follow_redirects=True) + client.post("/login", data={"username": admin_user.username, "password": "password123"}, follow_redirects=True) response = client.get(url_for("admin.list_users")) @@ -686,7 +686,7 @@ def test_regular_user_cannot_access_user_deletion(self, client, user, app): def test_delete_button_appears_in_ui(self, client, admin_user, user): """SMOKE: Delete button appears in user list UI.""" # Login as admin using the login endpoint - client.post("/login", data={"username": admin_user.username}, follow_redirects=True) + client.post("/login", data={"username": admin_user.username, "password": "password123"}, follow_redirects=True) response = client.get(url_for("admin.list_users")) @@ -709,7 +709,7 @@ def test_complete_user_deletion_workflow(self, client, admin_user, app): user_id = new_user.id # Login as admin using the login endpoint - client.post("/login", data={"username": admin_user.username}, follow_redirects=True) + client.post("/login", data={"username": admin_user.username, "password": "password123"}, follow_redirects=True) # Step 2: View user list (should show user) response = client.get(url_for("admin.list_users")) @@ -741,7 +741,7 @@ def test_admin_can_reset_user_password(self, client, admin_user, user, app): user_id = user.id # Login as admin using the login endpoint - client.post("/login", data={"username": admin_user.username}, follow_redirects=True) + client.post("/login", data={"username": admin_user.username, "password": "password123"}, follow_redirects=True) # Reset password response = client.post( @@ -770,7 +770,7 @@ def test_admin_can_reset_user_password(self, client, admin_user, user, app): def test_password_reset_form_accessible(self, client, admin_user, user): """SMOKE: Password reset form is accessible to admin.""" # Login as admin using the login endpoint - client.post("/login", data={"username": admin_user.username}, follow_redirects=True) + client.post("/login", data={"username": admin_user.username, "password": "password123"}, follow_redirects=True) # Access edit form response = client.get(url_for("admin.edit_user", user_id=user.id)) diff --git a/tests/test_audit_trail_smoke.py b/tests/test_audit_trail_smoke.py index 2b010adc..cc224cbf 100644 --- a/tests/test_audit_trail_smoke.py +++ b/tests/test_audit_trail_smoke.py @@ -22,10 +22,12 @@ def test_audit_log_creation_smoke(self, app, test_user, test_project): change_description="Smoke test audit log", ) - # Verify log was created - logs = AuditLog.query.filter_by(entity_type="Project", entity_id=test_project.id).all() + # Verify log was created (filter by user so we get the one we created, not fixture-created logs) + logs = AuditLog.query.filter_by( + entity_type="Project", entity_id=test_project.id, user_id=test_user.id, action="created" + ).all() - assert len(logs) > 0 + assert len(logs) >= 1 assert logs[0].action == "created" assert logs[0].user_id == test_user.id @@ -69,12 +71,14 @@ def test_audit_log_entity_history_smoke(self, app, test_user, test_project): entity_name=test_project.name, ) - # Retrieve entity history + # Retrieve entity history (may include fixture-created log; we expect at least our 3 updates) history = AuditLog.get_for_entity("Project", test_project.id, limit=10) - assert len(history) == 3 + assert len(history) >= 3 assert all(log.entity_type == "Project" for log in history) assert all(log.entity_id == test_project.id for log in history) + updated_logs = [log for log in history if log.action == "updated"] + assert len(updated_logs) == 3 def test_audit_log_user_activity_smoke(self, app, test_user, test_project): """Smoke test: Retrieve user activity history""" @@ -127,10 +131,13 @@ def test_audit_log_filtering_smoke(self, app, test_user, test_project): entity_name=test_project.name, ) - # Filter by action - created_logs = AuditLog.get_recent(action="created", limit=10) - assert len(created_logs) == 1 + # Filter by action and user so we only count the test's created log, not fixture-created logs + created_logs = AuditLog.get_recent(action="created", user_id=test_user.id, limit=10) + assert len(created_logs) >= 1 assert created_logs[0].action == "created" + # Our created log is for this project + our_created = [log for log in created_logs if log.entity_type == "Project" and log.entity_id == test_project.id] + assert len(our_created) == 1 # Filter by entity type project_logs = AuditLog.get_recent(entity_type="Project", limit=10) diff --git a/tests/test_invoices.py b/tests/test_invoices.py index e57a75f1..6c634657 100644 --- a/tests/test_invoices.py +++ b/tests/test_invoices.py @@ -1489,7 +1489,7 @@ def test_invoice_view_has_delete_button(app, client, user, project): from app.models import Client # Authenticate using login endpoint - client.post("/login", data={"username": user.username}, follow_redirects=True) + client.post("/login", data={"username": user.username, "password": "password123"}, follow_redirects=True) # Create client and invoice cl = ClientFactory(name="Delete Button Test Client", email="button@test.com") @@ -1534,7 +1534,7 @@ def test_invoice_list_has_delete_buttons(app, client, admin_user, project): project_id = project.id # Authenticate as admin using login endpoint - client.post("/login", data={"username": admin_user.username}, follow_redirects=True) + client.post("/login", data={"username": admin_user.username, "password": "password123"}, follow_redirects=True) # Create client and invoices cl = Client(name="List Delete Test Client", email="listdelete@test.com") @@ -1591,7 +1591,7 @@ def test_delete_invoice_with_complex_data_smoke(app, client, user, project): from app.models.payments import Payment # Authenticate using login endpoint - client.post("/login", data={"username": user.username}, follow_redirects=True) + client.post("/login", data={"username": user.username, "password": "password123"}, follow_redirects=True) # Create client and invoice cl = Client(name="Complex Delete Test", email="complex@test.com") diff --git a/tests/test_pdf_layout.py b/tests/test_pdf_layout.py index 29865ad9..7d9a5378 100644 --- a/tests/test_pdf_layout.py +++ b/tests/test_pdf_layout.py @@ -118,16 +118,16 @@ def test_pdf_layout_save_custom_template(admin_authenticated_client, app): assert response.status_code == 200 - # Verify settings were saved (for A4, it also updates Settings for backwards compatibility) + # Verify settings were saved (app may normalize CSS with @page rule; require our custom part) with app.app_context(): settings = Settings.get_settings() assert settings.invoice_pdf_template_html == custom_html - assert settings.invoice_pdf_template_css == custom_css + assert custom_css in (settings.invoice_pdf_template_css or "") # Also check InvoicePDFTemplate template = InvoicePDFTemplate.get_template("A4") assert template.template_html == custom_html - assert template.template_css == custom_css + assert custom_css in (template.template_css or "") @pytest.mark.smoke @@ -170,7 +170,17 @@ def test_pdf_layout_get_defaults(admin_authenticated_client): @pytest.mark.admin def test_pdf_layout_preview(admin_authenticated_client, sample_invoice): """Test PDF layout preview functionality.""" - # Test preview with custom HTML/CSS + # Preview requires a saved template; save a minimal one first + admin_authenticated_client.post( + "/admin/pdf-layout", + data={ + "invoice_pdf_template_html": "

Test Invoice {{ invoice.invoice_number }}

", + "invoice_pdf_template_css": "h1 { color: red; }", + "page_size": "A4", + }, + follow_redirects=True, + ) + response = admin_authenticated_client.post( "/admin/pdf-layout/preview", data={ @@ -181,7 +191,7 @@ def test_pdf_layout_preview(admin_authenticated_client, sample_invoice): ) assert response.status_code == 200 - # Should return HTML content + # Should return HTML content (invoice number or heading) assert b"Test Invoice" in response.data or b"INV-2024-001" in response.data diff --git a/tests/test_permissions_routes.py b/tests/test_permissions_routes.py index 1f8cf63d..58c1425f 100644 --- a/tests/test_permissions_routes.py +++ b/tests/test_permissions_routes.py @@ -9,7 +9,7 @@ def test_roles_list_page(client, admin_user): """Test that roles list page loads for admin""" # Login as admin - client.post("/login", data={"username": admin_user.username}, follow_redirects=True) + client.post("/login", data={"username": admin_user.username, "password": "password123"}, follow_redirects=True) # Access roles list page response = client.get("/admin/roles") @@ -20,7 +20,7 @@ def test_roles_list_page(client, admin_user): @pytest.mark.smoke def test_create_role_page(client, admin_user): """Test that create role page loads for admin""" - client.post("/login", data={"username": admin_user.username}, follow_redirects=True) + client.post("/login", data={"username": admin_user.username, "password": "password123"}, follow_redirects=True) response = client.get("/admin/roles/create") assert response.status_code == 200 @@ -30,7 +30,7 @@ def test_create_role_page(client, admin_user): @pytest.mark.smoke def test_permissions_list_page(client, admin_user): """Test that permissions list page loads for admin""" - client.post("/login", data={"username": admin_user.username}, follow_redirects=True) + client.post("/login", data={"username": admin_user.username, "password": "password123"}, follow_redirects=True) response = client.get("/admin/permissions") assert response.status_code == 200 @@ -48,7 +48,7 @@ def test_create_role_flow(app, client, admin_user): perm_id = permission.id # Login as admin - client.post("/login", data={"username": admin_user.username}, follow_redirects=True) + client.post("/login", data={"username": admin_user.username, "password": "password123"}, follow_redirects=True) # Create role response = client.post( @@ -78,7 +78,7 @@ def test_view_role_page(app, client, admin_user): role_id = role.id # Login as admin - client.post("/login", data={"username": admin_user.username}, follow_redirects=True) + client.post("/login", data={"username": admin_user.username, "password": "password123"}, follow_redirects=True) # View role response = client.get(f"/admin/roles/{role_id}") @@ -97,7 +97,7 @@ def test_edit_role_flow(app, client, admin_user): role_id = role.id # Login as admin - client.post("/login", data={"username": admin_user.username}, follow_redirects=True) + client.post("/login", data={"username": admin_user.username, "password": "password123"}, follow_redirects=True) # Edit role response = client.post( @@ -126,7 +126,7 @@ def test_delete_role_flow(app, client, admin_user): role_id = role.id # Login as admin - client.post("/login", data={"username": admin_user.username}, follow_redirects=True) + client.post("/login", data={"username": admin_user.username, "password": "password123"}, follow_redirects=True) # Delete role response = client.post(f"/admin/roles/{role_id}/delete", follow_redirects=True) @@ -149,7 +149,7 @@ def test_cannot_delete_system_role(app, client, admin_user): role_id = role.id # Login as admin - client.post("/login", data={"username": admin_user.username}, follow_redirects=True) + client.post("/login", data={"username": admin_user.username, "password": "password123"}, follow_redirects=True) # Try to delete system role response = client.post(f"/admin/roles/{role_id}/delete", follow_redirects=True) @@ -172,7 +172,7 @@ def test_cannot_edit_system_role(app, client, admin_user): role_id = role.id # Login as admin - client.post("/login", data={"username": admin_user.username}, follow_redirects=True) + client.post("/login", data={"username": admin_user.username, "password": "password123"}, follow_redirects=True) # Try to edit system role response = client.post( @@ -201,7 +201,7 @@ def test_manage_user_roles_page(app, client, admin_user): user_id = user.id # Login as admin - client.post("/login", data={"username": admin_user.username}, follow_redirects=True) + client.post("/login", data={"username": admin_user.username, "password": "password123"}, follow_redirects=True) # Access manage roles page response = client.get(f"/admin/users/{user_id}/roles") @@ -222,7 +222,7 @@ def test_assign_roles_to_user(app, client, admin_user): role_id = role.id # Login as admin - client.post("/login", data={"username": admin_user.username}, follow_redirects=True) + client.post("/login", data={"username": admin_user.username, "password": "password123"}, follow_redirects=True) # Assign role to user response = client.post(f"/admin/users/{user_id}/roles", data={"roles": [str(role_id)]}, follow_redirects=True) @@ -253,7 +253,7 @@ def test_api_get_user_permissions(app, client, admin_user): user_id = user.id # Login as admin - client.post("/login", data={"username": admin_user.username}, follow_redirects=True) + client.post("/login", data={"username": admin_user.username, "password": "password123"}, follow_redirects=True) # Get user permissions via API response = client.get(f"/api/users/{user_id}/permissions") @@ -280,7 +280,7 @@ def test_api_get_role_permissions(app, client, admin_user): role_id = role.id # Login as admin - client.post("/login", data={"username": admin_user.username}, follow_redirects=True) + client.post("/login", data={"username": admin_user.username, "password": "password123"}, follow_redirects=True) # Get role permissions via API response = client.get(f"/api/roles/{role_id}/permissions") diff --git a/tests/test_tasks_templates.py b/tests/test_tasks_templates.py index 8113020f..2ccd33ad 100644 --- a/tests/test_tasks_templates.py +++ b/tests/test_tasks_templates.py @@ -11,12 +11,13 @@ def test_create_task_page_has_tips(client, app): # Minimal data to render page user = User(username="ui_user", role="user") user.is_active = True + user.set_password("password123") db.session.add(user) db.session.add(Project(name="UI Test Project", client="UI Test Client")) db.session.commit() # Login using the login endpoint - client.post("/login", data={"username": user.username}, follow_redirects=True) + client.post("/login", data={"username": user.username, "password": "password123"}, follow_redirects=True) resp = client.get("/tasks/create") assert resp.status_code == 200 @@ -30,6 +31,7 @@ def test_edit_task_page_has_tips(client, app): # Minimal data to render page user = User(username="ui_editor", role="user") user.is_active = True + user.set_password("password123") project = Project(name="Edit UI Project", client="Client X") db.session.add_all([user, project]) db.session.commit() @@ -39,7 +41,7 @@ def test_edit_task_page_has_tips(client, app): db.session.commit() # Login using the login endpoint - client.post("/login", data={"username": user.username}, follow_redirects=True) + client.post("/login", data={"username": user.username, "password": "password123"}, follow_redirects=True) resp = client.get(f"/tasks/{task.id}/edit") assert resp.status_code == 200 @@ -57,12 +59,13 @@ def test_kanban_board_aria_and_dnd(authenticated_client, app): # Minimal data for rendering board user = User(username="kanban_user", role="admin") + user.set_password("password123") project = Project(name="Kanban Project", client="Client K", code="KAN") db.session.add_all([user, project]) db.session.commit() # authenticated_client already has a logged-in user, but we need to login as the new user - authenticated_client.post("/login", data={"username": user.username}, follow_redirects=True) + authenticated_client.post("/login", data={"username": user.username, "password": "password123"}, follow_redirects=True) resp = authenticated_client.get("/kanban") assert resp.status_code == 200 @@ -82,6 +85,7 @@ def test_kanban_card_shows_project_code_and_no_status_dropdown(authenticated_cli KanbanColumn.initialize_default_columns() admin = User(username="admin_user", role="admin") + admin.set_password("password123") project = Project(name="Very Long Project Name", client="CL", code="VLPN") db.session.add_all([admin, project]) db.session.commit() @@ -91,7 +95,7 @@ def test_kanban_card_shows_project_code_and_no_status_dropdown(authenticated_cli db.session.commit() # Login as admin using the login endpoint - authenticated_client.post("/login", data={"username": admin.username}, follow_redirects=True) + authenticated_client.post("/login", data={"username": admin.username, "password": "password123"}, follow_redirects=True) resp = authenticated_client.get("/kanban") assert resp.status_code == 200 diff --git a/tests/test_time_entry_resume.py b/tests/test_time_entry_resume.py index 7ef20de7..4aedba62 100644 --- a/tests/test_time_entry_resume.py +++ b/tests/test_time_entry_resume.py @@ -363,7 +363,7 @@ def test_resume_timer_smoke(client, user, project): timer_id = timer.id # Login using the login endpoint (after creating timer) - client.post("/login", data={"username": user.username}, follow_redirects=True) + client.post("/login", data={"username": user.username, "password": "password123"}, follow_redirects=True) # Resume the timer response = client.get(f"/timer/resume/{timer_id}", follow_redirects=True)