Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@
"files": {
"backend/routers/apps.py": 2335,
"backend/routers/chat.py": 1580,
"backend/routers/developer.py": 2186,
"backend/routers/developer.py": 2189,
"backend/routers/mcp_sse.py": 1857,
"backend/routers/sync.py": 2000,
"backend/routers/users.py": 2046
},
"raise_justifications": {
"backend/routers/chat.py": "PTT stereo rejection keeps the serving STT provider boundary explicit in the established chat admission owner.",
"backend/routers/developer.py": "The include_inactive branch of GET /v1/dev/user/goals now applies the limit its own docstring advertises; the bound sits at this existing route owner because get_all_goals stays deliberately unbounded for its five fetch-everything callers.",
"backend/routers/users.py": "GET /v1/users/subscription serializes the new mobile plus/max plans as `unlimited` for clients whose plan enum predates them, so day-one buyers read as paid instead of Free (mirrors the existing operator remap); real limits/grandfather are computed from the true plan first.",
"backend/routers/sync.py": "Fresh-sync admission gains a hard daily-audio anti-abuse ceiling gate (is_daily_audio_ceiling_exceeded), mirroring the adjacent hard-restriction gate, plus plan-aware soft-cap wiring; both belong at this existing ingestion owner, not a new module."
},
Expand Down
5 changes: 4 additions & 1 deletion backend/routers/developer.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import uuid

Check warning on line 1 in backend/routers/developer.py

View workflow job for this annotation

GitHub Actions / PR Metadata Preflight

Large changed file

backend/routers/developer.py is 2189 lines; consider splitting files over 800 lines.
from datetime import datetime, timezone, timedelta

from enum import Enum
Expand Down Expand Up @@ -1590,7 +1590,7 @@
)


def _create_conversation_from_segments(

Check warning on line 1593 in backend/routers/developer.py

View workflow job for this annotation

GitHub Actions / PR Metadata Preflight

Long function

_create_conversation_from_segments is 160 lines; consider extracting focused helpers over 150 lines.
uid: str,
request: CreateConversationFromTranscriptRequest,
*,
Expand Down Expand Up @@ -2021,7 +2021,10 @@
# oversized limit cannot stream the whole collection. Mirrors the GET /v3/memories hardening.
limit = max(1, min(limit, 1000))
if include_inactive:
goals = goals_db.get_all_goals(uid, include_inactive=True)
# get_all_goals takes no limit (it is also the "fetch everything" helper for
# /v1/dev/user/goals/{goal_id} and the MCP goal reads), so the clamp above is
# only a real bound once it is applied to the result here.
goals = goals_db.get_all_goals(uid, include_inactive=True)[:limit]
else:
goals = goals_db.get_user_goals(uid, limit=limit)

Expand Down
51 changes: 51 additions & 0 deletions backend/tests/unit/test_dev_goals_limit_include_inactive.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
"""Regression test: GET /v1/dev/user/goals must honour `limit` when include_inactive=true.

routers.developer.get_goals clamps `limit` to [1, 1000] and then branches. The default branch
calls goals_db.get_user_goals(uid, limit=limit), which is bounded. The include_inactive=True
branch called goals_db.get_all_goals(uid, include_inactive=True), which has no limit parameter
and streams the whole goals collection, so the clamp was dead code on that branch and the
endpoint returned every goal the user had ever created -- ignoring its own documented
"**limit**: Maximum number of goals to return". The limit is now applied to that result.

get_all_goals is deliberately unbounded (it is also the "fetch everything" helper behind
/v1/dev/user/goals/{goal_id}, routers/goals.py::get_all_goals and the MCP goal reads), so the
bound belongs at this call site rather than in the shared helper.
"""

import routers.developer as developer


def _fake_goals(count):
return [{'id': f'g{index}', 'title': f'goal {index}'} for index in range(count)]


def test_include_inactive_honours_limit(monkeypatch):
monkeypatch.setattr(developer.goals_db, 'get_all_goals', lambda uid, include_inactive=False: _fake_goals(25))

result = developer.get_goals(uid='u1', limit=5, include_inactive=True)

assert len(result) == 5
assert [goal['id'] for goal in result] == ['g0', 'g1', 'g2', 'g3', 'g4']


def test_include_inactive_applies_the_clamp_ceiling(monkeypatch):
monkeypatch.setattr(developer.goals_db, 'get_all_goals', lambda uid, include_inactive=False: _fake_goals(1500))

result = developer.get_goals(uid='u1', limit=99999, include_inactive=True)

assert len(result) == 1000


def test_active_only_branch_still_delegates_the_limit(monkeypatch):
captured = {}

def get_user_goals(uid, limit):
captured.update(uid=uid, limit=limit)
return _fake_goals(3)

monkeypatch.setattr(developer.goals_db, 'get_user_goals', get_user_goals)

result = developer.get_goals(uid='u1', limit=3, include_inactive=False)

assert captured == {'uid': 'u1', 'limit': 3}
assert len(result) == 3
Loading