task: Weekly review proposes task new/task close; tracker gains a closing write path
CI / verify (push) Successful in 54s
Release / release (push) Successful in 36s

Files changed:
- CHANGES.md
- INSTALL.md
- README.md
- VERSION
- docs/knowledge-and-commitment.md
- instructions/gtd-weekly-review/SKILL.md
- instructions/ingest-large-tree.md
- instructions/wiki-ingest/SKILL.md
- tools/CONTRACT.md
- tools/chemenu/commands/review_cmd.py
- tools/chemenu/commands/task_cmd.py
- tools/chemenu/review.py
- tools/chemenu/tasks/protocol.py
- tools/chemenu/tasks/superproductivity.py
- tools/chemenu/tests/test_review.py
- tools/chemenu/tests/test_superproductivity.py
- tools/chemenu/tests/test_task_cmd.py
This commit is contained in:
torben committed 2026-09-22 21:46:09 +02:00
1 parent 8b535b4016
commit 62d1c5e636
17 files changed
+563 -87

No files matched your search

+56 -1
View File
@@ -281,6 +281,7 @@ def test_open_items_reports_waiting_with_follow_up_at_from_due_with_time(cfg):
result = reader.open_items("Ship Chemenu 7.0")
assert len(result.waiting) == 1
waiting = result.waiting[0]
assert waiting.id == "t1"
assert waiting.title == "Warte auf Angebot vom Elektriker - Tobias"
assert waiting.follow_up_at == date(2026, 3, 1)
@@ -295,12 +296,26 @@ def test_open_items_unknown_project_is_empty_not_an_error(cfg):
result = reader.open_items("No Such Project")
assert result.count == 0
assert result.waiting == ()
assert result.items == ()
def test_open_items_items_carries_id_title_and_waiting_for_every_open_item(cfg):
"""Gitea #138 - `task list` reads this field, and it must agree with
`waiting`: every waiting item also appears here, marked `waiting=True`."""
reader = sp.SuperProductivityReader(cfg)
result = reader.open_items("Ship Chemenu 7.0")
by_id = {item.id: item for item in result.items}
assert len(result.items) == result.count
assert by_id["t1"].title == "Warte auf Angebot vom Elektriker - Tobias"
assert by_id["t1"].waiting is True
assert by_id["t2"].waiting is False
def test_someday_items_come_from_backlog_task_ids_only(cfg):
reader = sp.SuperProductivityReader(cfg)
items = reader.someday_items()
assert len(items) == 1
assert items[0].id == "t3"
assert items[0].title == "Irgendwann Keller aufraeumen"
assert items[0].modified == date(2026, 1, 15)
@@ -510,7 +525,9 @@ def test_api_reader_open_items_counts_against_project_task_ids_not_project_id_fi
result = reader.open_items("Ship Chemenu 7.0")
assert result.count == 2 # t1, t2 - not the subtask t2b
assert len(result.waiting) == 1
assert result.waiting[0].id == "t1"
assert result.waiting[0].follow_up_at == date(2026, 3, 15)
assert {item.id for item in result.items} == {"t1", "t2"}
def test_api_reader_someday_items():
@@ -518,7 +535,7 @@ def test_api_reader_someday_items():
with _api_server({"/projects": projects, "/tasks": tasks, "/tags": tags}) as server:
reader = sp.SuperProductivityApiReader(_api_cfg(server))
items = reader.someday_items()
assert [i.title for i in items] == ["Irgendwann Keller aufraeumen"]
assert [(i.id, i.title) for i in items] == [("t3", "Irgendwann Keller aufraeumen")]
def test_api_reader_401_without_the_right_token_fails_loud():
@@ -606,6 +623,23 @@ def _make_write_handler(state: dict, *, token: str = "test-token"):
return
self._reply(404, {"error": "not found"})
def do_PATCH(self): # noqa: N802
if self.headers.get("Authorization") != f"Bearer {token}":
self._reply(401, {"error": "unauthorized"})
return
length = int(self.headers.get("Content-Length", "0"))
payload = json.loads(self.rfile.read(length)) if length else {}
if self.path.startswith("/tasks/"):
task_id = self.path[len("/tasks/"):]
known_ids = {t["id"] for t in state.get("tasks", [])}
if task_id not in known_ids:
self._reply(404, {"code": "TASK_NOT_FOUND", "message": "Task not found"})
return
state.setdefault("patched", []).append((task_id, payload))
self._reply(200, {"id": task_id, **payload})
return
self._reply(404, {"error": "not found"})
def _reply(self, code: int, payload) -> None:
body = json.dumps(payload).encode("utf-8")
self.send_response(code)
@@ -714,6 +748,27 @@ def test_create_item_waiting_without_the_tag_refuses_and_posts_nothing():
assert "posted" not in state
# --- write path: close_item (Gitea #138) ---------------------------------------
def test_close_item_patches_is_done_true_and_nothing_else():
state = {"tasks": [{"id": "t1", "title": "x", "isDone": False}]}
with _write_api_server(state) as server:
writer = _writer_for(server)
writer.close_item("t1")
assert state["patched"] == [("t1", {"isDone": True})]
def test_close_item_unknown_id_refuses_and_writes_nothing():
state = {"tasks": [{"id": "t1", "title": "x", "isDone": False}]}
with _write_api_server(state) as server:
writer = _writer_for(server)
with pytest.raises(ValidationError):
writer.close_item("no-such-id")
assert "patched" not in state
# --- equivalence: both access paths agree on the same fixture (Gitea #133) --------
def test_snapshot_and_api_readers_agree_on_the_same_fixture(tmp_path):