From f1c13377a3979f7732ad5ff693ecb549855aba49 Mon Sep 17 00:00:00 2001 From: Teknium <127238744+teknium1@users.noreply.github.com> Date: Sat, 8 Aug 2026 11:57:45 -0700 Subject: [PATCH] test(cron): regression coverage for Windows encoding cluster - CJK/emoji round-trip + human-readable jobs.json (PRs #52302/#29754) - emoji through no_agent script stdout capture (issue #42384) - truncated/invalid UTF-8 script stdout must not raise (#47393) --- tests/cron/test_cron_script.py | 45 ++++++++++++++++++++++++++++++++++ tests/cron/test_jobs.py | 24 ++++++++++++++++++ 2 files changed, 69 insertions(+) diff --git a/tests/cron/test_cron_script.py b/tests/cron/test_cron_script.py index 3e77293ea7..dbe6bf9f6b 100644 --- a/tests/cron/test_cron_script.py +++ b/tests/cron/test_cron_script.py @@ -200,6 +200,51 @@ class TestRunJobScript: assert "encoding" not in captured["kwargs"] assert "errors" not in captured["kwargs"] + def test_emoji_stdout_round_trips_through_script_capture(self, cron_env): + """Emoji in script stdout must reach the caller intact (#42384). + + On Windows the fix is the utf-8 + errors='replace' popen kwargs + (asserted above); on POSIX the UTF-8 locale default must already + carry emoji through. Either way the delivery content is the real + text, never an exception. + """ + from cron.scheduler import _run_job_script + + script = cron_env / "scripts" / "emoji.py" + script.write_text( + 'import sys\n' + 'sys.stdout.buffer.write("backup done \\N{PARTY POPPER} ζ—₯欑".encode("utf-8"))\n', + encoding="utf-8", + ) + + success, output = _run_job_script("emoji.py") + + assert success is True + assert "backup done πŸŽ‰ ζ—₯欑" == output + + def test_invalid_utf8_stdout_does_not_raise(self, cron_env): + """Truncated/invalid UTF-8 in script stdout must never escape as an + exception (#47393) β€” a raised UnicodeDecodeError higher up would + silently drop the whole delivery (#42384). The run may fail, but it + must fail as a (False, message) result the scheduler can deliver. + """ + from cron.scheduler import _run_job_script + + script = cron_env / "scripts" / "bad_bytes.py" + # b'\xe6\x97' is the first two bytes of a three-byte CJK sequence β€” + # a truncated write, exactly the shape reported in #47393. + script.write_text( + "import sys\n" + "sys.stdout.buffer.write(b'partial \\xe6\\x97')\n", + encoding="utf-8", + ) + + success, output = _run_job_script("bad_bytes.py") # must not raise + + assert isinstance(success, bool) + assert isinstance(output, str) + assert output # a message is always produced, never a silent drop + class TestBuildJobPromptWithScript: """Test that script output is injected into the prompt.""" diff --git a/tests/cron/test_jobs.py b/tests/cron/test_jobs.py index 17b4347be4..0416bdcc7c 100644 --- a/tests/cron/test_jobs.py +++ b/tests/cron/test_jobs.py @@ -190,6 +190,30 @@ def tmp_cron_dir(tmp_path, monkeypatch): class TestJobCRUD: + def test_cjk_and_emoji_round_trip_readable_in_jobs_json(self, tmp_cron_dir): + """CJK/emoji job text must round-trip AND stay human-readable on disk. + + With json.dump's default ensure_ascii=True, every non-ASCII char in + jobs.json is written as \\uXXXX escapes, which users reported as + unreadable garbage when inspecting their job store (#52302, #29754). + ensure_ascii=False + the existing encoding="utf-8" writer keeps the + text literal; the utf-8-sig reader must parse it back identically. + """ + name = "ζ—₯ζ¬‘γƒ¬γƒγƒΌγƒˆ πŸŽ‰ cafΓ©" + job = create_job(prompt=f"Summarize {name}", schedule="30m", name=name) + + # Round-trip through save/load is lossless. + fetched = get_job(job["id"]) + assert fetched["name"] == name + assert name in fetched["prompt"] + + # On-disk representation is literal UTF-8, not \uXXXX escapes. + from cron.jobs import JOBS_FILE + raw = JOBS_FILE.read_text(encoding="utf-8") + assert "ζ—₯ζ¬‘γƒ¬γƒγƒΌγƒˆ" in raw + assert "πŸŽ‰" in raw + assert "\\u65e5" not in raw + def test_create_and_get(self, tmp_cron_dir): job = create_job(prompt="Check server status", schedule="30m") assert job["id"]