Tests: pytest-Suite für Konten, Haushalt, Kanban, Reisen und Migrationen
- backend/tests: jede Test-Funktion bekommt eine frische Datenbank; test_migrationen.py baut ältere Datenbank-Stände nach und prüft die verlustfreie Umstellung (auch zweiter Start ohne Änderung) - requirements-dev.txt mit pytest - Fix (von den Tests gefunden): Eine neue, leere Datenbank bekam keine erste Liste "Zuhause"; jetzt einmalig beim ersten Start Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
dc4ce18593
commit
79cbaab092
10 files changed
+478
No files matched your search
@@ -0,0 +1,50 @@
|
||||
"""Gemeinsame Test-Hilfen: jede Test-Funktion bekommt eine frische, leere Datenbank.
|
||||
|
||||
Die App liest den Datenordner beim Import; deshalb zeigt P42_DATA_DIR schon vorher auf einen
|
||||
Wegwerf-Ordner. Pro Test wird db.DB_FILE auf eine neue Datei umgebogen und das Schema angelegt.
|
||||
Hintergrund-Aufgaben (lifespan) laufen nicht, weil TestClient ohne "with" benutzt wird.
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
os.environ["P42_DATA_DIR"] = tempfile.mkdtemp(prefix="p42-test-")
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
||||
|
||||
import pytest # noqa: E402
|
||||
from fastapi.testclient import TestClient # noqa: E402
|
||||
|
||||
from app import auth, db # noqa: E402
|
||||
from app.main import app # noqa: E402
|
||||
|
||||
PASSWORD = "passwort123"
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def fresh_db(tmp_path, monkeypatch):
|
||||
monkeypatch.setattr(db, "DB_FILE", tmp_path / "p42.db")
|
||||
auth._failures.clear()
|
||||
db.init()
|
||||
return tmp_path / "p42.db"
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def conn(fresh_db):
|
||||
c = db.connect()
|
||||
yield c
|
||||
c.close()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def login():
|
||||
"""login("anna", admin=True) → angemeldeter TestClient mit .user (id, name)."""
|
||||
def make(username: str, admin: bool = False) -> TestClient:
|
||||
with db.transaction() as c:
|
||||
user_id = auth._create_user(c, username, username.capitalize(), PASSWORD, admin)
|
||||
client = TestClient(app)
|
||||
res = client.post("/api/auth/login", json={"username": username, "password": PASSWORD})
|
||||
assert res.status_code == 200, res.text
|
||||
client.user = {"id": user_id, "name": username.capitalize()}
|
||||
return client
|
||||
return make
|
||||
@@ -0,0 +1,28 @@
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from app.main import app
|
||||
|
||||
|
||||
def test_ohne_anmeldung_gesperrt():
|
||||
assert TestClient(app).get("/api/haushalt").status_code == 401
|
||||
|
||||
|
||||
def test_erstes_konto_wird_admin():
|
||||
client = TestClient(app)
|
||||
assert client.get("/api/auth/status").json()["setupRequired"] is True
|
||||
res = client.post("/api/auth/setup", json={"username": "erste", "password": "passwort123"})
|
||||
assert res.status_code == 200
|
||||
assert res.json()["isAdmin"] is True
|
||||
# Danach gibt es kein zweites "erstes Konto"
|
||||
assert TestClient(app).post("/api/auth/setup", json={"username": "zweite", "password": "passwort123"}).status_code == 403
|
||||
|
||||
|
||||
def test_falsches_passwort(login):
|
||||
login("anna")
|
||||
res = TestClient(app).post("/api/auth/login", json={"username": "anna", "password": "falsch"})
|
||||
assert res.status_code == 401
|
||||
|
||||
|
||||
def test_benutzerverwaltung_nur_admin(login):
|
||||
assert login("anna").get("/api/users").status_code == 403
|
||||
assert login("chef", admin=True).get("/api/users").status_code == 200
|
||||
@@ -0,0 +1,140 @@
|
||||
def state(client):
|
||||
return client.get("/api/haushalt").json()
|
||||
|
||||
|
||||
def add(client, list_id, name, area="einkauf", quantity=1, unit="stk"):
|
||||
return client.post("/api/haushalt/items", json={"list": area, "name": name, "quantity": quantity, "unit": unit, "listId": list_id})
|
||||
|
||||
|
||||
def items(data, list_id, area=None):
|
||||
return [i for i in data["items"] if i["listId"] == list_id and (area is None or i["list"] == area)]
|
||||
|
||||
|
||||
def new_list(client, name, everyone=True, members=()):
|
||||
res = client.post("/api/haushalt/listen", json={"name": name, "everyone": everyone, "members": list(members)})
|
||||
assert res.status_code == 201, res.text
|
||||
return res.json()["created"]
|
||||
|
||||
|
||||
def test_neue_db_hat_liste_zuhause(login):
|
||||
data = state(login("anna"))
|
||||
assert [l["name"] for l in data["lists"]] == ["Zuhause"]
|
||||
assert data["lists"][0]["everyone"] is True
|
||||
|
||||
|
||||
def test_hinzufuegen_fuehrt_zusammen_und_vereinheitlicht_namen(login):
|
||||
anna = login("anna")
|
||||
home = state(anna)["lists"][0]["id"]
|
||||
add(anna, home, "milch", quantity=2)
|
||||
data = add(anna, home, "Milch", quantity=1).json()
|
||||
assert [(i["name"], i["quantity"]) for i in items(data, home, "einkauf")] == [("Milch", 3)]
|
||||
# Andere Einheit = eigene Zeile
|
||||
data = add(anna, home, "Milch", quantity=1.5, unit="l").json()
|
||||
assert len(items(data, home, "einkauf")) == 2
|
||||
|
||||
|
||||
def test_listen_haben_eigenen_vorrat(login):
|
||||
anna = login("anna")
|
||||
home = state(anna)["lists"][0]["id"]
|
||||
urlaub = new_list(anna, "Urlaub")
|
||||
add(anna, urlaub, "Brot", area="vorrat")
|
||||
data = state(anna)
|
||||
assert [i["name"] for i in items(data, urlaub, "vorrat")] == ["Brot"]
|
||||
assert items(data, home, "vorrat") == []
|
||||
|
||||
|
||||
def test_private_liste_nur_fuer_mitglieder(login):
|
||||
anna, ben, chef = login("anna"), login("ben"), login("chef", admin=True)
|
||||
geheim = new_list(anna, "Geheim", everyone=False)
|
||||
item = add(anna, geheim, "Kuchen").json()
|
||||
item_id = items(item, geheim)[0]["id"]
|
||||
|
||||
for other in (ben, chef): # auch Admins sehen fremde private Listen nicht
|
||||
assert all(l["id"] != geheim for l in state(other)["lists"])
|
||||
assert add(other, geheim, "x").status_code == 404
|
||||
assert other.patch(f"/api/haushalt/items/{item_id}", json={"quantity": 5}).status_code == 404
|
||||
|
||||
anna.patch(f"/api/haushalt/listen/{geheim}", json={"name": "Geheim", "everyone": False, "members": [ben.user["id"]]})
|
||||
lst = next(l for l in state(ben)["lists"] if l["id"] == geheim)
|
||||
assert lst["canManage"] is False
|
||||
assert ben.patch(f"/api/haushalt/listen/{geheim}", json={"name": "x"}).status_code == 403
|
||||
assert ben.delete(f"/api/haushalt/listen/{geheim}").status_code == 403
|
||||
|
||||
|
||||
def test_liste_loeschen_entfernt_eintraege(login, conn):
|
||||
anna = login("anna")
|
||||
urlaub = new_list(anna, "Urlaub")
|
||||
add(anna, urlaub, "Brot", area="vorrat")
|
||||
add(anna, urlaub, "Wasser")
|
||||
assert anna.delete(f"/api/haushalt/listen/{urlaub}").status_code == 200
|
||||
assert conn.execute("SELECT COUNT(*) FROM household_items WHERE list_id = ?", (urlaub,)).fetchone()[0] == 0
|
||||
|
||||
|
||||
def test_ungueltiges_mitglied(login):
|
||||
res = login("anna").post("/api/haushalt/listen", json={"name": "x", "everyone": False, "members": [9999]})
|
||||
assert res.status_code == 400
|
||||
|
||||
|
||||
def test_abhaken_und_einkauf_beenden(login):
|
||||
anna = login("anna")
|
||||
home = state(anna)["lists"][0]["id"]
|
||||
add(anna, home, "Milch", quantity=2)
|
||||
add(anna, home, "Brot")
|
||||
add(anna, home, "Milch", area="vorrat", quantity=1)
|
||||
milch = next(i for i in items(state(anna), home, "einkauf") if i["name"] == "Milch")
|
||||
|
||||
data = anna.patch(f"/api/haushalt/items/{milch['id']}", json={"checked": True}).json()
|
||||
# Abgehakt bleibt auf der Einkaufsliste
|
||||
assert {i["name"]: i["checked"] for i in items(data, home, "einkauf")} == {"Milch": True, "Brot": False}
|
||||
|
||||
res = anna.post(f"/api/haushalt/listen/{home}/einkauf-beenden")
|
||||
assert res.json()["moved"] == 1
|
||||
data = res.json()
|
||||
assert [i["name"] for i in items(data, home, "einkauf")] == ["Brot"]
|
||||
assert [(i["name"], i["quantity"]) for i in items(data, home, "vorrat")] == [("Milch", 3)]
|
||||
# Nichts mehr abgehakt
|
||||
assert anna.post(f"/api/haushalt/listen/{home}/einkauf-beenden").status_code == 400
|
||||
|
||||
|
||||
def test_vorrat_laesst_sich_nicht_abhaken(login):
|
||||
anna = login("anna")
|
||||
home = state(anna)["lists"][0]["id"]
|
||||
vorrat = items(add(anna, home, "Reis", area="vorrat").json(), home, "vorrat")[0]
|
||||
assert anna.patch(f"/api/haushalt/items/{vorrat['id']}", json={"checked": True}).status_code == 400
|
||||
|
||||
|
||||
def test_erneut_hinzufuegen_hebt_haken_auf(login):
|
||||
anna = login("anna")
|
||||
home = state(anna)["lists"][0]["id"]
|
||||
milch = items(add(anna, home, "Milch").json(), home)[0]
|
||||
anna.patch(f"/api/haushalt/items/{milch['id']}", json={"checked": True})
|
||||
data = add(anna, home, "Milch").json()
|
||||
assert items(data, home)[0]["checked"] is False
|
||||
|
||||
|
||||
def test_aufgebraucht_kommt_mit_menge_1_auf_einkauf(login):
|
||||
anna = login("anna")
|
||||
home = state(anna)["lists"][0]["id"]
|
||||
reis = items(add(anna, home, "Reis", area="vorrat", quantity=4).json(), home)[0]
|
||||
data = anna.post(f"/api/haushalt/items/{reis['id']}/move").json()
|
||||
assert [(i["list"], i["quantity"]) for i in items(data, home)] == [("einkauf", 1)]
|
||||
|
||||
|
||||
def test_rezept_uebernehmen_in_gewaehlte_liste(login):
|
||||
anna = login("anna")
|
||||
urlaub = new_list(anna, "Urlaub")
|
||||
res = anna.post("/api/haushalt/rezept/uebernehmen", json={
|
||||
"items": [{"name": "Mehl", "quantity": 0.5, "unit": "kg"}], "aliases": [], "listId": urlaub,
|
||||
})
|
||||
assert res.status_code == 200
|
||||
assert [(i["name"], i["quantity"], i["unit"]) for i in items(res.json(), urlaub, "einkauf")] == [("Mehl", 0.5, "kg")]
|
||||
|
||||
|
||||
def test_einheitenwechsel_fuehrt_zusammen(login):
|
||||
anna = login("anna")
|
||||
home = state(anna)["lists"][0]["id"]
|
||||
add(anna, home, "Mehl", quantity=1, unit="kg")
|
||||
stk = items(add(anna, home, "Mehl", quantity=2).json(), home)
|
||||
stk = next(i for i in stk if i["unit"] == "stk")
|
||||
data = anna.patch(f"/api/haushalt/items/{stk['id']}", json={"unit": "kg"}).json()
|
||||
assert [(i["quantity"], i["unit"]) for i in items(data, home)] == [(3, "kg")]
|
||||
@@ -0,0 +1,65 @@
|
||||
from datetime import datetime, timedelta, timezone
|
||||
|
||||
|
||||
def ago(days):
|
||||
return (datetime.now(timezone.utc) - timedelta(days=days)).isoformat(timespec="milliseconds").replace("+00:00", "Z")
|
||||
|
||||
|
||||
def board(client, project_id):
|
||||
return client.get(f"/api/kanban/projekte/{project_id}").json()
|
||||
|
||||
|
||||
def make_board(client):
|
||||
return client.post("/api/kanban/projekte", json={"name": "Test"}).json()["id"]
|
||||
|
||||
|
||||
def task(client, project_id, title, status="offen"):
|
||||
data = client.post(f"/api/kanban/projekte/{project_id}/aufgaben", json={"title": title, "status": status}).json()
|
||||
return next(t for t in data["tasks"] if t["title"] == title)
|
||||
|
||||
|
||||
def test_erledigt_setzt_und_loescht_zeitpunkt(login):
|
||||
anna = login("anna")
|
||||
pid = make_board(anna)
|
||||
t = task(anna, pid, "A")
|
||||
assert t["doneAt"] is None
|
||||
data = anna.patch(f"/api/kanban/aufgaben/{t['id']}", json={"status": "erledigt"}).json()
|
||||
assert next(x for x in data["tasks"] if x["id"] == t["id"])["doneAt"]
|
||||
data = anna.patch(f"/api/kanban/aufgaben/{t['id']}", json={"status": "arbeit"}).json()
|
||||
assert next(x for x in data["tasks"] if x["id"] == t["id"])["doneAt"] is None
|
||||
assert task(anna, pid, "Gleich erledigt", status="erledigt")["doneAt"]
|
||||
|
||||
|
||||
def test_archiv_nach_2_tagen_loeschen_nach_14(login, conn):
|
||||
anna = login("anna")
|
||||
pid = make_board(anna)
|
||||
for title in ("frisch", "alt", "uralt"):
|
||||
task(anna, pid, title, status="erledigt")
|
||||
with conn:
|
||||
conn.execute("UPDATE kanban_tasks SET done_at = ? WHERE title = 'alt'", (ago(3),))
|
||||
conn.execute("UPDATE kanban_tasks SET done_at = ? WHERE title = 'uralt'", (ago(15),))
|
||||
data = board(anna, pid)
|
||||
assert [t["title"] for t in data["tasks"]] == ["frisch"]
|
||||
assert [t["title"] for t in data["archived"]] == ["alt"]
|
||||
assert data["archived"][0]["deleteAt"] > ago(-10)
|
||||
assert conn.execute("SELECT COUNT(*) FROM kanban_tasks WHERE title = 'uralt'").fetchone()[0] == 0
|
||||
|
||||
|
||||
def test_zurueckholen_startet_fristen_neu(login, conn):
|
||||
anna = login("anna")
|
||||
pid = make_board(anna)
|
||||
t = task(anna, pid, "alt", status="erledigt")
|
||||
with conn:
|
||||
conn.execute("UPDATE kanban_tasks SET done_at = ? WHERE id = ?", (ago(5), t["id"]))
|
||||
data = anna.post(f"/api/kanban/aufgaben/{t['id']}/zurueckholen").json()
|
||||
assert data["archived"] == []
|
||||
assert [x["title"] for x in data["tasks"]] == ["alt"]
|
||||
|
||||
|
||||
def test_loeschen_nur_ersteller_oder_admin(login):
|
||||
anna, ben, chef = login("anna"), login("ben"), login("chef", admin=True)
|
||||
pid = make_board(anna)
|
||||
t1, t2 = task(anna, pid, "eins"), task(anna, pid, "zwei")
|
||||
assert ben.delete(f"/api/kanban/aufgaben/{t1['id']}").status_code == 403
|
||||
assert anna.delete(f"/api/kanban/aufgaben/{t1['id']}").status_code == 200
|
||||
assert chef.delete(f"/api/kanban/aufgaben/{t2['id']}").status_code == 200
|
||||
@@ -0,0 +1,138 @@
|
||||
"""Alte Datenbank-Stände nachbauen und prüfen, dass der Start sie verlustfrei auf den aktuellen Stand bringt."""
|
||||
import sqlite3
|
||||
|
||||
import pytest
|
||||
|
||||
from app import db
|
||||
|
||||
T = "2026-01-01T00:00:00.000Z"
|
||||
|
||||
KANBAN_PROJECTS = """CREATE TABLE kanban_projects (
|
||||
id INTEGER PRIMARY KEY, name TEXT NOT NULL, description TEXT NOT NULL DEFAULT '', position REAL NOT NULL,
|
||||
completed_at TEXT, created_at TEXT NOT NULL, created_by INTEGER, updated_at TEXT NOT NULL, updated_by INTEGER)"""
|
||||
# Kanban-Aufgaben vor "erledigt seit"
|
||||
KANBAN_TASKS_OHNE_DONE = """CREATE TABLE kanban_tasks (
|
||||
id INTEGER PRIMARY KEY, project_id INTEGER NOT NULL REFERENCES kanban_projects(id) ON DELETE CASCADE,
|
||||
title TEXT NOT NULL, description TEXT NOT NULL DEFAULT '', status TEXT NOT NULL, assignee INTEGER,
|
||||
priority INTEGER NOT NULL DEFAULT 2, position REAL NOT NULL, created_at TEXT NOT NULL, created_by INTEGER,
|
||||
updated_at TEXT NOT NULL, updated_by INTEGER)"""
|
||||
# Reise-Städte vor Höhe, Zeitzone und Hauptstadt
|
||||
TRAVEL_ALT = """CREATE TABLE travel_cities (
|
||||
id INTEGER PRIMARY KEY, user_id INTEGER NOT NULL, geoname_id INTEGER NOT NULL, name TEXT NOT NULL,
|
||||
admin1 TEXT NOT NULL DEFAULT '', country TEXT NOT NULL, lat REAL NOT NULL, lon REAL NOT NULL,
|
||||
population INTEGER, created_at TEXT NOT NULL, UNIQUE (user_id, geoname_id))"""
|
||||
# Vorrat/Einkauf ganz alt: ohne Einheit
|
||||
HAUSHALT_OHNE_EINHEIT = """CREATE TABLE household_items (
|
||||
id INTEGER PRIMARY KEY, key TEXT NOT NULL, name TEXT NOT NULL, list TEXT NOT NULL, quantity INTEGER NOT NULL,
|
||||
updated_at TEXT NOT NULL, updated_by INTEGER, UNIQUE (key, list))"""
|
||||
# Vorrat/Einkauf mit Einheit, aber ohne Listen (Stand auf dem Server vor den Listen)
|
||||
HAUSHALT_OHNE_LISTEN = """CREATE TABLE household_items (
|
||||
id INTEGER PRIMARY KEY, key TEXT NOT NULL, name TEXT NOT NULL, list TEXT NOT NULL, quantity REAL NOT NULL,
|
||||
unit TEXT NOT NULL DEFAULT 'stk', updated_at TEXT NOT NULL, updated_by INTEGER, UNIQUE (key, list, unit))"""
|
||||
# Zwischenstand: mehrere Einkaufslisten, Vorrat ohne Liste (list_id NULL)
|
||||
ZWISCHENSTAND = [
|
||||
"""CREATE TABLE shopping_lists (id INTEGER PRIMARY KEY, name TEXT NOT NULL, everyone INTEGER NOT NULL DEFAULT 1,
|
||||
position REAL NOT NULL, created_at TEXT NOT NULL, created_by INTEGER)""",
|
||||
"""CREATE TABLE household_items (
|
||||
id INTEGER PRIMARY KEY, key TEXT NOT NULL, name TEXT NOT NULL, list TEXT NOT NULL,
|
||||
list_id INTEGER REFERENCES shopping_lists(id) ON DELETE CASCADE, quantity REAL NOT NULL,
|
||||
unit TEXT NOT NULL DEFAULT 'stk', updated_at TEXT NOT NULL, updated_by INTEGER,
|
||||
CHECK ((list = 'einkauf') = (list_id IS NOT NULL)))""",
|
||||
"CREATE UNIQUE INDEX household_vorrat ON household_items (key, unit) WHERE list_id IS NULL",
|
||||
"CREATE UNIQUE INDEX household_einkauf ON household_items (list_id, key, unit) WHERE list_id IS NOT NULL",
|
||||
f"INSERT INTO shopping_lists VALUES (1, 'Einkaufsliste', 1, 1, '{T}', NULL)",
|
||||
]
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def old_db(tmp_path, monkeypatch):
|
||||
"""Leere Datei mit den angegebenen alten Tabellen; danach db.init() wie beim Serverstart."""
|
||||
path = tmp_path / "alt.db"
|
||||
monkeypatch.setattr(db, "DB_FILE", path)
|
||||
|
||||
def build(*statements):
|
||||
c = sqlite3.connect(path)
|
||||
for sql in statements:
|
||||
c.execute(sql)
|
||||
c.commit()
|
||||
c.close()
|
||||
db.init()
|
||||
conn = db.connect()
|
||||
return conn
|
||||
return build
|
||||
|
||||
|
||||
def household(conn):
|
||||
return sorted(tuple(r) for r in conn.execute("SELECT name, list, list_id, quantity, unit, checked FROM household_items"))
|
||||
|
||||
|
||||
def test_haushalt_ohne_einheit(old_db):
|
||||
conn = old_db(
|
||||
HAUSHALT_OHNE_EINHEIT,
|
||||
f"INSERT INTO household_items VALUES (1, 'milch', 'Milch', 'vorrat', 2, '{T}', NULL)",
|
||||
f"INSERT INTO household_items VALUES (2, 'brot', 'Brot', 'einkauf', 1, '{T}', NULL)",
|
||||
)
|
||||
assert [tuple(r) for r in conn.execute("SELECT id, name FROM shopping_lists")] == [(1, "Zuhause")]
|
||||
assert household(conn) == [("Brot", "einkauf", 1, 1, "stk", 0), ("Milch", "vorrat", 1, 2, "stk", 0)]
|
||||
|
||||
|
||||
def test_haushalt_ohne_listen(old_db):
|
||||
conn = old_db(
|
||||
HAUSHALT_OHNE_LISTEN,
|
||||
f"INSERT INTO household_items VALUES (1, 'mehl', 'Mehl', 'vorrat', 1.5, 'kg', '{T}', NULL)",
|
||||
f"INSERT INTO household_items VALUES (2, 'mehl', 'Mehl', 'einkauf', 2, 'stk', '{T}', NULL)",
|
||||
)
|
||||
assert [r["name"] for r in conn.execute("SELECT name FROM shopping_lists")] == ["Zuhause"]
|
||||
assert household(conn) == [("Mehl", "einkauf", 1, 2, "stk", 0), ("Mehl", "vorrat", 1, 1.5, "kg", 0)]
|
||||
|
||||
|
||||
def test_haushalt_zwischenstand(old_db):
|
||||
conn = old_db(
|
||||
*ZWISCHENSTAND,
|
||||
f"INSERT INTO household_items VALUES (1, 'reis', 'Reis', 'vorrat', NULL, 1, 'stk', '{T}', NULL)",
|
||||
f"INSERT INTO household_items VALUES (2, 'reis', 'Reis', 'einkauf', 1, 3, 'stk', '{T}', NULL)",
|
||||
)
|
||||
# "Einkaufsliste" heißt jetzt "Zuhause" und enthält auch den Vorrat
|
||||
assert [tuple(r) for r in conn.execute("SELECT id, name FROM shopping_lists")] == [(1, "Zuhause")]
|
||||
assert household(conn) == [("Reis", "einkauf", 1, 3, "stk", 0), ("Reis", "vorrat", 1, 1, "stk", 0)]
|
||||
indexes = {r[0] for r in conn.execute("SELECT name FROM sqlite_master WHERE type = 'index'")}
|
||||
assert not {"household_vorrat", "household_einkauf"} & indexes
|
||||
|
||||
|
||||
def test_kanban_ohne_erledigt_zeitpunkt(old_db):
|
||||
conn = old_db(
|
||||
KANBAN_PROJECTS,
|
||||
KANBAN_TASKS_OHNE_DONE,
|
||||
f"INSERT INTO kanban_projects VALUES (1, 'P', '', 1, NULL, '{T}', NULL, '{T}', NULL)",
|
||||
f"INSERT INTO kanban_tasks VALUES (1, 1, 'fertig', '', 'erledigt', NULL, 2, 1, '{T}', NULL, '{T}', NULL)",
|
||||
f"INSERT INTO kanban_tasks VALUES (2, 1, 'offen', '', 'offen', NULL, 2, 2, '{T}', NULL, '{T}', NULL)",
|
||||
)
|
||||
rows = dict(conn.execute("SELECT title, done_at FROM kanban_tasks").fetchall())
|
||||
# Erledigte zählen ab dem Update – nicht ab dem alten Änderungsdatum (sonst sofort gelöscht)
|
||||
assert rows["fertig"] > T and rows["offen"] is None
|
||||
|
||||
|
||||
def test_reisen_ohne_neue_spalten(old_db):
|
||||
conn = old_db(
|
||||
TRAVEL_ALT,
|
||||
f"INSERT INTO travel_cities VALUES (1, 1, 2950159, 'Berlin', '', 'DE', 52.5, 13.4, NULL, '{T}')",
|
||||
)
|
||||
row = conn.execute("SELECT name, elevation, timezone, capital FROM travel_cities").fetchone()
|
||||
assert tuple(row) == ("Berlin", None, None, None)
|
||||
|
||||
|
||||
def test_zweiter_start_aendert_nichts(old_db):
|
||||
conn = old_db(
|
||||
HAUSHALT_OHNE_LISTEN,
|
||||
f"INSERT INTO household_items VALUES (1, 'mehl', 'Mehl', 'vorrat', 1, 'stk', '{T}', NULL)",
|
||||
)
|
||||
before = household(conn), [tuple(r) for r in conn.execute("SELECT * FROM shopping_lists")]
|
||||
db.init()
|
||||
assert (household(conn), [tuple(r) for r in conn.execute("SELECT * FROM shopping_lists")]) == before
|
||||
|
||||
|
||||
def test_geloeschte_listen_kommen_nicht_zurueck(conn):
|
||||
with conn:
|
||||
conn.execute("DELETE FROM shopping_lists")
|
||||
db.init()
|
||||
assert conn.execute("SELECT COUNT(*) FROM shopping_lists").fetchone()[0] == 0
|
||||
@@ -0,0 +1,34 @@
|
||||
BERLIN = {"geonameId": 2950159, "name": "Berlin", "country": "DE", "lat": 52.52, "lon": 13.41, "population": 3426354, "capital": True}
|
||||
LEIDEN = {"geonameId": 2751773, "name": "Leiden", "country": "NL", "lat": 52.16, "lon": 4.49}
|
||||
|
||||
|
||||
def test_staedte_anderer_nur_lesend(login):
|
||||
anna, ben = login("anna"), login("ben")
|
||||
anna.post("/api/reisen/staedte", json=BERLIN)
|
||||
data = ben.get("/api/reisen").json()
|
||||
assert data["cities"] == []
|
||||
assert [(p["name"], [c["name"] for c in p["cities"]]) for p in data["others"]] == [("Anna", ["Berlin"])]
|
||||
city_id = data["others"][0]["cities"][0]["id"]
|
||||
assert ben.delete(f"/api/reisen/staedte/{city_id}").status_code == 404
|
||||
assert anna.get("/api/reisen").json()["others"] == []
|
||||
|
||||
|
||||
def test_hauptstadt_kennzeichen(login):
|
||||
anna = login("anna")
|
||||
anna.post("/api/reisen/staedte", json=BERLIN)
|
||||
data = anna.post("/api/reisen/staedte", json=LEIDEN).json()
|
||||
# Mit Angabe aus der Suche gespeichert, ohne Angabe "unbekannt" (wird im Hintergrund nachgetragen)
|
||||
assert {c["name"]: c["capital"] for c in data["cities"]} == {"Berlin": True, "Leiden": None}
|
||||
|
||||
|
||||
def test_doppelte_stadt_wird_ignoriert(login):
|
||||
anna = login("anna")
|
||||
anna.post("/api/reisen/staedte", json=BERLIN)
|
||||
assert len(anna.post("/api/reisen/staedte", json=BERLIN).json()["cities"]) == 1
|
||||
|
||||
|
||||
def test_land_markieren(login):
|
||||
anna = login("anna")
|
||||
assert anna.put("/api/reisen/laender/IS").json()["countries"] == ["IS"]
|
||||
assert anna.delete("/api/reisen/laender/IS").json()["countries"] == []
|
||||
assert anna.put("/api/reisen/laender/xx").status_code == 400
|
||||
Reference in new issue
Block a user