mirror of
https://github.com/DRYTRIX/TimeTracker.git
synced 2025-12-30 15:49:44 -06:00
Update routes, services, and tests
This commit is contained in:
@@ -632,7 +632,15 @@ def create_app(config=None):
|
||||
app.logger.warning(f"Failed to initialize PostHog: {e}")
|
||||
|
||||
# Fail-fast on weak/missing secret in production
|
||||
if not app.debug and app.config.get("FLASK_ENV", "production") == "production":
|
||||
# Skip validation in testing or debug mode
|
||||
is_testing = app.config.get("TESTING", False)
|
||||
# Check both config and environment variable for FLASK_ENV
|
||||
flask_env_config = app.config.get("FLASK_ENV")
|
||||
flask_env_env = os.getenv("FLASK_ENV", "production")
|
||||
flask_env = flask_env_config if flask_env_config else flask_env_env
|
||||
is_production_env = flask_env == "production" and not is_testing
|
||||
|
||||
if not app.debug and is_production_env:
|
||||
secret = app.config.get("SECRET_KEY")
|
||||
placeholder_values = {
|
||||
"dev-secret-key-change-in-production",
|
||||
|
||||
@@ -882,7 +882,7 @@ def new_transfer():
|
||||
reason = f"Transfer from {from_warehouse.code} to {to_warehouse.code}"
|
||||
|
||||
# Create negative movement (from source warehouse)
|
||||
out_movement, _ = StockMovement.record_movement(
|
||||
out_movement, _unused = StockMovement.record_movement(
|
||||
movement_type="transfer",
|
||||
stock_item_id=stock_item_id,
|
||||
warehouse_id=from_warehouse_id,
|
||||
@@ -896,7 +896,7 @@ def new_transfer():
|
||||
)
|
||||
|
||||
# Create positive movement (to destination warehouse)
|
||||
in_movement, _ = StockMovement.record_movement(
|
||||
in_movement, _unused = StockMovement.record_movement(
|
||||
movement_type="transfer",
|
||||
stock_item_id=stock_item_id,
|
||||
warehouse_id=to_warehouse_id,
|
||||
|
||||
@@ -14,6 +14,7 @@ from app import db, log_event, track_event
|
||||
from app.services import InvoiceService, ProjectService
|
||||
from app.repositories import InvoiceRepository, ProjectRepository
|
||||
from app.models import Invoice, Project, Settings
|
||||
from app.utils.db import safe_commit
|
||||
from app.utils.api_responses import success_response, error_response, paginated_response
|
||||
from app.utils.event_bus import emit_event
|
||||
from app.constants import WebhookEvent, InvoiceStatus
|
||||
|
||||
@@ -349,7 +349,7 @@ def create_project():
|
||||
entity_type="project",
|
||||
entity_id=project.id,
|
||||
entity_name=project.name,
|
||||
description=f'Created project "{project.name}" for {client.name}',
|
||||
description=f'Created project "{project.name}" for {project.client.name}',
|
||||
ip_address=request.remote_addr,
|
||||
user_agent=request.headers.get("User-Agent"),
|
||||
)
|
||||
|
||||
@@ -11,8 +11,8 @@ from flask_login import login_required, current_user
|
||||
from sqlalchemy.orm import joinedload
|
||||
from app import db
|
||||
from app.services import ProjectService
|
||||
from app.repositories import ProjectRepository, ClientRepository
|
||||
from app.models import Project, Client, UserFavoriteProject
|
||||
from app.repositories import ProjectRepository, ClientRepository, TimeEntryRepository
|
||||
from app.models import Project, Client, UserFavoriteProject, TimeEntry
|
||||
from app.utils.permissions import admin_or_permission_required
|
||||
|
||||
projects_bp = Blueprint("projects", __name__)
|
||||
|
||||
@@ -2,11 +2,12 @@ from flask import Blueprint, render_template, request, redirect, url_for, flash,
|
||||
from flask_babel import gettext as _
|
||||
from flask_login import login_required, current_user
|
||||
from app import db, log_event, track_event
|
||||
from app.models import Quote, QuoteItem, QuoteAttachment, Client, Project, Invoice
|
||||
from app.models import Quote, QuoteItem, QuoteAttachment, Client, Project, Invoice, QuoteTemplate
|
||||
from datetime import datetime
|
||||
from decimal import Decimal, InvalidOperation
|
||||
from app.utils.db import safe_commit
|
||||
from app.utils.permissions import admin_or_permission_required, permission_required
|
||||
from app.utils.config_manager import get_setting
|
||||
|
||||
quotes_bp = Blueprint("quotes", __name__)
|
||||
|
||||
@@ -61,6 +62,11 @@ def create_quote():
|
||||
valid_until = request.form.get("valid_until", "").strip()
|
||||
notes = request.form.get("notes", "").strip()
|
||||
terms = request.form.get("terms", "").strip()
|
||||
payment_terms = request.form.get("payment_terms", "").strip()
|
||||
discount_type = request.form.get("discount_type", "").strip()
|
||||
discount_amount = request.form.get("discount_amount", "").strip()
|
||||
discount_reason = request.form.get("discount_reason", "").strip()
|
||||
coupon_code = request.form.get("coupon_code", "").strip()
|
||||
|
||||
try:
|
||||
current_app.logger.info(
|
||||
@@ -1084,6 +1090,10 @@ def create_template():
|
||||
@admin_or_permission_required("create_quotes")
|
||||
def save_template_from_quote(template_id):
|
||||
"""Save current quote as a template"""
|
||||
quote_id = request.form.get("quote_id", type=int)
|
||||
if not quote_id:
|
||||
flash(_("Quote ID is required"), "error")
|
||||
return redirect(url_for("quotes.list_templates"))
|
||||
quote = Quote.query.get_or_404(quote_id)
|
||||
|
||||
# Check permissions
|
||||
|
||||
@@ -4,7 +4,7 @@ Uses pattern matching and heuristics (can be extended with actual AI APIs)
|
||||
"""
|
||||
|
||||
from typing import Dict, List, Any, Optional
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timedelta
|
||||
from app import db
|
||||
from app.models import TimeEntry, Project, Task, Client
|
||||
from sqlalchemy import func
|
||||
|
||||
@@ -23,8 +23,11 @@ def admin_user(app):
|
||||
@pytest.fixture
|
||||
def authenticated_admin_client(client, admin_user):
|
||||
"""Create an authenticated admin client."""
|
||||
# Use the actual login endpoint to properly authenticate
|
||||
client.post("/login", data={"username": admin_user.username}, follow_redirects=True)
|
||||
from flask_login import login_user
|
||||
|
||||
with client.session_transaction() as sess:
|
||||
# Use Flask-Login's login_user directly for tests
|
||||
login_user(admin_user)
|
||||
return client
|
||||
|
||||
|
||||
|
||||
@@ -277,7 +277,7 @@ def test_pdf_generation_with_default_template(app, sample_invoice):
|
||||
|
||||
@pytest.mark.smoke
|
||||
@pytest.mark.admin
|
||||
def test_pdf_layout_navigation_link_exists(admin_authenticated_client):
|
||||
def test_pdf_layout_navigation_link_exists(admin_authenticated_client, app):
|
||||
"""Test that PDF layout link exists in admin navigation."""
|
||||
# Access admin dashboard or any admin page
|
||||
response = admin_authenticated_client.get("/admin/settings")
|
||||
@@ -285,6 +285,11 @@ def test_pdf_layout_navigation_link_exists(admin_authenticated_client):
|
||||
assert response.status_code == 200
|
||||
# Should contain link to PDF layout page
|
||||
# The link might be in the navigation or as a menu item
|
||||
html = response.get_data(as_text=True)
|
||||
# Check for PDF layout link - it's in a dropdown menu
|
||||
with app.app_context():
|
||||
pdf_layout_url = url_for("admin.pdf_layout")
|
||||
assert "admin.pdf_layout" in html or "pdf-layout" in html or "PDF Templates" in html or pdf_layout_url in html
|
||||
|
||||
|
||||
@pytest.mark.smoke
|
||||
|
||||
@@ -7,6 +7,7 @@ previous time entries with pre-filled data.
|
||||
|
||||
import pytest
|
||||
from datetime import datetime, timedelta
|
||||
from flask import url_for
|
||||
from app import db
|
||||
from app.models import TimeEntry, User, Project, Task
|
||||
|
||||
@@ -272,12 +273,19 @@ def test_admin_can_duplicate_any_entry(admin_authenticated_client, user, project
|
||||
def test_duplicate_button_on_dashboard(authenticated_client, time_entry_with_all_fields, app):
|
||||
"""Smoke test: Duplicate button should appear on dashboard."""
|
||||
with app.app_context():
|
||||
# Clear any cache that might affect the dashboard
|
||||
from app.utils.cache import get_cache
|
||||
cache = get_cache()
|
||||
cache.delete(f"dashboard:{time_entry_with_all_fields.user_id}")
|
||||
|
||||
response = authenticated_client.get("/dashboard")
|
||||
assert response.status_code == 200
|
||||
html = response.get_data(as_text=True)
|
||||
|
||||
# Check for duplicate button/link (may use icon or text)
|
||||
assert "fa-copy" in html or "duplicate" in html.lower()
|
||||
# The button uses fa-copy icon and duplicate_timer route
|
||||
duplicate_url = url_for("timer.duplicate_timer", timer_id=time_entry_with_all_fields.id)
|
||||
assert "fa-copy" in html or "duplicate" in html.lower() or "duplicate_timer" in html or duplicate_url in html
|
||||
|
||||
|
||||
@pytest.mark.smoke
|
||||
|
||||
@@ -34,8 +34,11 @@ def admin_user(app):
|
||||
@pytest.fixture
|
||||
def authenticated_admin_client(client, admin_user):
|
||||
"""Create an authenticated admin client."""
|
||||
from flask_login import login_user
|
||||
|
||||
with client.session_transaction() as sess:
|
||||
sess["_user_id"] = str(admin_user.id)
|
||||
# Use Flask-Login's login_user directly for tests
|
||||
login_user(admin_user)
|
||||
return client
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user