FFFFFFF                                                                  [100%]
=================================== FAILURES ===================================
___ test_malformed_tags_do_not_break_or_invent_suggestions[urgent-expected0] ___

tags = 'urgent', expected = ['urgent']

    @pytest.mark.parametrize("tags, expected", [
        ("urgent", ["urgent"]), (12, []), (True, []),
        ({"urgent": 1}, []), ([None, " urgent ", "urgent", 2], ["urgent"]),
    ])
    def test_malformed_tags_do_not_break_or_invent_suggestions(tags, expected):
        result = organize.parse_suggestions(json.dumps([{"uuid": "task", "tags": tags}]), "codex")
>       assert result[0]["tags"] == expected
E       AssertionError: assert ['u', 'r', 'g', 'e', 'n'] == ['urgent']
E         
E         At index 0 diff: 'u' != 'urgent'
E         Left contains 4 more items, first extra item: 'r'
E         Use -v to get more diff

tests/test_organize_safety.py:16: AssertionError
_____ test_malformed_tags_do_not_break_or_invent_suggestions[12-expected1] _____

tags = 12, expected = []

    @pytest.mark.parametrize("tags, expected", [
        ("urgent", ["urgent"]), (12, []), (True, []),
        ({"urgent": 1}, []), ([None, " urgent ", "urgent", 2], ["urgent"]),
    ])
    def test_malformed_tags_do_not_break_or_invent_suggestions(tags, expected):
>       result = organize.parse_suggestions(json.dumps([{"uuid": "task", "tags": tags}]), "codex")
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_organize_safety.py:15: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

stdout = '[{"uuid": "task", "tags": 12}]', agent = 'codex'

    def parse_suggestions(stdout: str, agent: str) -> list[dict]:
        """Extract the JSON suggestions array from agent stdout, defensively."""
        text = (stdout or "").strip()
        if agent == "claude":
            try:
                env = json.loads(text)
                if isinstance(env, dict) and isinstance(env.get("result"), str):
                    text = env["result"].strip()
            except json.JSONDecodeError:
                pass
        # strip ``` / ```json fences
        text = re.sub(r"```(?:json)?", "", text).strip()
        # take the outermost array
        start, end = text.find("["), text.rfind("]")
        if start != -1 and end > start:
            text = text[start : end + 1]
        data = json.loads(text)
        if not isinstance(data, list):
            raise ValueError("agent did not return a JSON array")
        out: list[dict] = []
        for d in data:
            if not isinstance(d, dict) or not d.get("uuid"):
                continue
            title = d.get("suggested_title")
            notes = d.get("append_notes")
            when = d.get("when")
            dest = d.get("dest")
            out.append(
                {
                    "uuid": str(d["uuid"]),
                    "suggested_title": str(title).strip()[:120] if title and str(title).strip() else None,
                    "append_notes": str(notes).strip() if notes and str(notes).strip() else None,
>                   "tags": [str(t).strip() for t in (d.get("tags") or []) if str(t).strip()][:5],
                                                      ^^^^^^^^^^^^^^^^^^^
                    "when": str(when).strip() if when and str(when).strip() else None,
                    "dest": str(dest).strip() if dest and str(dest).strip() else None,
                    "reason": str(d.get("reason") or "").strip(),
                }
            )
E           TypeError: 'int' object is not iterable

src/suur_things_mcp/organize.py:207: TypeError
____ test_malformed_tags_do_not_break_or_invent_suggestions[True-expected2] ____

tags = True, expected = []

    @pytest.mark.parametrize("tags, expected", [
        ("urgent", ["urgent"]), (12, []), (True, []),
        ({"urgent": 1}, []), ([None, " urgent ", "urgent", 2], ["urgent"]),
    ])
    def test_malformed_tags_do_not_break_or_invent_suggestions(tags, expected):
>       result = organize.parse_suggestions(json.dumps([{"uuid": "task", "tags": tags}]), "codex")
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_organize_safety.py:15: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

stdout = '[{"uuid": "task", "tags": true}]', agent = 'codex'

    def parse_suggestions(stdout: str, agent: str) -> list[dict]:
        """Extract the JSON suggestions array from agent stdout, defensively."""
        text = (stdout or "").strip()
        if agent == "claude":
            try:
                env = json.loads(text)
                if isinstance(env, dict) and isinstance(env.get("result"), str):
                    text = env["result"].strip()
            except json.JSONDecodeError:
                pass
        # strip ``` / ```json fences
        text = re.sub(r"```(?:json)?", "", text).strip()
        # take the outermost array
        start, end = text.find("["), text.rfind("]")
        if start != -1 and end > start:
            text = text[start : end + 1]
        data = json.loads(text)
        if not isinstance(data, list):
            raise ValueError("agent did not return a JSON array")
        out: list[dict] = []
        for d in data:
            if not isinstance(d, dict) or not d.get("uuid"):
                continue
            title = d.get("suggested_title")
            notes = d.get("append_notes")
            when = d.get("when")
            dest = d.get("dest")
            out.append(
                {
                    "uuid": str(d["uuid"]),
                    "suggested_title": str(title).strip()[:120] if title and str(title).strip() else None,
                    "append_notes": str(notes).strip() if notes and str(notes).strip() else None,
>                   "tags": [str(t).strip() for t in (d.get("tags") or []) if str(t).strip()][:5],
                                                      ^^^^^^^^^^^^^^^^^^^
                    "when": str(when).strip() if when and str(when).strip() else None,
                    "dest": str(dest).strip() if dest and str(dest).strip() else None,
                    "reason": str(d.get("reason") or "").strip(),
                }
            )
E           TypeError: 'bool' object is not iterable

src/suur_things_mcp/organize.py:207: TypeError
___ test_malformed_tags_do_not_break_or_invent_suggestions[tags3-expected3] ____

tags = {'urgent': 1}, expected = []

    @pytest.mark.parametrize("tags, expected", [
        ("urgent", ["urgent"]), (12, []), (True, []),
        ({"urgent": 1}, []), ([None, " urgent ", "urgent", 2], ["urgent"]),
    ])
    def test_malformed_tags_do_not_break_or_invent_suggestions(tags, expected):
        result = organize.parse_suggestions(json.dumps([{"uuid": "task", "tags": tags}]), "codex")
>       assert result[0]["tags"] == expected
E       AssertionError: assert ['urgent'] == []
E         
E         Left contains one more item: 'urgent'
E         Use -v to get more diff

tests/test_organize_safety.py:16: AssertionError
___ test_malformed_tags_do_not_break_or_invent_suggestions[tags4-expected4] ____

tags = [None, ' urgent ', 'urgent', 2], expected = ['urgent']

    @pytest.mark.parametrize("tags, expected", [
        ("urgent", ["urgent"]), (12, []), (True, []),
        ({"urgent": 1}, []), ([None, " urgent ", "urgent", 2], ["urgent"]),
    ])
    def test_malformed_tags_do_not_break_or_invent_suggestions(tags, expected):
        result = organize.parse_suggestions(json.dumps([{"uuid": "task", "tags": tags}]), "codex")
>       assert result[0]["tags"] == expected
E       AssertionError: assert ['None', 'urg...'urgent', '2'] == ['urgent']
E         
E         At index 0 diff: 'None' != 'urgent'
E         Left contains 3 more items, first extra item: 'urgent'
E         Use -v to get more diff

tests/test_organize_safety.py:16: AssertionError
________________ test_code_fences_inside_notes_survive_parsing _________________

    def test_code_fences_inside_notes_survive_parsing():
        notes = 'Run this example:\n```json\n{"ok": true}\n```'
        payload = [{"uuid": "task", "append_notes": notes}]
        result = organize.parse_suggestions('Here you go:\n```json\n' + json.dumps(payload) + '\n```\nDone.', "codex")
>       assert result[0]["append_notes"] == notes
E       assert 'Run this exa...n{"ok": true}' == 'Run this exa...": true}\n```'
E         
E           Run this example:
E         - ```json
E         + 
E         - {"ok": true}
E         ?             -
E         + {"ok": true}
E         - ```

tests/test_organize_safety.py:23: AssertionError
____________ test_organizer_only_returns_unique_submitted_task_ids _____________

monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x1098c42b0>

    def test_organizer_only_returns_unique_submitted_task_ids(monkeypatch):
        monkeypatch.setattr(organize, "_command", lambda *_: ["fake-agent"])
        monkeypatch.setattr(organize, "_login_path", lambda: "/fake/bin")
        payload = [{"uuid": "task", "suggested_title": "First"},
                   {"uuid": "other", "suggested_title": "Unrelated"},
                   {"uuid": "task", "suggested_title": "Duplicate"}]
        monkeypatch.setattr(organize.subprocess, "run", lambda *a, **kw:
                            subprocess.CompletedProcess(a[0], 0, json.dumps(payload), ""))
        result = organize.organize("Inbox", [{"uuid": "task", "title": "Original"}], [], "codex")
>       assert [(r["uuid"], r["suggested_title"]) for r in result] == [("task", "First")]
E       AssertionError: assert [('task', 'Fi... 'Duplicate')] == [('task', 'First')]
E         
E         Left contains 2 more items, first extra item: ('other', 'Unrelated')
E         Use -v to get more diff

tests/test_organize_safety.py:35: AssertionError
=========================== short test summary info ============================
FAILED tests/test_organize_safety.py::test_malformed_tags_do_not_break_or_invent_suggestions[urgent-expected0]
FAILED tests/test_organize_safety.py::test_malformed_tags_do_not_break_or_invent_suggestions[12-expected1]
FAILED tests/test_organize_safety.py::test_malformed_tags_do_not_break_or_invent_suggestions[True-expected2]
FAILED tests/test_organize_safety.py::test_malformed_tags_do_not_break_or_invent_suggestions[tags3-expected3]
FAILED tests/test_organize_safety.py::test_malformed_tags_do_not_break_or_invent_suggestions[tags4-expected4]
FAILED tests/test_organize_safety.py::test_code_fences_inside_notes_survive_parsing
FAILED tests/test_organize_safety.py::test_organizer_only_returns_unique_submitted_task_ids
7 failed in 0.09s
