fix: harden countdown validation and recurrence
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
from copy import deepcopy
|
||||
from datetime import date
|
||||
|
||||
from backend.lunar_support import (
|
||||
@@ -265,3 +266,149 @@ def test_countdown_backup_merge_remaps_ids_owned_by_another_user(client):
|
||||
replaced = client.get("/api/v1/countdowns").json()
|
||||
assert len(replaced) == 1
|
||||
assert replaced[0]["id"] == items[0]["id"]
|
||||
|
||||
|
||||
def test_countdown_rejects_blank_text_and_preserves_lunar_recurrence(client):
|
||||
boot(client)
|
||||
assert create_countdown(client, title=" ").status_code == 422
|
||||
assert create_countdown(client, icon="\n\t").status_code == 422
|
||||
lunar = {
|
||||
"calendar_mode": "lunar",
|
||||
"lunar_month": 8,
|
||||
"lunar_day": 4,
|
||||
"event_date": "2026-01-01",
|
||||
}
|
||||
weekly = create_countdown(client, repeat_rule="weekly", **lunar)
|
||||
monthly = create_countdown(client, repeat_rule="monthly", **lunar)
|
||||
assert weekly.status_code == 201
|
||||
assert monthly.status_code == 201
|
||||
assert weekly.json()["repeat_rule"] == "weekly"
|
||||
assert monthly.json()["repeat_rule"] == "monthly"
|
||||
|
||||
|
||||
def test_future_fixed_year_lunar_countdown_does_not_occur_before_anchor(client):
|
||||
boot(client)
|
||||
created = create_countdown(
|
||||
client,
|
||||
title="未来农历事件",
|
||||
event_date="2030-01-01",
|
||||
calendar_mode="lunar",
|
||||
lunar_month=1,
|
||||
lunar_day=1,
|
||||
repeat_rule="yearly",
|
||||
)
|
||||
assert created.status_code == 201
|
||||
item = created.json()
|
||||
assert item["event_date"] == "2030-02-03"
|
||||
assert item["display_date"] >= item["event_date"]
|
||||
|
||||
|
||||
def test_archived_countdown_cannot_be_edited(client):
|
||||
boot(client)
|
||||
item = create_countdown(client).json()
|
||||
assert client.delete(f"/api/v1/countdowns/{item['id']}").status_code == 204
|
||||
response = client.patch(f"/api/v1/countdowns/{item['id']}", json={"title": "归档后偷改"})
|
||||
assert response.status_code == 409
|
||||
|
||||
|
||||
def test_stale_countdown_edit_is_rejected(client):
|
||||
boot(client)
|
||||
item = create_countdown(client).json()
|
||||
first = client.patch(
|
||||
f"/api/v1/countdowns/{item['id']}",
|
||||
json={"title": "第一次修改", "expected_updated_at": item["updated_at"]},
|
||||
)
|
||||
assert first.status_code == 200
|
||||
stale = client.patch(
|
||||
f"/api/v1/countdowns/{item['id']}",
|
||||
json={"title": "过期修改", "expected_updated_at": item["updated_at"]},
|
||||
)
|
||||
assert stale.status_code == 409
|
||||
current = client.get("/api/v1/countdowns").json()[0]
|
||||
assert current["title"] == "第一次修改"
|
||||
|
||||
|
||||
def test_restore_rejects_malformed_countdowns_atomically(client):
|
||||
boot(client)
|
||||
original = create_countdown(client, title="必须保留").json()
|
||||
exported = client.get("/api/v1/export").json()
|
||||
|
||||
malformed = deepcopy(exported)
|
||||
malformed["countdowns"][0]["archived_at"] = "not-a-date"
|
||||
response = client.post("/api/v1/restore", params={"mode": "replace"}, json=malformed)
|
||||
assert response.status_code == 422
|
||||
assert [item["id"] for item in client.get("/api/v1/countdowns").json()] == [original["id"]]
|
||||
|
||||
for invalid in (None, 7):
|
||||
malformed = deepcopy(exported)
|
||||
malformed["countdowns"] = invalid
|
||||
response = client.post("/api/v1/restore", params={"mode": "replace"}, json=malformed)
|
||||
assert response.status_code == 422
|
||||
assert [item["id"] for item in client.get("/api/v1/countdowns").json()] == [original["id"]]
|
||||
|
||||
duplicate = deepcopy(exported)
|
||||
duplicate["countdowns"].append(deepcopy(duplicate["countdowns"][0]))
|
||||
response = client.post("/api/v1/restore", params={"mode": "replace"}, json=duplicate)
|
||||
assert response.status_code == 422
|
||||
assert [item["id"] for item in client.get("/api/v1/countdowns").json()] == [original["id"]]
|
||||
|
||||
two_pinned = deepcopy(exported)
|
||||
extra = deepcopy(two_pinned["countdowns"][0])
|
||||
extra["id"] = "34deeea0-d976-4581-b378-a66f28623de8"
|
||||
extra["title"] = "第二个置顶"
|
||||
two_pinned["countdowns"][0]["pinned"] = True
|
||||
extra["pinned"] = True
|
||||
two_pinned["countdowns"].append(extra)
|
||||
response = client.post("/api/v1/restore", params={"mode": "replace"}, json=two_pinned)
|
||||
assert response.status_code == 200
|
||||
assert sum(item["pinned"] for item in client.get("/api/v1/countdowns").json()) == 1
|
||||
|
||||
|
||||
def test_restore_validates_lunar_metadata_booleans_and_count(client):
|
||||
boot(client)
|
||||
item = create_countdown(
|
||||
client,
|
||||
title="农历备份",
|
||||
event_date="2025-01-01",
|
||||
calendar_mode="lunar",
|
||||
lunar_month=8,
|
||||
lunar_day=4,
|
||||
).json()
|
||||
exported = client.get("/api/v1/export").json()
|
||||
|
||||
contradictory = deepcopy(exported)
|
||||
contradictory["countdowns"][0]["event_date"] = "2025-01-01"
|
||||
assert client.post("/api/v1/restore", params={"mode": "replace"}, json=contradictory).status_code == 422
|
||||
assert client.get("/api/v1/countdowns").json()[0]["id"] == item["id"]
|
||||
|
||||
string_booleans = deepcopy(exported)
|
||||
string_booleans["countdowns"][0]["ignore_year"] = "false"
|
||||
string_booleans["countdowns"][0]["pinned"] = "false"
|
||||
assert client.post("/api/v1/restore", params={"mode": "replace"}, json=string_booleans).status_code == 422
|
||||
|
||||
clean = deepcopy(exported)
|
||||
clean["countdowns"][0]["id"] = "13ad79f4-78bc-4a53-a952-e1da84be1a9a"
|
||||
restored = client.post("/api/v1/restore", params={"mode": "merge"}, json=clean)
|
||||
assert restored.status_code == 200
|
||||
assert restored.json()["restored"] == 1
|
||||
|
||||
|
||||
def test_restore_accepts_lunar_dates_whose_solar_anchor_is_in_next_year(client):
|
||||
boot(client)
|
||||
created = create_countdown(
|
||||
client,
|
||||
title="跨公历年农历日",
|
||||
event_date="2001-01-01",
|
||||
calendar_mode="lunar",
|
||||
lunar_month=12,
|
||||
lunar_day=20,
|
||||
)
|
||||
assert created.status_code == 201
|
||||
assert created.json()["event_date"] == "2002-02-01"
|
||||
exported = client.get("/api/v1/export").json()
|
||||
|
||||
restored = client.post("/api/v1/restore", params={"mode": "replace"}, json=exported)
|
||||
assert restored.status_code == 200
|
||||
item = client.get("/api/v1/countdowns").json()[0]
|
||||
assert item["event_date"] == "2002-02-01"
|
||||
assert item["lunar_year"] == 2001
|
||||
|
||||
Reference in New Issue
Block a user