Skip to content

feat: addeed admin dashboard#17

Merged
mahimairaja merged 6 commits intomainfrom
feat/add-heart-dashboard
Feb 15, 2026
Merged

feat: addeed admin dashboard#17
mahimairaja merged 6 commits intomainfrom
feat/add-heart-dashboard

Conversation

@mahimairaja
Copy link
Copy Markdown
Collaborator

@mahimairaja mahimairaja commented Feb 15, 2026

Summary by CodeRabbit

  • New Features

    • Dashboard: login with API key, overview stats, trends, session list and session detail pages; pause/resume screening and shareable screening link; frontend UI components and hooks for browsing, filtering, sorting and searching sessions.
  • Bug Fixes

    • Paused screening now returns 403 Forbidden with clearer pause messaging; public profiles expose active status and optional message.
  • Chores

    • New DASHBOARD_API_KEY configuration and test env var.
  • Tests

    • Extensive dashboard auth, contract, stats, sessions, trends and session-detail test suites added.

@mahimairaja mahimairaja self-assigned this Feb 15, 2026
@mahimairaja mahimairaja added the enhancement New feature or request label Feb 15, 2026
@vercel
Copy link
Copy Markdown

vercel bot commented Feb 15, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
waterloo-ai-agents-hackathon2026 Ready Ready Preview, Comment Feb 15, 2026 3:52am

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Feb 15, 2026

Caution

Review failed

The pull request is closed.

📝 Walkthrough

Walkthrough

Adds a dashboard feature: authenticated dashboard API endpoints (stats, sessions, session detail, trends, heart status), dashboard API key support and dependency, heart deactivation DB column and migration, extensive backend schemas/tests, OpenAPI updates, and a complete frontend dashboard client, UI, hooks, and types.

Changes

Cohort / File(s) Summary
Config & Env
backend/.env.example, backend/src/core/config.py
New DASHBOARD_API_KEY env/config entry.
DB Schema & Model
backend/migrations/versions/443b26386e80_add_heart_deactive.py, backend/src/models/heart_model.py
Adds nullable timezone-aware deactivated_at column on hearts with migration.
Auth & Dependencies
backend/src/dependencies.py, backend/src/api/routes.py
New verify_dashboard_access dependency and dashboard router mounting; X-Dashboard-Key header handling.
Backend Dashboard API & Schemas
backend/src/api/v1/endpoints/dashboard.py, backend/src/schemas/dashboard_schema.py
New dashboard endpoints (stats, sessions list/detail, trends, heart status GET/PATCH), many Pydantic schemas, helpers and in-process caching.
Public API & Session Behavior
backend/src/api/v1/endpoints/public.py, backend/src/api/v1/endpoints/sessions.py, backend/src/schemas/public_schema.py, backend/src/services/config_loader.py
PublicHeartProfileResponse extended with active and message; sessions endpoint returns 403 when paused; removed forced is_active reset on update.
Backend Tests & Fixtures
backend/src/tests/* (many new files), backend/src/tests/conftest.py, backend/src/tests/test_dashboard_api.py
Large test suite and fixtures for dashboard auth, contract, stats, sessions, detail, trends, heart status, integrity; test scaffolding and fake M7 DB/session utilities.
OpenAPI & Generated Models
frontend/openapi.json, shared/openapi.json, frontend/src/api/model/*, frontend/src/api/model/index.ts
OpenAPI updated with dashboard endpoints, security header renamed to X-Dashboard-Key; many generated TypeScript models and re-exports added.
Frontend API Client & Types
frontend/src/api/dashboard.ts, frontend/src/types/dashboard.ts
New dashboard client functions (verifyDashboardKey, getStats, getTrends, getSessions, getSessionDetail, heart status toggle) and consolidated dashboard types.
Frontend UI, Hooks & Pages
frontend/src/components/dashboard/*, frontend/src/pages/dashboard/*, frontend/src/hooks/*, frontend/src/App.tsx
New dashboard UI components, pages (Login, Overview, Sessions, SessionDetail), auth wrapper, react-query hooks, routing and sessionStorage key handling.
Frontend deps & tooling
frontend/package.json
Added recharts dependency for charting.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Frontend
    participant API
    participant DB
    participant Cache

    rect rgba(200,200,255,0.5)
    User->>Frontend: Enter dashboard API key (login)
    Frontend->>API: GET /api/v1/dashboard/stats (X-Dashboard-Key)
    API->>API: verify_dashboard_access(header)
    API->>DB: Query heart metadata & aggregated stats
    DB-->>API: Return rows
    API->>Cache: Store aggregated result (TTL 60s)
    API-->>Frontend: DashboardStatsResponse
    Frontend->>Frontend: Render dashboard UI
    end

    rect rgba(200,255,200,0.5)
    User->>Frontend: Toggle heart active state
    Frontend->>API: PATCH /api/v1/dashboard/heart/status (X-Dashboard-Key, payload)
    API->>DB: Update heart.is_active / deactivated_at
    API->>Cache: Invalidate cached entries for heart
    API-->>Frontend: DashboardHeartStatusResponse
    Frontend->>Frontend: Refetch stats/trends and update UI
    end
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

  • feat: addeed admin dashboard #17 — Very similar dashboard additions across backend (DASHBOARD_API_KEY, verify_dashboard_access, deactivated_at, endpoints) and frontend (dashboard client/models/pages); strong overlap.
  • Feat: sync back frontend #14 — Overlaps OpenAPI/model generation and frontend API surface changes that affect generated TS models and routing.
  • Frontend #6 — Related frontend routing/component additions (App.tsx) and dashboard page integration.

Poem

🐰 I found a tiny dashboard key,

I hopped through stats and sessions free,
Hearts can pause, then bloom anew,
Cached numbers sparkle, charts in view,
Hop—analytics bounce with me!

🚥 Pre-merge checks | ✅ 2 | ❌ 2
❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Title check ⚠️ Warning The PR title has a typo ('addeed' instead of 'added') and is vague; it doesn't clearly indicate the main changes which involve a comprehensive dashboard API implementation with authentication, multiple endpoints, database migrations, and frontend components. Revise the title to be more specific and accurate, for example: 'feat: implement admin dashboard API with authentication and session management' or similar to better reflect the scope of the changeset.
Docstring Coverage ⚠️ Warning Docstring coverage is 3.93% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into main

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/add-heart-dashboard

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

🤖 Fix all issues with AI agents
In `@backend/src/api/v1/endpoints/dashboard.py`:
- Around line 619-639: The trend query and DashboardTrendPoint construction are
producing empty-date points because date_trunc is applied to
SessionDb.started_at which can be NULL; update the query used in trend_query to
bucket by coalesce(SessionDb.started_at, SessionDb.created_at) (or add a filter
to exclude rows where SessionDb.started_at is NULL) so the bucket is never NULL,
and keep the DashboardTrendPoint mapping the same—look for the
SessionDb.started_at/created_at usage and the trend_query/group_by(bucket) logic
to apply coalesce or the additional where clause.
- Around line 280-287: The function signature for get_dashboard_sessions
currently uses Query(...) calls as default values (page, per_page, verdict,
sort_by, sort_order, search, date_from, date_to) which triggers Ruff B008;
change each parameter to use Annotated[...] metadata instead of calling Query in
the default, e.g. import Annotated and replace occurrences like "page: int =
Query(default=1, ge=1)" with "page: Annotated[int, Query(default=1, ge=1)] = 1"
(and similarly for per_page, verdict, sort_by, sort_order, search, date_from,
date_to) so that Query(...) is part of the type annotation rather than a default
value.

In `@frontend/src/components/dashboard/SessionFilters.tsx`:
- Around line 42-45: The sort change handler in SessionFilters.tsx currently
updates sort_by and sort_order but doesn't reset pagination, causing empty
pages; modify the onChange callback inside the onChange={(e) => { ... }} block
to also set page: 1 when calling the parent onChange (i.e., call onChange({
...filters, sort_by, sort_order, page: 1 })) so that changing sort always resets
to the first page; update any related helper/handler that reads filters to
expect page to be provided on sort changes.
- Around line 10-52: Add accessible labels for each filter control (the search
input, verdict select, date_from and date_to inputs, and the sort select) so
screen readers can identify them: update the JSX around the inputs that use
filters, value and onChange (the search input, the verdict <select>, the date
inputs bound to filters.date_from and filters.date_to, and the sort <select>
that parses sort_by/sort_order) to include either a <label htmlFor="..."> with
matching id on the control or an aria-label/aria-labelledby attribute with a
visually-hidden label element; ensure ids are unique (e.g., search-input,
verdict-select, date-from-input, date-to-input, sort-select) and preserve
existing value/onChange behavior when adding the labels.

In `@frontend/src/hooks/useDashboardAuth.ts`:
- Around line 1-18: The hook reads STORAGE_KEY from localStorage once via
useMemo causing stale apiKey/isAuthenticated; change useDashboardAuth to use
useState for apiKey and derive isAuthenticated from that state, initialize from
sessionStorage (use sessionStorage instead of localStorage), update the state
inside login(key) after verifyDashboardKey and inside logout() after removing
the key from sessionStorage so consumers re-render correctly, and simplify the
hook's return to only expose isAuthenticated, login, and logout (remove unused
returned values).

In `@frontend/src/hooks/useDashboardStats.ts`:
- Around line 4-15: The dashboard queries (useDashboardStats -> statsQuery and
trendsQuery) are using static queryKey values so cached data can be shared
across different signed-in admins; include the current auth identity (e.g.
dashboard API key, user id, or session token) in the queryKey for both
statsQuery and trendsQuery and pass it through to the query functions (e.g. add
authKey to ['dashboard-stats', authKey] and ['dashboard-trends', authKey,
period, days]) so React Query scopes cache per user; ensure you read the auth
value from the existing auth context/session and handle undefined safely (do not
omit the authKey or use a constant) so cache never spills between users.

In `@frontend/src/pages/dashboard/DashboardLogin.tsx`:
- Around line 32-44: Add an explicit label element for the password input (match
label htmlFor to the input id) so the input bound to value={key} and
onChange={(e) => setKey(e.target.value)} is properly announced by screen
readers; mark the input aria-invalid={Boolean(error)} and add aria-describedby
pointing to the error message id; render the error text with role="alert" or
aria-live="assertive" and that same id so updates are announced; update the JSX
around the input, the error rendering, and any related props in
DashboardLogin.tsx to use these attributes.

In `@frontend/src/pages/dashboard/DashboardSessionDetail.tsx`:
- Around line 39-42: The rendering hides valid zero-length sessions because it
uses a truthy check on data.session.duration_seconds; update the conditional in
DashboardSessionDetail (the JSX that references data.session.started_at and
data.session.duration_seconds) to test for null/undefined explicitly (e.g.,
duration_seconds !== null && duration_seconds !== undefined) so that 0 is
treated as a valid value and still renders the minutes/seconds string
(Math.floor(duration_seconds/60) and duration_seconds % 60).

In `@frontend/src/pages/dashboard/DashboardSessions.tsx`:
- Around line 31-52: The UI currently treats missing data as an empty state;
update the rendering logic in DashboardSessions.tsx to check
sessionsQuery.isError (and use sessionsQuery.error?.message) before falling back
to the empty-state branch so real query failures show an error message to the
user (instead of "No sessions yet"); modify the ternary/conditional that uses
sessionsQuery.isLoading and data to add an isError branch that renders a clear
error UI (referencing sessionsQuery, sessionsQuery.error, data.sessions,
SessionCard, Pagination and setFilters) and preserve existing loading,
data-present, and empty-state behaviors.
🧹 Nitpick comments (9)
frontend/src/components/dashboard/PauseToggle.tsx (1)

9-20: Add ARIA state for toggle semantics.

This helps assistive tech understand the pressed/loading state without changing behavior.

♿ Suggested accessibility tweak
     <button
       type="button"
       disabled={isLoading}
+      aria-pressed={active}
+      aria-busy={isLoading}
+      aria-label={active ? 'Pause interviews' : 'Activate interviews'}
       onClick={() => onToggle(!active)}
       className={`inline-flex items-center gap-2 rounded-full border px-3 py-1 text-sm font-medium ${
         active
frontend/src/components/dashboard/DashboardLayout.tsx (1)

14-17: Use replace navigation on logout to avoid back-navigation to protected routes.
This keeps history clean after logout and reduces confusing back-button behavior.

♻️ Proposed change
-  const logout = () => {
-    localStorage.removeItem('dashboard_api_key');
-    navigate('/dashboard/login');
-  };
+  const logout = () => {
+    localStorage.removeItem('dashboard_api_key');
+    navigate('/dashboard/login', { replace: true });
+  };
backend/src/api/v1/endpoints/sessions.py (1)

269-273: Keep paused-state messaging consistent across endpoints.
start_session now uses “Screening is currently paused…”, while pre_check still says “Interviews are currently paused.” Consider aligning the wording (or centralizing it) to avoid mixed UX copy.

backend/src/dependencies.py (1)

22-47: Consider timing-safe comparison for API keys.
A timing-safe compare (secrets.compare_digest) slightly improves security posture and can be shared with the admin key check for consistency.

🔒 Suggested change
+import secrets
+
 async def verify_admin_key(api_key: str | None = Security(admin_key_header)) -> str:
     """Simple API key auth for admin endpoints."""
     expected = config.ADMIN_API_KEY
-    if not api_key or not expected or api_key != expected:
+    if not api_key or not expected or not secrets.compare_digest(api_key, expected):
         raise HTTPException(
             status_code=status.HTTP_403_FORBIDDEN,
             detail="Invalid admin API key",
         )
     return api_key
@@
 async def verify_dashboard_access(
     x_dashboard_key: str | None = Security(dashboard_key_header),
 ) -> str:
     """API key auth for dashboard endpoints."""
     expected = config.DASHBOARD_API_KEY
-    if not x_dashboard_key or not expected or x_dashboard_key != expected:
+    if (
+        not x_dashboard_key
+        or not expected
+        or not secrets.compare_digest(x_dashboard_key, expected)
+    ):
         raise HTTPException(
             status_code=status.HTTP_401_UNAUTHORIZED,
             detail="Unauthorized",
         )
     return x_dashboard_key
frontend/src/App.tsx (1)

17-22: Avoid hard-coding the dashboard storage key.
Line 18 duplicates the key string, which can drift from the value in the dashboard auth hook.

♻️ Suggested refactor (shared constant)
-import type { ReactElement } from 'react';
+import type { ReactElement } from 'react';
+import { DASHBOARD_STORAGE_KEY } from './hooks/useDashboardAuth';
 ...
 function RequireDashboardAuth({ children }: { children: ReactElement }) {
-  const hasKey = Boolean(localStorage.getItem('dashboard_api_key'));
+  const hasKey = Boolean(localStorage.getItem(DASHBOARD_STORAGE_KEY));
   if (!hasKey) {
     return <Navigate to="/dashboard/login" replace />;
   }
   return children;
 }

If DASHBOARD_STORAGE_KEY isn’t exported yet, consider moving it to a small shared constants module used by both places.

frontend/src/components/dashboard/TranscriptView.tsx (1)

15-35: Add aria-expanded / aria-controls for toggle accessibility.

Small a11y improvement for screen readers.

♿ Suggested tweak
         {transcript.map((turn) => {
           const isExpanded = Boolean(expanded[turn.turn]);
           const answer = turn.answer || '';
           const shouldCollapse = answer.length > 260;
           const shown = shouldCollapse && !isExpanded ? `${answer.slice(0, 260)}...` : answer;
+          const answerId = `transcript-answer-${turn.turn}`;
           return (
             <div key={turn.turn} className="rounded-lg border border-slate-200 p-3">
               <p className="text-sm font-semibold text-slate-800">🤖 Q{turn.turn}: {turn.question}</p>
-              <p className="mt-2 whitespace-pre-wrap text-sm text-slate-700">👤 A{turn.turn}: {shown}</p>
+              <p id={answerId} className="mt-2 whitespace-pre-wrap text-sm text-slate-700">
+                👤 A{turn.turn}: {shown}
+              </p>
               {shouldCollapse && (
                 <button
                   type="button"
                   className="mt-1 text-xs font-medium text-violet-600"
                   onClick={() => setExpanded((prev) => ({ ...prev, [turn.turn]: !isExpanded }))}
+                  aria-expanded={isExpanded}
+                  aria-controls={answerId}
                 >
                   {isExpanded ? 'Show less' : 'Show more'}
                 </button>
               )}
frontend/src/pages/dashboard/DashboardSessions.tsx (1)

17-18: debouncedFilters isn’t debounced; remove or implement a real debounce.

useMemo just returns the same object reference and doesn’t throttle requests, so the name is misleading. Either remove it or replace with an actual debounce/deferred value.

♻️ Suggested simplification
-  const debouncedFilters = useMemo(() => filters, [filters]);
-  const sessionsQuery = useSessions(debouncedFilters);
+  const sessionsQuery = useSessions(filters);
frontend/src/types/dashboard.ts (1)

135-143: Constrain SessionQueryParams to the allowed enum values.

Using broad string types makes it easy to send invalid query values.

♻️ Suggested typing refinement
 export interface SessionQueryParams {
   page?: number;
   per_page?: number;
-  verdict?: string;
-  sort_by?: string;
-  sort_order?: string;
+  verdict?: 'date' | 'no_date' | 'pending';
+  sort_by?: 'date' | 'score' | 'name';
+  sort_order?: 'asc' | 'desc';
   search?: string;
   date_from?: string;
   date_to?: string;
 }
backend/src/api/v1/endpoints/dashboard.py (1)

62-74: Use get_heart_id() dependency or fail fast when app.state.heart_id is unset.

_resolve_heart silently falls back to the most recently created heart instead of following the fail-fast pattern established by get_heart_id() in dependencies.py. While startup guarantees app.state.heart_id is set (via events.py lifespan), the fallback behavior is inconsistent with the codebase's defensive patterns and defeats the explicit 503 error strategy used elsewhere. Refactor to either inject heart_id via the existing get_heart_id() dependency or match that function's fail-fast approach.

Comment thread backend/src/api/v1/endpoints/dashboard.py Outdated
Comment thread backend/src/api/v1/endpoints/dashboard.py
Comment thread frontend/src/components/dashboard/SessionFilters.tsx
Comment thread frontend/src/components/dashboard/SessionFilters.tsx
Comment thread frontend/src/hooks/useDashboardAuth.ts Outdated
Comment thread frontend/src/hooks/useDashboardStats.ts
Comment thread frontend/src/pages/dashboard/DashboardLogin.tsx Outdated
Comment thread frontend/src/pages/dashboard/DashboardSessionDetail.tsx
Comment thread frontend/src/pages/dashboard/DashboardSessions.tsx
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In `@backend/src/tests/conftest.py`:
- Line 45: The DASHBOARD_API_KEY environment variable set with
os.environ.setdefault("DASHBOARD_API_KEY", "dashboard-test-key") does not match
the test constant VALID_DASHBOARD_KEY ("test-dashboard-key-12345"), causing auth
failures in fixtures like client and dashboard_headers; fix by making them
identical—either change the os.environ.setdefault call to use
VALID_DASHBOARD_KEY's value or update VALID_DASHBOARD_KEY to match the
environment value so both DASHBOARD_API_KEY and VALID_DASHBOARD_KEY reference
the same string used by the client and dashboard_headers fixtures.

In `@backend/src/tests/test_m7_dashboard_api_contract.py`:
- Around line 8-17: The autouse fixture _patch_dashboard_key sets
config_module.config.DASHBOARD_API_KEY after the FastAPI app/client is already
initialized which can cause async loop mismatch; change the approach so the
config is patched before app creation—either make the fixture session-scoped and
ensure it runs before the client fixture, remove autouse and add an explicit
dependency from the client or tests to _patch_dashboard_key, or set the
DASHBOARD_API_KEY environment variable prior to importing src.core.config so
that config_module.config.DASHBOARD_API_KEY is set at import time; reference the
_patch_dashboard_key fixture, monkeypatch, and
config_module.config.DASHBOARD_API_KEY when making the change.

In `@backend/src/tests/test_m7_dashboard_heart_status.py`:
- Around line 294-302: Replace the broad Exception assertions in tests
test_m7_status_014_patch_missing_active_field and
test_m7_status_015_patch_invalid_active_type so they catch
pydantic.ValidationError raised by
DashboardHeartStatusPatchRequest.model_validate, i.e., import
pydantic.ValidationError in the test file and use pytest.raises(ValidationError)
around the DashboardHeartStatusPatchRequest.model_validate(...) calls to assert
the specific validation error.

In `@backend/src/tests/test_m7_dashboard_sessions.py`:
- Around line 627-628: The assertion is tautological; replace it with a real
descending-order check by comparing the original list of scores (vals, derived
from out.sessions) to a version sorted in descending order or by asserting each
adjacent pair is non-increasing; specifically, compute vals =
[s.scores.aggregate for s in out.sessions if s.scores] and then assert vals ==
sorted(vals, reverse=True) (or assert all(vals[i] >= vals[i+1] for i in
range(len(vals)-1))) so the test actually verifies the endpoint returns scores
in descending order.
🧹 Nitpick comments (9)
backend/src/tests/test_m7_dashboard_integrity.py (2)

147-152: Test only validates fixture data, not actual endpoint isolation.

This test asserts that all sessions in the fixture have the same heart_id, which is expected since the fixture creates them that way. It doesn't actually test that the endpoint prevents cross-heart data leakage.

To properly test isolation, you'd need to create sessions for a different heart and verify they're not returned when querying for m7_seeded_heart.


207-221: SQL injection test uses mocked DB—doesn't validate actual SQL safety.

The test passes a SQL injection payload as a search parameter, but since _build_sessions_db returns a fake DB with predefined results, this doesn't actually test whether the endpoint properly sanitizes input against real SQL execution. The test will always pass regardless of actual SQL injection vulnerability.

Consider adding an integration test with a real database or verifying that the search parameter is properly parameterized in the query builder.

backend/src/tests/test_m7_dashboard_api_contract.py (1)

130-136: Non-deterministic assertion weakens test validity.

The assertion resp.status_code in (200, 422) doesn't definitively test the expected behavior for an invalid date range (start after end). If the API should reject this as invalid input, assert 422; if it should return empty results, assert 200.

backend/src/tests/test_m7_dashboard_auth.py (1)

9-9: Duplicate constant—consider importing from conftest.

VALID_DASHBOARD_KEY is also defined in conftest.py. Import it to maintain a single source of truth:

♻️ Proposed refactor
-VALID_DASHBOARD_KEY = "test-dashboard-key-12345"
+from tests.conftest import VALID_DASHBOARD_KEY
backend/src/tests/test_m7_dashboard_trends.py (1)

189-204: Consider consistent config patching approach.

This test uses the same monkeypatch pattern that may cause async loop issues (as seen in pipeline failures for test_m7_dashboard_api_contract.py). If those failures are resolved with a different approach, apply the same fix here.

backend/src/tests/test_m7_dashboard_stats.py (4)

45-53: Test name is misleading.

The test name suggests it verifies an HTTP 200 status code, but it calls get_dashboard_stats directly without making an HTTP request. Consider renaming to something like test_m7_stats_001_succeeds_with_valid_auth or add an actual HTTP test via the test client if HTTP status verification is intended.


105-127: Consider adding coverage for SCORING and SCORED statuses.

Per the implementation, completed_sessions aggregates counts for COMPLETED, SCORING, and SCORED statuses. This test only exercises the COMPLETED status. Adding a test case with SCORING or SCORED rows would verify that all three statuses are correctly included in completed_sessions.


249-267: Test name doesn't match test behavior.

The test is named score_distribution_boundary_values, which implies testing scores at tier boundaries (e.g., 79.9 vs 80.0). However, the test merely verifies that pre-aggregated tier counts are returned correctly—similar to test_012. If boundary testing is intended, it would require providing scores near thresholds and verifying the SQL CASE expression categorization, which this mock-based approach cannot do.

Consider renaming to test_m7_stats_013_score_distribution_all_tiers_present or adding an integration test with actual scores at boundary values.


326-342: Consider adding an edge case for booking_rate when total_dates is zero.

The implementation handles division by zero by returning booking_rate = 0.0 when total_dates is 0. Adding a test case to verify this behavior would strengthen coverage:

`@pytest.mark.asyncio`
async def test_m7_stats_019_booking_rate_zero_when_no_dates(
    dashboard_request, m7_seeded_heart, make_fake_db_m7, fake_result_builder_m7
):
    dashboard_request.app.state.heart_id = m7_seeded_heart.id
    db = _build_stats_db(
        make_fake_db_m7,
        fake_result_builder_m7,
        booking_total=5,
        heart=m7_seeded_heart,
    )
    out = await get_dashboard_stats(dashboard_request, "ok", db)
    assert out.bookings.booking_rate == 0.0

Comment thread backend/src/tests/conftest.py
Comment thread backend/src/tests/test_m7_dashboard_api_contract.py
Comment thread backend/src/tests/test_m7_dashboard_heart_status.py Outdated
Comment thread backend/src/tests/test_m7_dashboard_sessions.py
@mahimairaja mahimairaja merged commit 1c64226 into main Feb 15, 2026
2 checks passed
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@backend/src/tests/test_m7_dashboard_sessions.py`:
- Around line 682-711: The test test_m7_sessions_026_sort_by_name currently
asserts the suitor names using a set, which ignores order; change the assertion
to verify ordering by extracting the names in order (e.g., [s.suitor_name for s
in out.sessions]) and assert equality to the expected ordered list ["Alex",
"Bella", "Charlie"] so the sort_by="name", sort_order="asc" behavior is properly
tested.
🧹 Nitpick comments (2)
backend/src/tests/test_m7_dashboard_sessions.py (1)

35-59: Drop unused *args to keep the helper signature clean.

_call_sessions doesn’t use *args, and Ruff flagged it; removing it clarifies intent.

♻️ Proposed fix
-async def _call_sessions(func, request, auth, db, *args, **kwargs):
+async def _call_sessions(func, request, auth, db, **kwargs):
backend/src/tests/test_m7_dashboard_auth.py (1)

61-76: Remove unused client fixture parameters.

These tests don’t use the fixture, and Ruff flagged it.

♻️ Proposed fix
-async def test_m7_auth_006_auth_does_not_affect_public_endpoints(client):
+async def test_m7_auth_006_auth_does_not_affect_public_endpoints():
@@
-async def test_m7_auth_007_auth_does_not_affect_suitor_endpoints(client):
+async def test_m7_auth_007_auth_does_not_affect_suitor_endpoints():

Comment on lines +682 to +711
@pytest.mark.asyncio
async def test_m7_sessions_026_sort_by_name(
dashboard_request,
m7_seeded_heart,
m7_sample_sessions,
make_fake_db_m7,
fake_result_builder_m7,
m7_sample_suitors,
):
dashboard_request.app.state.heart_id = m7_seeded_heart.id
m7_sample_suitors[0].name = "Charlie"
m7_sample_suitors[1].name = "Alex"
m7_sample_suitors[2].name = "Bella"
rows = _rows_for_sessions(m7_sample_sessions[:3], m7_sample_suitors[:3])
db = _build_db_for_sessions(
make_fake_db_m7,
fake_result_builder_m7,
total=3,
rows=rows,
heart=m7_seeded_heart,
)
out = await _call_sessions(
get_dashboard_sessions,
dashboard_request,
"ok",
db,
sort_by="name",
sort_order="asc",
)
assert {s.suitor_name for s in out.sessions} == {"Alex", "Bella", "Charlie"}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Assert actual order for name sorting.

Using a set only checks membership and won’t fail if the order is wrong.

🔧 Proposed fix
-    assert {s.suitor_name for s in out.sessions} == {"Alex", "Bella", "Charlie"}
+    assert [s.suitor_name for s in out.sessions] == ["Alex", "Bella", "Charlie"]
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@pytest.mark.asyncio
async def test_m7_sessions_026_sort_by_name(
dashboard_request,
m7_seeded_heart,
m7_sample_sessions,
make_fake_db_m7,
fake_result_builder_m7,
m7_sample_suitors,
):
dashboard_request.app.state.heart_id = m7_seeded_heart.id
m7_sample_suitors[0].name = "Charlie"
m7_sample_suitors[1].name = "Alex"
m7_sample_suitors[2].name = "Bella"
rows = _rows_for_sessions(m7_sample_sessions[:3], m7_sample_suitors[:3])
db = _build_db_for_sessions(
make_fake_db_m7,
fake_result_builder_m7,
total=3,
rows=rows,
heart=m7_seeded_heart,
)
out = await _call_sessions(
get_dashboard_sessions,
dashboard_request,
"ok",
db,
sort_by="name",
sort_order="asc",
)
assert {s.suitor_name for s in out.sessions} == {"Alex", "Bella", "Charlie"}
`@pytest.mark.asyncio`
async def test_m7_sessions_026_sort_by_name(
dashboard_request,
m7_seeded_heart,
m7_sample_sessions,
make_fake_db_m7,
fake_result_builder_m7,
m7_sample_suitors,
):
dashboard_request.app.state.heart_id = m7_seeded_heart.id
m7_sample_suitors[0].name = "Charlie"
m7_sample_suitors[1].name = "Alex"
m7_sample_suitors[2].name = "Bella"
rows = _rows_for_sessions(m7_sample_sessions[:3], m7_sample_suitors[:3])
db = _build_db_for_sessions(
make_fake_db_m7,
fake_result_builder_m7,
total=3,
rows=rows,
heart=m7_seeded_heart,
)
out = await _call_sessions(
get_dashboard_sessions,
dashboard_request,
"ok",
db,
sort_by="name",
sort_order="asc",
)
assert [s.suitor_name for s in out.sessions] == ["Alex", "Bella", "Charlie"]
🤖 Prompt for AI Agents
In `@backend/src/tests/test_m7_dashboard_sessions.py` around lines 682 - 711, The
test test_m7_sessions_026_sort_by_name currently asserts the suitor names using
a set, which ignores order; change the assertion to verify ordering by
extracting the names in order (e.g., [s.suitor_name for s in out.sessions]) and
assert equality to the expected ordered list ["Alex", "Bella", "Charlie"] so the
sort_by="name", sort_order="asc" behavior is properly tested.

@coderabbitai coderabbitai bot mentioned this pull request Feb 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant