Files

81 lines
3.3 KiB
Python
Raw Permalink Normal View History

"""Snooze: a conversation leaves the Inbox until a set time, then comes back."""
from __future__ import annotations
import datetime as dt
from sqlalchemy import select
from app.models import Message, Thread
from app.snooze import wake_due_job
from tests.api.conftest import HDR, MailCtx, raw_mail
def _in(days: float = 1) -> str:
return (dt.datetime.now(dt.UTC) + dt.timedelta(days=days)).isoformat()
async def _inbox_ids(ctx: MailCtx) -> list[str]:
resp = await ctx.client.get("/api/threads", params={"view": "folder:inbox"})
return [t["id"] for t in resp.json()["threads"]]
async def test_snoozed_thread_leaves_the_inbox(mail_ctx: MailCtx) -> None:
old = await mail_ctx.deliver(
raw_mail("Old"), 1, at=dt.datetime.now(dt.UTC) - dt.timedelta(days=2)
)
new = await mail_ctx.deliver(raw_mail("New"), 2)
c = mail_ctx.client
resp = await c.post(f"/api/threads/{new.thread_id}/snooze", json={"until": _in()}, headers=HDR)
assert resp.status_code == 200, resp.text
assert await _inbox_ids(mail_ctx) == [str(old.thread_id)]
snoozed = (await c.get("/api/threads", params={"view": "snoozed"})).json()["threads"]
assert [t["id"] for t in snoozed] == [str(new.thread_id)]
assert snoozed[0]["snoozed_until"] is not None
counts = (await c.get("/api/mailboxes/counts")).json()
assert counts["snoozed"] == 1 and counts["inbox_unread"] == 1
# Unsnooze: straight back, at the top.
await c.post(f"/api/threads/{new.thread_id}/unsnooze", headers=HDR)
assert await _inbox_ids(mail_ctx) == [str(new.thread_id), str(old.thread_id)]
async def test_snooze_needs_a_future_time(mail_ctx: MailCtx) -> None:
msg = await mail_ctx.deliver(raw_mail("x"), 1)
for until in (_in(-1), _in(400), "2030-01-01T09:00:00"):
resp = await mail_ctx.client.post(
f"/api/threads/{msg.thread_id}/snooze", json={"until": until}, headers=HDR
)
assert resp.status_code == 400, until
async def test_due_thread_wakes_on_top_and_unread(mail_ctx: MailCtx) -> None:
week_ago = dt.datetime.now(dt.UTC) - dt.timedelta(days=7)
snoozed = await mail_ctx.deliver(raw_mail("Snoozed"), 1, seen=True, at=week_ago)
newer = await mail_ctx.deliver(raw_mail("Newer"), 2, seen=True)
thread = await mail_ctx.session.get(Thread, snoozed.thread_id)
assert thread is not None
thread.snoozed_until = dt.datetime.now(dt.UTC) - dt.timedelta(minutes=1)
thread.snoozed_at = week_ago
await mail_ctx.session.commit()
assert await wake_due_job({}) >= 1
assert await _inbox_ids(mail_ctx) == [str(snoozed.thread_id), str(newer.thread_id)]
snoozed_id = snoozed.id
mail_ctx.session.expire_all()
flags = await mail_ctx.session.scalar(select(Message.flags).where(Message.id == snoozed_id))
assert flags is not None and "\\Seen" not in flags
async def test_a_reply_wakes_a_snoozed_thread(mail_ctx: MailCtx) -> None:
first = await mail_ctx.deliver(raw_mail("Question", mid="<q@x>"), 1)
await mail_ctx.client.post(
f"/api/threads/{first.thread_id}/snooze", json={"until": _in(3)}, headers=HDR
)
m = raw_mail("Re: Question", headers={"In-Reply-To": "<q@x>", "References": "<q@x>"})
await mail_ctx.deliver(m, 2, at=dt.datetime.now(dt.UTC) + dt.timedelta(seconds=5))
await wake_due_job({})
assert await _inbox_ids(mail_ctx) == [str(first.thread_id)]