Ctrl K
ComunidadGuías…

tests/test_calendar_reminder_minutes_parsing.py

Rama dev · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
"""do_manage_calendar must honour abbreviated reminder phrasings like "mins"/"hrs".

`_reminder_minutes` parsed the reminder offset with regexes anchored on
`(?:m|min|minute|minutes)\b` / `(?:h|hr|hour|hours)\b`. The trailing `\b`
made the very common plural abbreviations "mins" and "hrs" fail to match
(after "min" the next char "s" is a word char, so no boundary), so a request
like ``reminder_minutes: "5 mins"`` silently produced no reminder at all —
even though the sibling duration parser (no `\b`) already accepted them.
"""

import json
import sys
import uuid

import pytest

from tests.helpers.import_state import clear_fake_database_modules
from tests.helpers.sqlite_db import make_temp_sqlite

clear_fake_database_modules()

import core.database as cdb
from core.database import Note

_TS, _ENGINE, _TMPDB = make_temp_sqlite(cdb.Base.metadata)


@pytest.fixture(autouse=True)
def _bind_temp_db(monkeypatch):
    monkeypatch.setitem(sys.modules, "core.database", cdb)
    parent = sys.modules.get("core")
    if parent is not None:
        monkeypatch.setattr(parent, "database", cdb, raising=False)
    monkeypatch.setattr(cdb, "SessionLocal", _TS)
    yield


async def _create_with_reminder(reminder, owner):
    from src.tool_implementations import do_manage_calendar

    payload = {
        "action": "create_event",
        "summary": "Dentist",
        # Far-future so the reminder is never "already passed".
        "dtstart": "2030-01-01T10:00:00",
        "reminder_minutes": reminder,
    }
    return await do_manage_calendar(json.dumps(payload), owner=owner)


@pytest.mark.parametrize("reminder,expected", [
    ("5 mins", 5),
    ("10 mins", 10),
    ("2 hrs", 120),
    ("1 hr", 60),
    ("15 minutes", 15),   # regression: long form still works
    ("30m", 30),          # regression: bare unit still works
])
async def test_reminder_minutes_accepts_abbreviations(reminder, expected):
    owner = "tester-" + uuid.uuid4().hex[:6]
    res = await _create_with_reminder(reminder, owner)
    assert res.get("exit_code") == 0, res
    assert f"reminder {expected} min before" in res.get("response", ""), res
    assert res.get("reminder_note_id"), res
    assert res.get("reminder_minutes") == expected, res
    assert res.get("dtstart") == "2030-01-01T10:00:00", res

    db = _TS()
    try:
        note = (
            db.query(Note)
            .filter(Note.owner == owner, Note.title == "Calendar reminder: Dentist")
            .first()
        )
        assert note is not None, "reminder note should have been created"
    finally:
        db.close()


async def test_no_reminder_when_offset_absent():
    owner = "tester-" + uuid.uuid4().hex[:6]
    from src.tool_implementations import do_manage_calendar

    payload = {
        "action": "create_event",
        "summary": "No Reminder Event",
        "dtstart": "2030-02-01T10:00:00",
    }
    res = await do_manage_calendar(json.dumps(payload), owner=owner)
    assert res.get("exit_code") == 0, res
    assert "reminder set" not in res.get("response", ""), res


async def test_update_event_can_add_reminder_after_creation():
    owner = "tester-" + uuid.uuid4().hex[:6]
    from src.tool_implementations import do_manage_calendar

    created = await do_manage_calendar(json.dumps({
        "action": "create_event",
        "summary": "Kindergarten pickup",
        "dtstart": "2030-03-01T15:00:00",
    }), owner=owner)
    assert created.get("exit_code") == 0, created

    updated = await do_manage_calendar(json.dumps({
        "action": "update_event",
        "uid": created["uid"],
        "reminder_minutes": 15,
    }), owner=owner)
    assert updated.get("exit_code") == 0, updated
    assert "reminder set 15 min before" in updated.get("response", ""), updated
    assert updated.get("reminder_note_id")
    assert updated.get("reminder_minutes") == 15, updated
    assert updated.get("dtstart") == "2030-03-01T15:00:00", updated

    db = _TS()
    try:
        note = (
            db.query(Note)
            .filter(Note.owner == owner, Note.title == "Calendar reminder: Kindergarten pickup")
            .first()
        )
        assert note is not None, "update_event should create the reminder note"
    finally:
        db.close()


async def test_reminder_and_duration_parsers_stay_linear_on_digit_and_space_floods():
    """CodeQL py/polynomial-redos: `(\\d+)\\s*unit` rescanned a digit run from
    every offset and `alarm\\s*:?\\s*\\d+` split one whitespace run two ways.
    Tool arguments come from model output, so keep them O(n)."""
    import time
    from src.tool_implementations import do_manage_calendar

    owner = "tester-" + uuid.uuid4().hex[:6]
    started = time.perf_counter()
    res = await do_manage_calendar(json.dumps({
        "action": "create_event",
        "summary": "Flood",
        "dtstart": "2030-04-01T10:00:00",
        "reminder_minutes": "0" * 40_000 + "x",
        "duration": "0" * 40_000 + "x",
    }), owner=owner)
    assert time.perf_counter() - started < 2.0
    assert res.get("exit_code") == 0, res
    assert "reminder set" not in res.get("response", ""), res

    # A reminder is set, so the "is the description only a reminder?" check runs.
    started = time.perf_counter()
    res = await do_manage_calendar(json.dumps({
        "action": "create_event",
        "summary": "Flood 2",
        "dtstart": "2030-04-02T10:00:00",
        "description": "alarm" + "\t" * 40_000,
        "reminder_minutes": 5,
    }), owner=owner)
    assert time.perf_counter() - started < 2.0
    assert res.get("exit_code") == 0, res
    assert "reminder 5 min before" in res.get("response", ""), res