mirror of
https://github.com/DRYTRIX/TimeTracker.git
synced 2026-01-05 11:09:55 -06:00
- 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.
769 lines
23 KiB
Python
769 lines
23 KiB
Python
"""
|
|
Comprehensive model testing suite.
|
|
Tests all models, relationships, properties, and business logic.
|
|
"""
|
|
|
|
import pytest
|
|
from datetime import datetime, timedelta, date
|
|
from decimal import Decimal
|
|
|
|
from app.models import User, Project, TimeEntry, Client, Settings, Invoice, InvoiceItem, Task
|
|
from factories import InvoiceFactory
|
|
from app import db
|
|
|
|
|
|
# ============================================================================
|
|
# User Model Tests
|
|
# ============================================================================
|
|
|
|
|
|
@pytest.mark.unit
|
|
@pytest.mark.models
|
|
@pytest.mark.smoke
|
|
def test_user_creation(app, user):
|
|
"""Test basic user creation."""
|
|
assert user.id is not None
|
|
assert user.username == "testuser"
|
|
assert user.role == "user"
|
|
assert user.is_active is True
|
|
|
|
|
|
@pytest.mark.unit
|
|
@pytest.mark.models
|
|
def test_user_is_admin_property(app, admin_user):
|
|
"""Test user is_admin property."""
|
|
assert admin_user.is_admin is True
|
|
|
|
|
|
@pytest.mark.unit
|
|
@pytest.mark.models
|
|
def test_user_active_timer(app, user, active_timer):
|
|
"""Test user active_timer property."""
|
|
# Refresh user to load relationships
|
|
db.session.refresh(user)
|
|
assert user.active_timer is not None
|
|
assert user.active_timer.id == active_timer.id
|
|
|
|
|
|
@pytest.mark.unit
|
|
@pytest.mark.models
|
|
def test_user_time_entries_relationship(app, user, multiple_time_entries):
|
|
"""Test user time entries relationship."""
|
|
db.session.refresh(user)
|
|
assert len(user.time_entries.all()) == 5
|
|
|
|
|
|
@pytest.mark.unit
|
|
@pytest.mark.models
|
|
def test_user_to_dict(app, user):
|
|
"""Test user serialization to dictionary."""
|
|
user_dict = user.to_dict()
|
|
assert "id" in user_dict
|
|
assert "username" in user_dict
|
|
assert "role" in user_dict
|
|
# Should not include sensitive data
|
|
assert "password" not in user_dict
|
|
|
|
|
|
# ============================================================================
|
|
# Client Model Tests
|
|
# ============================================================================
|
|
|
|
|
|
@pytest.mark.unit
|
|
@pytest.mark.models
|
|
@pytest.mark.smoke
|
|
def test_client_creation(app, test_client):
|
|
"""Test basic client creation."""
|
|
assert test_client.id is not None
|
|
assert test_client.name == "Test Client Corp"
|
|
assert test_client.status == "active"
|
|
assert test_client.default_hourly_rate == Decimal("85.00")
|
|
|
|
|
|
@pytest.mark.unit
|
|
@pytest.mark.models
|
|
def test_client_projects_relationship(app, test_client, multiple_projects):
|
|
"""Test client projects relationship."""
|
|
db.session.refresh(test_client)
|
|
assert len(test_client.projects.all()) == 3
|
|
|
|
|
|
@pytest.mark.unit
|
|
@pytest.mark.models
|
|
def test_client_total_projects_property(app, test_client, multiple_projects):
|
|
"""Test client total_projects property."""
|
|
db.session.refresh(test_client)
|
|
assert test_client.total_projects == 3
|
|
|
|
|
|
@pytest.mark.unit
|
|
@pytest.mark.models
|
|
def test_client_archive_activate(app, test_client):
|
|
"""Test client archive and activate methods."""
|
|
db.session.refresh(test_client)
|
|
|
|
# Archive client
|
|
test_client.archive()
|
|
db.session.commit()
|
|
assert test_client.status == "inactive"
|
|
|
|
# Activate client
|
|
test_client.activate()
|
|
db.session.commit()
|
|
assert test_client.status == "active"
|
|
|
|
|
|
@pytest.mark.unit
|
|
@pytest.mark.models
|
|
def test_client_get_active_clients(app, multiple_clients):
|
|
"""Test get_active_clients class method."""
|
|
active_clients = Client.get_active_clients()
|
|
assert len(active_clients) >= 3
|
|
|
|
|
|
@pytest.mark.unit
|
|
@pytest.mark.models
|
|
def test_client_to_dict(app, test_client):
|
|
"""Test client serialization to dictionary."""
|
|
client_dict = test_client.to_dict()
|
|
assert "id" in client_dict
|
|
assert "name" in client_dict
|
|
assert "status" in client_dict
|
|
|
|
|
|
# ============================================================================
|
|
# Project Model Tests
|
|
# ============================================================================
|
|
|
|
|
|
@pytest.mark.unit
|
|
@pytest.mark.models
|
|
@pytest.mark.smoke
|
|
def test_project_creation(app, project):
|
|
"""Test basic project creation."""
|
|
assert project.id is not None
|
|
assert project.name == "Test Project"
|
|
assert project.billable is True
|
|
assert project.status == "active"
|
|
|
|
|
|
@pytest.mark.unit
|
|
@pytest.mark.models
|
|
def test_project_client_relationship(app, project, test_client):
|
|
"""Test project client relationship."""
|
|
db.session.refresh(project)
|
|
db.session.refresh(test_client)
|
|
assert project.client_id == test_client.id
|
|
# Check backward compatibility
|
|
if hasattr(project, "client"):
|
|
assert project.client == test_client.name
|
|
|
|
|
|
@pytest.mark.unit
|
|
@pytest.mark.models
|
|
def test_project_time_entries_relationship(app, project, multiple_time_entries):
|
|
"""Test project time entries relationship."""
|
|
db.session.refresh(project)
|
|
assert len(project.time_entries.all()) == 5
|
|
|
|
|
|
@pytest.mark.unit
|
|
@pytest.mark.models
|
|
def test_project_total_hours(app, project, multiple_time_entries):
|
|
"""Test project total_hours property."""
|
|
db.session.refresh(project)
|
|
# Each entry is 8 hours (9am to 5pm), 5 entries = 40 hours
|
|
assert project.total_hours > 0
|
|
|
|
|
|
@pytest.mark.unit
|
|
@pytest.mark.models
|
|
def test_project_estimated_cost(app, project, multiple_time_entries):
|
|
"""Test project estimated_cost property."""
|
|
db.session.refresh(project)
|
|
estimated_cost = project.estimated_cost
|
|
assert estimated_cost > 0
|
|
# Cost should be hours * hourly_rate
|
|
expected_cost = project.total_hours * float(project.hourly_rate)
|
|
assert abs(float(estimated_cost) - expected_cost) < 0.01
|
|
|
|
|
|
@pytest.mark.unit
|
|
@pytest.mark.models
|
|
def test_project_archive(app, project):
|
|
"""Test project archiving."""
|
|
db.session.refresh(project)
|
|
project.status = "archived"
|
|
db.session.commit()
|
|
assert project.status == "archived"
|
|
|
|
|
|
# ============================================================================
|
|
# Time Entry Model Tests
|
|
# ============================================================================
|
|
|
|
|
|
@pytest.mark.unit
|
|
@pytest.mark.models
|
|
@pytest.mark.smoke
|
|
def test_time_entry_creation(app, time_entry):
|
|
"""Test basic time entry creation."""
|
|
assert time_entry.id is not None
|
|
assert time_entry.start_time is not None
|
|
assert time_entry.end_time is not None
|
|
|
|
|
|
@pytest.mark.unit
|
|
@pytest.mark.models
|
|
def test_time_entry_duration(app, time_entry):
|
|
"""Test time entry duration calculations."""
|
|
db.session.refresh(time_entry)
|
|
assert time_entry.duration_seconds > 0
|
|
assert time_entry.duration_hours > 0
|
|
assert time_entry.duration_formatted is not None
|
|
|
|
|
|
@pytest.mark.unit
|
|
@pytest.mark.models
|
|
def test_active_timer_is_active(app, active_timer):
|
|
"""Test active timer is_active property."""
|
|
db.session.refresh(active_timer)
|
|
assert active_timer.is_active is True
|
|
assert active_timer.end_time is None
|
|
|
|
|
|
@pytest.mark.unit
|
|
@pytest.mark.models
|
|
def test_stop_timer(app, active_timer):
|
|
"""Test stopping an active timer."""
|
|
db.session.refresh(active_timer)
|
|
active_timer.stop_timer()
|
|
db.session.commit()
|
|
|
|
db.session.refresh(active_timer)
|
|
assert active_timer.is_active is False
|
|
assert active_timer.end_time is not None
|
|
assert active_timer.duration_seconds > 0
|
|
|
|
|
|
@pytest.mark.unit
|
|
@pytest.mark.models
|
|
def test_time_entry_tag_list(app, test_client):
|
|
"""Test time entry tag_list property."""
|
|
from app.models import User, Project
|
|
|
|
user = User.query.first() or User(username="test", role="user")
|
|
project = Project.query.first() or Project(name="Test", client_id=test_client.id, billable=True)
|
|
|
|
if not user.id:
|
|
db.session.add(user)
|
|
if not project.id:
|
|
db.session.add(project)
|
|
db.session.commit()
|
|
|
|
from factories import TimeEntryFactory
|
|
|
|
entry = TimeEntryFactory(
|
|
user_id=user.id,
|
|
project_id=project.id,
|
|
start_time=datetime.utcnow(),
|
|
end_time=datetime.utcnow() + timedelta(hours=1),
|
|
tags="python,testing,development",
|
|
source="manual",
|
|
)
|
|
|
|
db.session.refresh(entry)
|
|
assert entry.tag_list == ["python", "testing", "development"]
|
|
|
|
|
|
# ============================================================================
|
|
# Task Model Tests
|
|
# ============================================================================
|
|
|
|
|
|
@pytest.mark.unit
|
|
@pytest.mark.models
|
|
def test_task_creation(app, task):
|
|
"""Test basic task creation."""
|
|
db.session.refresh(task)
|
|
assert task.id is not None
|
|
assert task.name == "Test Task"
|
|
assert task.status == "todo"
|
|
|
|
|
|
@pytest.mark.unit
|
|
@pytest.mark.models
|
|
def test_task_project_relationship(app, task, project):
|
|
"""Test task project relationship."""
|
|
db.session.refresh(task)
|
|
db.session.refresh(project)
|
|
assert task.project_id == project.id
|
|
|
|
|
|
@pytest.mark.unit
|
|
@pytest.mark.models
|
|
def test_task_status_transitions(app, task):
|
|
"""Test task status transitions."""
|
|
db.session.refresh(task)
|
|
|
|
# Mark as in progress
|
|
task.status = "in_progress"
|
|
db.session.commit()
|
|
assert task.status == "in_progress"
|
|
|
|
# Mark as done
|
|
task.status = "done"
|
|
db.session.commit()
|
|
assert task.status == "done"
|
|
|
|
|
|
# ============================================================================
|
|
# Invoice Model Tests
|
|
# ============================================================================
|
|
|
|
|
|
@pytest.mark.unit
|
|
@pytest.mark.models
|
|
@pytest.mark.smoke
|
|
def test_invoice_creation(app, invoice):
|
|
"""Test basic invoice creation."""
|
|
# Invoice is already refreshed in the fixture, no need to refresh again
|
|
assert invoice.id is not None
|
|
assert invoice.invoice_number is not None
|
|
assert invoice.status == "draft"
|
|
|
|
|
|
@pytest.mark.unit
|
|
@pytest.mark.models
|
|
def test_invoice_number_generation(app):
|
|
"""Test invoice number generation."""
|
|
invoice_number = Invoice.generate_invoice_number()
|
|
assert invoice_number is not None
|
|
assert "INV-" in invoice_number
|
|
|
|
|
|
@pytest.mark.unit
|
|
@pytest.mark.models
|
|
def test_invoice_calculate_totals(app, invoice_with_items):
|
|
"""Test invoice total calculations."""
|
|
invoice, items = invoice_with_items
|
|
|
|
# Invoice is already committed and refreshed in the fixture
|
|
# 10 * 75 + 5 * 60 = 750 + 300 = 1050
|
|
assert invoice.subtotal == Decimal("1050.00")
|
|
|
|
# Tax: 20% of 1050 = 210
|
|
assert invoice.tax_amount == Decimal("210.00")
|
|
|
|
# Total: 1050 + 210 = 1260
|
|
assert invoice.total_amount == Decimal("1260.00")
|
|
|
|
|
|
@pytest.mark.unit
|
|
@pytest.mark.models
|
|
def test_invoice_payment_tracking(app, invoice_with_items):
|
|
"""Test invoice payment tracking."""
|
|
invoice, items = invoice_with_items
|
|
|
|
# Record partial payment
|
|
partial_payment = invoice.total_amount / 2
|
|
invoice.record_payment(
|
|
amount=partial_payment, payment_date=date.today(), payment_method="bank_transfer", payment_reference="TEST-123"
|
|
)
|
|
db.session.commit()
|
|
|
|
db.session.expire(invoice)
|
|
db.session.refresh(invoice)
|
|
assert invoice.payment_status == "partially_paid"
|
|
assert invoice.amount_paid == partial_payment
|
|
assert invoice.is_partially_paid is True
|
|
|
|
# Record remaining payment
|
|
remaining = invoice.outstanding_amount
|
|
invoice.record_payment(amount=remaining, payment_method="bank_transfer")
|
|
db.session.commit()
|
|
|
|
db.session.expire(invoice)
|
|
db.session.refresh(invoice)
|
|
assert invoice.payment_status == "fully_paid"
|
|
assert invoice.is_paid is True
|
|
assert invoice.outstanding_amount == Decimal("0")
|
|
|
|
|
|
@pytest.mark.unit
|
|
@pytest.mark.models
|
|
def test_invoice_overdue_status(app, user, project, test_client):
|
|
"""Test invoice overdue status."""
|
|
# Create overdue invoice
|
|
overdue_invoice = InvoiceFactory(
|
|
invoice_number=Invoice.generate_invoice_number(),
|
|
project_id=project.id,
|
|
client_id=test_client.id,
|
|
client_name="Test Client",
|
|
due_date=date.today() - timedelta(days=10),
|
|
created_by=user.id,
|
|
status="sent",
|
|
)
|
|
db.session.commit()
|
|
|
|
# Ensure status is 'sent' for overdue calculation compatibility
|
|
overdue_invoice.status = "sent"
|
|
db.session.commit()
|
|
db.session.refresh(overdue_invoice)
|
|
assert overdue_invoice.is_overdue is True
|
|
assert overdue_invoice.days_overdue == 10
|
|
|
|
|
|
# ============================================================================
|
|
# Settings Model Tests
|
|
# ============================================================================
|
|
|
|
|
|
@pytest.mark.unit
|
|
@pytest.mark.models
|
|
def test_settings_singleton(app):
|
|
"""Test settings singleton pattern."""
|
|
settings1 = Settings.get_settings()
|
|
settings2 = Settings.get_settings()
|
|
|
|
assert settings1.id == settings2.id
|
|
|
|
|
|
@pytest.mark.unit
|
|
@pytest.mark.models
|
|
def test_settings_default_values(app):
|
|
"""Test settings default values."""
|
|
settings = Settings.get_settings()
|
|
|
|
# Check that settings has expected attributes
|
|
assert hasattr(settings, "id")
|
|
# Add more default value checks based on your Settings model
|
|
|
|
|
|
# ============================================================================
|
|
# Model Relationship Tests
|
|
# ============================================================================
|
|
|
|
|
|
@pytest.mark.integration
|
|
@pytest.mark.models
|
|
@pytest.mark.database
|
|
def test_cascade_delete_user_time_entries(app, user, multiple_time_entries):
|
|
"""Test cascade delete of user time entries."""
|
|
user_id = user.id
|
|
|
|
# Get time entry count
|
|
entry_count = TimeEntry.query.filter_by(user_id=user_id).count()
|
|
assert entry_count == 5
|
|
|
|
# Delete user
|
|
db.session.delete(user)
|
|
db.session.commit()
|
|
|
|
# Check time entries are deleted or handled
|
|
remaining_entries = TimeEntry.query.filter_by(user_id=user_id).count()
|
|
# Depending on cascade settings, entries might be deleted or set to null
|
|
# For now, we just verify the operation completed without errors
|
|
assert remaining_entries >= 0 # Operation completed successfully
|
|
|
|
|
|
@pytest.mark.integration
|
|
@pytest.mark.models
|
|
@pytest.mark.database
|
|
def test_project_client_relationship_integrity(app, project, test_client):
|
|
"""Test project-client relationship integrity."""
|
|
# Verify the relationship
|
|
assert project.client_id == test_client.id
|
|
|
|
# Get project through client relationship
|
|
client_projects = Client.query.get(test_client.id).projects.all()
|
|
project_ids = [p.id for p in client_projects]
|
|
assert project.id in project_ids
|
|
|
|
|
|
# ============================================================================
|
|
# Model Validation Tests
|
|
# ============================================================================
|
|
|
|
|
|
@pytest.mark.unit
|
|
@pytest.mark.models
|
|
def test_project_requires_name(app, test_client):
|
|
"""Test that project requires a name."""
|
|
# Project __init__ requires name as first positional argument
|
|
# This test verifies the API enforces this requirement
|
|
with pytest.raises(TypeError):
|
|
project = Project(billable=True)
|
|
|
|
|
|
@pytest.mark.unit
|
|
@pytest.mark.models
|
|
def test_time_entry_requires_start_time(app, user, project):
|
|
"""Test that time entry requires start time."""
|
|
# TimeEntry requires start_time at database level (nullable=False)
|
|
# This test verifies the database enforces this requirement
|
|
from sqlalchemy.exc import IntegrityError
|
|
from app import db
|
|
|
|
with pytest.raises(IntegrityError):
|
|
entry = TimeEntry(user_id=user.id, project_id=project.id, source="manual")
|
|
db.session.add(entry)
|
|
db.session.commit()
|
|
|
|
|
|
# ============================================================================
|
|
# User Deletion and Cascading Tests
|
|
# ============================================================================
|
|
|
|
|
|
@pytest.mark.unit
|
|
@pytest.mark.models
|
|
def test_user_deletion_without_relationships(app):
|
|
"""Test that a user without relationships can be deleted."""
|
|
with app.app_context():
|
|
# Create a user with no relationships
|
|
delete_user = User(username="deletable", role="user")
|
|
delete_user.is_active = True
|
|
db.session.add(delete_user)
|
|
db.session.commit()
|
|
user_id = delete_user.id
|
|
|
|
# Delete the user
|
|
db.session.delete(delete_user)
|
|
db.session.commit()
|
|
|
|
# Verify deletion
|
|
deleted = User.query.get(user_id)
|
|
assert deleted is None
|
|
|
|
|
|
@pytest.mark.unit
|
|
@pytest.mark.models
|
|
def test_user_deletion_cascades_project_costs(app, test_client):
|
|
"""Test that deleting a user cascades to project costs."""
|
|
from app.models import ProjectCost
|
|
from datetime import date
|
|
|
|
with app.app_context():
|
|
# Create user and project
|
|
user = User(username="costuser", role="user")
|
|
user.is_active = True
|
|
db.session.add(user)
|
|
|
|
project = Project(name="Cost Test Project", client_id=test_client.id, billable=True)
|
|
db.session.add(project)
|
|
db.session.commit()
|
|
|
|
# Create project cost
|
|
cost = ProjectCost(
|
|
project_id=project.id,
|
|
user_id=user.id,
|
|
description="Test expense",
|
|
category="materials",
|
|
amount=Decimal("100.00"),
|
|
cost_date=date.today(),
|
|
)
|
|
db.session.add(cost)
|
|
db.session.commit()
|
|
|
|
user_id = user.id
|
|
cost_id = cost.id
|
|
|
|
# Delete user
|
|
db.session.delete(user)
|
|
db.session.commit()
|
|
|
|
# Verify user is deleted
|
|
deleted_user = User.query.get(user_id)
|
|
assert deleted_user is None
|
|
|
|
# Verify project cost is cascaded (deleted)
|
|
deleted_cost = ProjectCost.query.get(cost_id)
|
|
assert deleted_cost is None
|
|
|
|
|
|
@pytest.mark.unit
|
|
@pytest.mark.models
|
|
def test_user_deletion_cascades_time_entries(app, test_client):
|
|
"""Test that deleting a user cascades to time entries."""
|
|
with app.app_context():
|
|
# Create user and project
|
|
user = User(username="entryuser", role="user")
|
|
user.is_active = True
|
|
db.session.add(user)
|
|
|
|
project = Project(name="Entry Test Project", client_id=test_client.id, billable=True)
|
|
db.session.add(project)
|
|
db.session.commit()
|
|
|
|
# Create time entry
|
|
entry = TimeEntry(
|
|
user_id=user.id,
|
|
project_id=project.id,
|
|
start_time=datetime.utcnow(),
|
|
end_time=datetime.utcnow() + timedelta(hours=1),
|
|
description="Test entry",
|
|
)
|
|
db.session.add(entry)
|
|
db.session.commit()
|
|
|
|
user_id = user.id
|
|
entry_id = entry.id
|
|
|
|
# Delete user
|
|
db.session.delete(user)
|
|
db.session.commit()
|
|
|
|
# Verify user is deleted
|
|
deleted_user = User.query.get(user_id)
|
|
assert deleted_user is None
|
|
|
|
# Verify time entry is cascaded (deleted)
|
|
deleted_entry = TimeEntry.query.get(entry_id)
|
|
assert deleted_entry is None
|
|
|
|
|
|
@pytest.mark.unit
|
|
@pytest.mark.models
|
|
def test_user_deletion_removes_from_favorite_projects(app, test_client):
|
|
"""Test that deleting a user removes them from favorite projects."""
|
|
with app.app_context():
|
|
# Create user and project
|
|
user = User(username="favuser", role="user")
|
|
user.is_active = True
|
|
db.session.add(user)
|
|
|
|
project = Project(name="Favorite Test Project", client_id=test_client.id, billable=True)
|
|
db.session.add(project)
|
|
db.session.commit()
|
|
|
|
# Add project to favorites
|
|
user.favorite_projects.append(project)
|
|
db.session.commit()
|
|
|
|
# Verify favorite was added
|
|
assert project in user.favorite_projects.all()
|
|
|
|
user_id = user.id
|
|
project_id = project.id
|
|
|
|
# Delete user
|
|
db.session.delete(user)
|
|
db.session.commit()
|
|
|
|
# Verify user is deleted
|
|
deleted_user = User.query.get(user_id)
|
|
assert deleted_user is None
|
|
|
|
# Verify project still exists (favorites are many-to-many)
|
|
remaining_project = Project.query.get(project_id)
|
|
assert remaining_project is not None
|
|
|
|
# Verify user is not in project's favorited_by
|
|
assert user_id not in [u.id for u in remaining_project.favorited_by.all()]
|
|
|
|
|
|
@pytest.mark.unit
|
|
@pytest.mark.models
|
|
def test_user_deletion_preserves_tasks_assigned_to_them(app, test_client):
|
|
"""Test that deleting a user preserves tasks but nullifies assigned_to."""
|
|
with app.app_context():
|
|
# Create users and project
|
|
creator = User(username="creator", role="user")
|
|
creator.is_active = True
|
|
assignee = User(username="assignee", role="user")
|
|
assignee.is_active = True
|
|
db.session.add_all([creator, assignee])
|
|
|
|
project = Project(name="Task Test Project", client_id=test_client.id, billable=True)
|
|
db.session.add(project)
|
|
db.session.commit()
|
|
|
|
# Create task
|
|
task = Task(
|
|
project_id=project.id,
|
|
name="Test Task",
|
|
description="Test description",
|
|
created_by=creator.id,
|
|
assigned_to=assignee.id,
|
|
)
|
|
db.session.add(task)
|
|
db.session.commit()
|
|
|
|
assignee_id = assignee.id
|
|
task_id = task.id
|
|
|
|
# Delete assignee
|
|
db.session.delete(assignee)
|
|
db.session.commit()
|
|
|
|
# Verify assignee is deleted
|
|
deleted_user = User.query.get(assignee_id)
|
|
assert deleted_user is None
|
|
|
|
# Verify task still exists but assigned_to is nullified
|
|
remaining_task = Task.query.get(task_id)
|
|
assert remaining_task is not None
|
|
assert remaining_task.assigned_to is None
|
|
|
|
|
|
@pytest.mark.unit
|
|
@pytest.mark.models
|
|
def test_user_cannot_be_deleted_if_has_created_tasks(app, test_client):
|
|
"""Test that deleting a user who created tasks cascades properly."""
|
|
from sqlalchemy.exc import IntegrityError
|
|
|
|
with app.app_context():
|
|
# Create user and project
|
|
creator = User(username="taskcreator", role="user")
|
|
creator.is_active = True
|
|
db.session.add(creator)
|
|
|
|
project = Project(name="Task Creator Project", client_id=test_client.id, billable=True)
|
|
db.session.add(project)
|
|
db.session.commit()
|
|
|
|
# Create task
|
|
task = Task(project_id=project.id, name="Created Task", description="Test description", created_by=creator.id)
|
|
db.session.add(task)
|
|
db.session.commit()
|
|
|
|
creator_id = creator.id
|
|
|
|
# Try to delete creator - should raise IntegrityError because created_by is NOT NULL
|
|
with pytest.raises(IntegrityError):
|
|
db.session.delete(creator)
|
|
db.session.commit()
|
|
|
|
db.session.rollback()
|
|
|
|
# Verify creator still exists
|
|
still_exists = User.query.get(creator_id)
|
|
assert still_exists is not None
|
|
|
|
|
|
@pytest.mark.unit
|
|
@pytest.mark.models
|
|
def test_user_deletion_count_check(app):
|
|
"""Test that we can query user count before and after deletion."""
|
|
with app.app_context():
|
|
# Get initial count
|
|
initial_count = User.query.count()
|
|
|
|
# Create and delete a user
|
|
temp_user = User(username="tempuser", role="user")
|
|
temp_user.is_active = True
|
|
db.session.add(temp_user)
|
|
db.session.commit()
|
|
|
|
# Verify count increased
|
|
assert User.query.count() == initial_count + 1
|
|
|
|
# Delete user
|
|
db.session.delete(temp_user)
|
|
db.session.commit()
|
|
|
|
# Verify count back to initial
|
|
assert User.query.count() == initial_count
|