loreal-utilisation-dept/backend/tests/test_chat.py
DJP 993e370cea feat: Forecast, Project Type Summary, Time Log Detail, AI Chat, filters v2, stats bar, RBAC
Brings the new app to full parity with the original L'Oréal SPA and
beyond. Backend 59/59 tests (was 40, +19). Frontend typecheck/lint/build
clean. Main entry chunk 15.76 KB gz (budget 30 KB).

Backend — new endpoints + services:
- POST /api/deliverable/parse        — parse Deliverable Summary CSV/XLSX
- POST /api/projectsummary/parse     — parse Project Summary CSV/XLSX
- GET  /api/timelog/rows             — paginated, searchable, sortable view
                                        over the parsed Zoho upload
- GET  /api/forecast                 — 4-week pipeline + capacity decision
- GET  /api/project-types            — hours/asset, duration, concentration
                                        per project type + auto-insights
- POST /api/chat                     — Claude API proxy. 503s gracefully
                                        when ANTHROPIC_API_KEY is unset.
                                        Prompt-cached system prompt;
                                        rate-limited 20/min/IP.
- GET  /api/auth/me now returns role.

Backend — services:
- zoho_parse.py: extracts ~20 fields (brand, division, hub, userRole,
  projectType, assetCount, projectStatus, project start/end dates,
  userAgency, employingCompany, sageJobProfile, …) with back-compat
  aliases so existing callers keep working.
- parse_store.py: in-process TTL-cached registry of parsed uploads keyed
  by content hash. Lets endpoints reference an upload without re-sending it.
- forecast.py: working-day overlap math, exit-rate, weekly throughput
  baseline, capacity decision string mirroring the original wording.
- project_types.py: per-type aggregation + concentration-risk insights.
- timelog_filters.py: server-side filter by brands/divisions/hubs/roles.
- ai_context.py: builds the dashboard context block fed to Claude.

Frontend — new pages + components:
- pages/Forecast.tsx                 — ComposedChart (stacked bars + line)
                                        + capacity-decision banner + table
- pages/ProjectTypeSummary.tsx       — sortable table + small trend chart
- pages/TimeLogDetail.tsx            — virtualised, searchable, sortable
                                        view over all parsed timelog rows
- components/ChatView.tsx            — floating side panel with Claude.
                                        6 preset prompts mirroring the
                                        original. Visible only for roles
                                        with chat access.
- components/ChatToggle.tsx          — bottom-right FAB.
- components/StatsBar.tsx            — always-visible: Time Entries /
                                        People / Projects / Total Hours /
                                        Date Range.
- hooks/useDataContext.tsx           — single source of truth for filter
                                        state + parsed upload + filter
                                        dimensions (brands/divs/hubs/
                                        roles derived from uploads).

Frontend — modified:
- App.tsx, Navbar.tsx                — 7 tabs + role gating per the
                                        original TAB_ACCESS matrix.
- hooks/useAuth.tsx                  — role + canAccess(tab).
- lib/filters.ts, FilterBar.tsx      — Brand / Division / Hub / Role
                                        multiselects added (additive — keep
                                        Department / Name / Billing).
- pages/Department, Resourcing,
  Bookings, Tutorial.tsx             — wired into DataContext; tutorial
                                        is now a single 9-step global tour
                                        mirroring the original's narrative.

Config:
- backend/.env.example: ADMIN_ROLE, ANTHROPIC_API_KEY, ANTHROPIC_MODEL.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 21:40:03 -04:00

54 lines
1.6 KiB
Python

"""Chat endpoint tests."""
from __future__ import annotations
import os
import pytest
from fastapi.testclient import TestClient
def _client_with_bypass():
os.environ["DEV_AUTH_BYPASS"] = "true"
os.environ["ANTHROPIC_API_KEY"] = ""
from app.config import get_settings
get_settings.cache_clear()
import importlib
import app.main as main_mod
importlib.reload(main_mod)
return TestClient(main_mod.app, base_url="https://testserver")
def teardown_module(_mod):
os.environ["DEV_AUTH_BYPASS"] = "false"
os.environ.pop("ANTHROPIC_API_KEY", None)
from app.config import get_settings
get_settings.cache_clear()
import importlib
import app.main as main_mod
importlib.reload(main_mod)
def test_chat_503_when_no_api_key():
c = _client_with_bypass()
r = c.post("/api/chat", json={
"messages": [{"role": "user", "content": "hi"}],
})
assert r.status_code == 503
body = r.json()
assert "AI chat is not configured" in body["detail"]
def test_chat_400_when_empty_messages():
# Even with the key set, we should refuse empty messages.
os.environ["DEV_AUTH_BYPASS"] = "true"
os.environ["ANTHROPIC_API_KEY"] = "sk-test"
from app.config import get_settings
get_settings.cache_clear()
import importlib
import app.main as main_mod
importlib.reload(main_mod)
c = TestClient(main_mod.app, base_url="https://testserver")
r = c.post("/api/chat", json={"messages": []})
# 422 from pydantic validation or 400 from our own check — either is acceptable.
assert r.status_code in (400, 422)