from __future__ import annotations import aiosqlite from backend import notes from backend.config import settings def test_extract_templates(): assert notes.extract_templates("Daily log\n$journal\n# Journal for %date%") == ["journal"] def test_extract_templates_dedup_and_case(): assert notes.extract_templates("$Journal $journal $JOURNAL") == ["journal"] def test_extract_templates_multiple(): assert notes.extract_templates("$journal and $standup") == ["journal", "standup"] def test_extract_templates_ignores_prices(): # first char must be a letter — "$5" is not a template marker assert notes.extract_templates("Lunch was $5, coffee $3") == [] def test_templates_are_not_tags(): body = "$journal #diary" assert notes.extract_templates(body) == ["journal"] assert notes.extract_tags(body) == ["diary"] async def test_templates_endpoint_lists_definitions(client): r = await client.post("/api/notes", json={"body": "# Journal for %date%\n\n$journal\n\n- "}) assert r.status_code == 200 r = await client.get("/api/templates") assert r.status_code == 200 names = [t["name"] for t in r.json()] assert "journal" in names tpl = next(t for t in r.json() if t["name"] == "journal") assert tpl["username"] == "schmeeve" assert tpl["title"] == "Journal for %date%" async def test_get_template_returns_body(client): await client.post("/api/notes", json={"body": "# Journal for %date%\n\n$journal\n"}) r = await client.get("/api/templates/journal") assert r.status_code == 200 assert r.json()["body"].startswith("# Journal for %date%") assert r.json()["name"] == "journal" async def test_get_template_404(client): r = await client.get("/api/templates/nope") assert r.status_code == 404 async def test_get_template_name_normalization(client): await client.post("/api/notes", json={"body": "# T\n$Journal\n"}) r = await client.get("/api/templates/$JOURNAL") assert r.status_code == 200 assert r.json()["name"] == "journal" async def test_newest_note_wins_for_duplicate_template(client): await client.post("/api/notes", json={"body": "# Old journal\n$journal\nold"}) r = await client.post("/api/notes", json={"body": "# New journal\n$journal\nnew"}) new_id = r.json()["id"] r = await client.get("/api/templates/journal") assert r.json()["id"] == new_id assert "new" in r.json()["body"] async def test_user_space_beats_common_for_templates(client): await client.post( "/api/notes", json={"body": "# Common journal\n$journal\ncommon", "username": "common"} ) r = await client.post("/api/notes", json={"body": "# My journal\n$journal\nmine"}) own_id = r.json()["id"] r = await client.get("/api/templates/journal") assert r.json()["id"] == own_id assert r.json()["username"] == "schmeeve" async def test_removing_marker_removes_template(client): r = await client.post("/api/notes", json={"body": "# J\n$journal\n"}) note_id = r.json()["id"] await client.put(f"/api/notes/{note_id}", json={"body": "# J\nno marker\n"}) r = await client.get("/api/templates/journal") assert r.status_code == 404 async def test_deleting_note_removes_template(client): r = await client.post("/api/notes", json={"body": "# J\n$journal\n"}) note_id = r.json()["id"] await client.delete(f"/api/notes/{note_id}") r = await client.get("/api/templates/journal") assert r.status_code == 404 async def test_lookup_by_title(client): await client.post("/api/notes", json={"body": "# Journal for 2026-09-26\nentry\n"}) r = await client.get("/api/notes/lookup", params={"title": "Journal for 2026-09-26"}) assert r.status_code == 200 assert "entry" in r.json()["body"] r = await client.get("/api/notes/lookup", params={"title": "Journal for 1999-01-01"}) assert r.status_code == 404 async def test_lookup_scoped_to_user(client): await client.post("/api/notes", json={"body": "# Shared title\n", "username": "common"}) r = await client.get("/api/notes/lookup", params={"title": "Shared title"}) assert r.status_code == 404 # schmeeve's space only async def test_lookup_requires_auth(remote_client): r = await remote_client.get("/api/notes/lookup", params={"title": "x"}) assert r.status_code == 401 async def test_templates_require_auth(remote_client): assert (await remote_client.get("/api/templates")).status_code == 401 assert (await remote_client.get("/api/templates/x")).status_code == 401 async def test_backfill_templates(client): r = await client.post("/api/notes", json={"body": "# J\n$journal\n"}) note_id = r.json()["id"] async with aiosqlite.connect(settings.database_path) as db: await db.execute("DELETE FROM note_templates WHERE note_id = ?", (note_id,)) await db.commit() await notes.backfill_templates() r = await client.get("/api/templates/journal") assert r.status_code == 200 assert r.json()["id"] == note_id async def test_cli_install_script(client): r = await client.get("/schmeeve/cli") assert r.status_code == 200 assert "__SNOTES_URL__" not in r.text assert "__SNOTES_USER__" not in r.text assert 'SNOTES_USER="schmeeve"' in r.text assert 'SNOTES_URL="http://test"' in r.text assert "curl -fsSL" in r.text async def test_cli_install_script_unknown_user(client): r = await client.get("/ghost/cli") assert r.status_code == 404 async def test_cli_install_script_is_public(remote_client): r = await remote_client.get("/schmeeve/cli") assert r.status_code == 200 assert 'SNOTES_USER="schmeeve"' in r.text async def test_cli_script_served(client): r = await client.get("/cli/snotes.py") assert r.status_code == 200 assert "# /// script" in r.text assert "textual" in r.text