task: Weekly review proposes task new/task close; tracker gains a closing write path
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:
1 parent
8b535b4016
commit
62d1c5e636
17 files changed
+563
-87
No files matched your search
@@ -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):
|
||||
|
||||
Reference in new issue
Block a user