import asyncio import importlib from datetime import datetime, timedelta from pathlib import Path timetracking_router = importlib.import_module("app.timetracking.backend.router") def test_live_timer_rounds_started_minute_up_instead_of_zero(): started = datetime(2026, 8, 30, 10, 0, 0) entry = {"start_tid": started, "pause_total_seconds": 0, "paused_at": None} assert timetracking_router._elapsed_minutes_excluding_pause(entry, started) == 0 assert timetracking_router._elapsed_minutes_excluding_pause(entry, started + timedelta(seconds=1)) == 1 assert timetracking_router._elapsed_minutes_excluding_pause(entry, started + timedelta(seconds=60)) == 1 assert timetracking_router._elapsed_minutes_excluding_pause(entry, started + timedelta(seconds=61)) == 2 def test_manual_time_keeps_local_wall_clock_and_creates_one_entry_per_employee(monkeypatch): captured = {} monkeypatch.setattr(timetracking_router, "_resolve_case_customer_id", lambda *_args: 77) def fake_execute_query(query, params): captured["query"] = query captured["params"] = params employee_ids = params[-1] return [{"id": index + 1, "medarbejder_id": employee_id} for index, employee_id in enumerate(employee_ids)] monkeypatch.setattr(timetracking_router, "execute_query", fake_execute_query) result = asyncio.run( timetracking_router.create_manual_time_v1( { "sag_id": 88, "medarbejder_ids": [12, 34], "worked_date": "2026-08-18", "start_tid": "2026-08-18T10:00:00", "slut_tid": "2026-08-18T11:00:00", "faktisk_tid_min": 60, }, current_user={"id": 9, "username": "tester"}, ) ) assert result["count"] == 2 assert [row["medarbejder_id"] for row in result["created"]] == [12, 34] assert captured["params"][-1] == [12, 34] assert captured["params"][10] == datetime(2026, 8, 18, 10, 0) assert captured["params"][11] == datetime(2026, 8, 18, 11, 0) assert "UNNEST(%s::bigint[])" in captured["query"] def test_manual_time_bulk_resolves_current_user_and_removes_duplicates(monkeypatch): captured = {} monkeypatch.setattr(timetracking_router, "_resolve_case_customer_id", lambda *_args: 77) def fake_execute_query(_query, params): captured["employee_ids"] = params[-1] return [{"id": 1, "medarbejder_id": employee_id} for employee_id in params[-1]] monkeypatch.setattr(timetracking_router, "execute_query", fake_execute_query) result = asyncio.run( timetracking_router.create_manual_time_v1( { "sag_id": 88, "medarbejder_ids": [None, 9, 12, 12], "faktisk_tid_min": 30, }, current_user={"id": 9, "username": "tester"}, ) ) assert captured["employee_ids"] == [9, 12] assert result["count"] == 2 def test_case_time_form_sends_local_time_and_multiple_employees(): template = Path("app/modules/sag/templates/detail_v3.html").read_text() manual_form_script = template.split("async function createManualTimeV1(event)", 1)[1].split( "document.addEventListener('DOMContentLoaded'", 1 )[0] assert 'class="form-check-input mt-0 time-v1-employee-option"' in template assert "{% for user in assignment_users %}" in template assert "loadTimeV1EmployeeOptions" not in template assert "medarbejder_ids: employeeIds" in manual_form_script assert "startObj = `${dateVal}T${tStart}:00`;" in manual_form_script assert "endObj = `${endDate}T${tEnd}:00`;" in manual_form_script assert ".toISOString()" not in manual_form_script quick_time_script = template.split("async function registerQuickTimeFromComment(content)", 1)[1].split( "function resolveCommentAwaitCustomerStatus", 1 )[0] assert "startIso = `${dateValue}T${startValue}:00`;" in quick_time_script assert "endIso = addMinutesToTimeV1LocalIso(startIso, minutes);" in quick_time_script assert ".toISOString()" not in quick_time_script def test_bottom_bar_timer_changes_refresh_case_timer_panel(): bottom_bar = Path("static/js/bottom-bar.js").read_text() case_template = Path("app/modules/sag/templates/detail_v3.html").read_text() assert "notifyTimerStateChanged('paused')" in bottom_bar assert "notifyTimerStateChanged('resumed'" in bottom_bar assert "notifyTimerStateChanged('stopped')" in bottom_bar assert "window.addEventListener('bb:timer-state-changed'" in case_template assert "event.key === 'bmc:timer-state-changed'" in case_template assert "timerChip.classList.toggle('is-hidden', !hasActiveTimer && !hasPausedTimer)" in bottom_bar assert "timerText.textContent = 'Pauset ยท ' + name" in bottom_bar assert "const visibleTimer = timer.active ? timer : (paused[0] || {})" in bottom_bar