Context
Engine.snapshot() is the single interface between the engine and every front-end. The GUI polls it several times a second and renders whatever it returns: the stage name, the counters, the speed, the ETA, the elapsed clock. moon_bridge.py serves it over the loopback API, and render_gui.py and integration_http.py both depend on its shape.
It has no unit tests. Everything it produces is currently verified only by running a real download and looking at the window.
That gap is not theoretical. #58 is a bug that lives in snapshot() — the elapsed clock never stops after a run — and nothing caught it, because catching it requires nothing more than calling the method twice and comparing two numbers.
tests/test_no_chrome.py already proves the engine can be driven headlessly with Chrome and the network stubbed at the moon_extract boundary, so the hard part of the setup is already solved and working.
What to do
Add tests/test_snapshot.py covering the state machine that snapshot() exposes. The interesting assertions are about transitions, not single values:
- Idle before anything runs. A fresh
Engine() reports stage == "idle", elapsed_s == 0, and zeroed counters. It must not raise.
- Stage progression. With the internal counters set to represent extraction in progress, then downloading, then everything done,
stage reports extracting, downloading, done respectively. The branch is at moon_engine.py:586-593.
- The log cursor.
snapshot(cursor=N) returns only lines after N, and the returned cursor advances. Log a few lines, read with the returned cursor, assert you get nothing the second time. This is the contract the GUI relies on to avoid gaps it cannot detect.
- ETA and speed are safe when empty. With no byte samples,
speed_mbs and eta_s are 0 and nothing divides by zero.
- Counters reset between runs.
start() must clear the previous run's totals.
Do not restructure snapshot() to make it testable. It is already testable — it is a method on an object with no I/O. If you find you cannot test something without changing production code, say so in the PR rather than changing it: that is useful information about the design.
Acceptance criteria
Notes for the contributor
No Windows needed. Read tests/conftest.py and tests/test_no_chrome.py first: they show how the engine is driven in isolation. tests/test_proxy_pool.py shows the preferred style for small focused tests.
If #58 is still open when you start, do not fix it here — write the test that would have caught it and let it fail, or coordinate on that issue. Two PRs touching the same lines is the one thing worth avoiding.
Comment here with which behaviours you are taking if you would rather split this.
Context
Engine.snapshot()is the single interface between the engine and every front-end. The GUI polls it several times a second and renders whatever it returns: the stage name, the counters, the speed, the ETA, the elapsed clock.moon_bridge.pyserves it over the loopback API, andrender_gui.pyandintegration_http.pyboth depend on its shape.It has no unit tests. Everything it produces is currently verified only by running a real download and looking at the window.
That gap is not theoretical. #58 is a bug that lives in
snapshot()— the elapsed clock never stops after a run — and nothing caught it, because catching it requires nothing more than calling the method twice and comparing two numbers.tests/test_no_chrome.pyalready proves the engine can be driven headlessly with Chrome and the network stubbed at themoon_extractboundary, so the hard part of the setup is already solved and working.What to do
Add
tests/test_snapshot.pycovering the state machine thatsnapshot()exposes. The interesting assertions are about transitions, not single values:Engine()reportsstage == "idle",elapsed_s == 0, and zeroed counters. It must not raise.stagereportsextracting,downloading,donerespectively. The branch is atmoon_engine.py:586-593.snapshot(cursor=N)returns only lines afterN, and the returnedcursoradvances. Log a few lines, read with the returned cursor, assert you get nothing the second time. This is the contract the GUI relies on to avoid gaps it cannot detect.speed_mbsandeta_sare0and nothing divides by zero.start()must clear the previous run's totals.Do not restructure
snapshot()to make it testable. It is already testable — it is a method on an object with no I/O. If you find you cannot test something without changing production code, say so in the PR rather than changing it: that is useful information about the design.Acceptance criteria
pytest tests/ -qpasses with the new file, no browser and no networksleeplonger than neededNotes for the contributor
No Windows needed. Read
tests/conftest.pyandtests/test_no_chrome.pyfirst: they show how the engine is driven in isolation.tests/test_proxy_pool.pyshows the preferred style for small focused tests.If #58 is still open when you start, do not fix it here — write the test that would have caught it and let it fail, or coordinate on that issue. Two PRs touching the same lines is the one thing worth avoiding.
Comment here with which behaviours you are taking if you would rather split this.