chore: finalize v1.0.0 — cleanup, unified errors, date validation
- Move integration tests to tests/; fix .gitignore to scope root-only
- Remove tracked debug artefacts (probe2_stderr/stdout.txt)
- __init__.py: version via importlib.metadata; export create_server in __all__
- server.py: unified JSON error format {"error":"..."} for all tools
- server.py: date validation (YYYY-MM-DD) for all meal-plan tools
- server.py: clear_list reports partial failures (failed_count, failed_ids)
- server.py: -> str annotations on get_circles, get_tasks, get_activities
- server.py: document TODO:94 as known limitation (no name in sortingIndexByTaskList)
- server.py: date validation also added to get_meal_plan
- Add LICENSE (MIT, Marcus van Elst)
- Add CHANGELOG.md (Keep a Changelog, v0.1.0–v1.0.0)
- README.md: restructured by use case; 🔒 marks write tools
- CLAUDE.md: update to v1.0.0 state; condense roadmap history
- SPEC.md: add version stamp
- pyproject.toml: version 1.0.0 (single source of truth)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
"""Integration test for emoji + color in get_lists / create_list (v0.5.1)."""
|
||||
import sys, json
|
||||
sys.path.insert(0, "src")
|
||||
|
||||
import keyring
|
||||
class _K:
|
||||
def get_password(self, s, k): return {"email": "marcus@gecheckt.de", "password": "Lasdas1234"}.get(k)
|
||||
keyring.get_password = _K().get_password
|
||||
|
||||
from mcp_familywall.server import create_list, delete_list, get_lists
|
||||
|
||||
errors = []
|
||||
|
||||
def log(msg):
|
||||
sys.stdout.buffer.write((str(msg) + "\n").encode("utf-8"))
|
||||
|
||||
def check(label, result, **expect):
|
||||
try:
|
||||
obj = json.loads(result)
|
||||
except Exception:
|
||||
errors.append(f"FAIL [{label}]: not JSON -- {result!r}")
|
||||
return None
|
||||
for k, v in expect.items():
|
||||
if obj.get(k) != v:
|
||||
errors.append(f"FAIL [{label}]: {k}={obj.get(k)!r}, expected {v!r}")
|
||||
return None
|
||||
log(f"OK [{label}]: {dict((k, obj.get(k)) for k in expect)}")
|
||||
return obj
|
||||
|
||||
# --- Test 1: create with emoji + color ---
|
||||
r1 = create_list("__test_emoji__", "TODOS", emoji="\U0001F331", color="#E53935")
|
||||
log(f"create with emoji+color = {r1}")
|
||||
o1 = check("create_emoji_color", r1, created=True, emoji="\U0001F331", color="#E53935")
|
||||
id1 = o1["id"] if o1 else None
|
||||
|
||||
# --- Test 2: create without emoji/color ---
|
||||
r2 = create_list("__test_plain__", "SHOPPING_LIST")
|
||||
log(f"create plain = {r2}")
|
||||
o2 = check("create_plain", r2, created=True, emoji=None, color=None)
|
||||
id2 = o2["id"] if o2 else None
|
||||
|
||||
# --- Test 3: get_lists shows emoji + color ---
|
||||
lists_json = get_lists()
|
||||
try:
|
||||
all_lists = json.loads(lists_json)
|
||||
by_id = {l["id"]: l for l in all_lists}
|
||||
|
||||
if id1 and id1 in by_id:
|
||||
l = by_id[id1]
|
||||
if l.get("emoji") == "\U0001F331" and l.get("color") == "#E53935":
|
||||
log(f"OK [get_lists emoji+color]: emoji={l['emoji']!r} color={l['color']!r}")
|
||||
else:
|
||||
errors.append(f"FAIL [get_lists emoji+color]: emoji={l.get('emoji')!r} color={l.get('color')!r}")
|
||||
elif id1:
|
||||
errors.append(f"FAIL [get_lists emoji+color]: {id1} not in lists")
|
||||
|
||||
if id2 and id2 in by_id:
|
||||
l = by_id[id2]
|
||||
if l.get("emoji") is None and l.get("color") is None:
|
||||
log(f"OK [get_lists plain]: emoji={l.get('emoji')!r} color={l.get('color')!r}")
|
||||
else:
|
||||
errors.append(f"FAIL [get_lists plain]: emoji={l.get('emoji')!r} color={l.get('color')!r}")
|
||||
elif id2:
|
||||
errors.append(f"FAIL [get_lists plain]: {id2} not in lists")
|
||||
|
||||
# Test 4: system lists have emoji=None (API returns ""), color=None (absent)
|
||||
sys_lists = [l for l in all_lists if "SYS-CAT" in l.get("name", "") or
|
||||
l.get("name") in ("Aufgaben", "Einkaufsliste")]
|
||||
for sl in sys_lists:
|
||||
if sl.get("emoji") is None and sl.get("color") is None:
|
||||
log(f"OK [get_lists sys emoji/color=null]: {sl.get('name')}")
|
||||
else:
|
||||
errors.append(f"FAIL [get_lists sys emoji/color]: name={sl.get('name')!r} emoji={sl.get('emoji')!r} color={sl.get('color')!r}")
|
||||
|
||||
except Exception as e:
|
||||
errors.append(f"FAIL [get_lists parse]: {e}")
|
||||
|
||||
# --- Cleanup ---
|
||||
for lid, label in [(id1, "emoji"), (id2, "plain")]:
|
||||
if lid:
|
||||
r = delete_list(lid)
|
||||
try:
|
||||
obj = json.loads(r)
|
||||
if obj.get("deleted"):
|
||||
log(f"OK [cleanup {label}]: deleted {lid}")
|
||||
else:
|
||||
errors.append(f"FAIL [cleanup {label}]: {r}")
|
||||
except Exception:
|
||||
errors.append(f"FAIL [cleanup {label}]: {r}")
|
||||
|
||||
# --- Summary ---
|
||||
if errors:
|
||||
log("\nFAILURES:")
|
||||
for e in errors:
|
||||
log(" " + e)
|
||||
sys.exit(1)
|
||||
else:
|
||||
log("\nAll tests passed.")
|
||||
@@ -0,0 +1,89 @@
|
||||
"""Integration test for create_list and delete_list (v0.5.0)."""
|
||||
import sys, json, os
|
||||
os.environ["PYTHONIOENCODING"] = "utf-8"
|
||||
sys.path.insert(0, "src")
|
||||
|
||||
# Patch keyring to use test credentials
|
||||
import keyring
|
||||
class _FakeKeyring:
|
||||
_store = {"email": "marcus@gecheckt.de", "password": "Lasdas1234"}
|
||||
def get_password(self, service, key):
|
||||
return self._store.get(key)
|
||||
keyring.get_password = _FakeKeyring().get_password
|
||||
|
||||
from mcp_familywall.server import create_list, delete_list, get_lists
|
||||
|
||||
errors = []
|
||||
|
||||
def check(label, result, expect_key, expect_val=None):
|
||||
try:
|
||||
obj = json.loads(result)
|
||||
except Exception:
|
||||
errors.append(f"FAIL [{label}]: not JSON -- {result!r}")
|
||||
return None
|
||||
if expect_key not in obj:
|
||||
errors.append(f"FAIL [{label}]: missing key '{expect_key}' in {obj}")
|
||||
return None
|
||||
if expect_val is not None and obj[expect_key] != expect_val:
|
||||
errors.append(f"FAIL [{label}]: {expect_key}={obj[expect_key]!r}, expected {expect_val!r}")
|
||||
return None
|
||||
sys.stdout.buffer.write(f"OK [{label}]: {expect_key}={obj.get(expect_key)!r}\n".encode("utf-8"))
|
||||
return obj
|
||||
|
||||
def log(msg):
|
||||
sys.stdout.buffer.write((str(msg) + "\n").encode("utf-8"))
|
||||
|
||||
# --- Test 1: create SHOPPING_LIST ---
|
||||
r1 = create_list("__claude_test_shop__", "SHOPPING_LIST")
|
||||
log(f"create SHOPPING_LIST = {r1}")
|
||||
o1 = check("create_shopping_list", r1, "created", True)
|
||||
shop_id = o1["id"] if o1 else None
|
||||
|
||||
# --- Test 2: create TODOS ---
|
||||
r2 = create_list("__claude_test_todos__", "TODOS", shared_to_all=True, emoji="\u2705")
|
||||
log(f"create TODOS = {r2}")
|
||||
o2 = check("create_todos", r2, "created", True)
|
||||
todos_id = o2["id"] if o2 else None
|
||||
|
||||
# --- Test 3: verify both appear in get_lists ---
|
||||
lists_json = get_lists()
|
||||
log(f"get_lists (first 300 chars) = {lists_json[:300]}")
|
||||
try:
|
||||
all_lists = json.loads(lists_json)
|
||||
ids = [l["id"] for l in all_lists]
|
||||
if shop_id and shop_id in ids:
|
||||
log(f"OK [get_lists contains shop]: {shop_id}")
|
||||
elif shop_id:
|
||||
errors.append(f"FAIL [get_lists contains shop]: {shop_id} not in lists")
|
||||
if todos_id and todos_id in ids:
|
||||
log(f"OK [get_lists contains todos]: {todos_id}")
|
||||
elif todos_id:
|
||||
errors.append(f"FAIL [get_lists contains todos]: {todos_id} not in lists")
|
||||
except Exception as e:
|
||||
errors.append(f"FAIL [get_lists parse]: {e}")
|
||||
|
||||
# --- Test 4: delete SHOPPING_LIST ---
|
||||
if shop_id:
|
||||
r3 = delete_list(shop_id)
|
||||
log(f"delete SHOPPING_LIST = {r3}")
|
||||
check("delete_shopping_list", r3, "deleted", True)
|
||||
|
||||
# --- Test 5: delete TODOS ---
|
||||
if todos_id:
|
||||
r4 = delete_list(todos_id)
|
||||
log(f"delete TODOS = {r4}")
|
||||
check("delete_todos", r4, "deleted", True)
|
||||
|
||||
# --- Test 6: invalid list_type ---
|
||||
r5 = create_list("bad", "INVALID_TYPE")
|
||||
assert "Error" in r5, f"Expected error for invalid list_type, got: {r5}"
|
||||
log(f"OK [invalid list_type]: {r5}")
|
||||
|
||||
# --- Summary ---
|
||||
if errors:
|
||||
log("\nFAILURES:")
|
||||
for e in errors:
|
||||
log(" " + e)
|
||||
sys.exit(1)
|
||||
else:
|
||||
log("\nAll tests passed.")
|
||||
Reference in New Issue
Block a user