test_external_dispatch_detection.py 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  1. """Telling our own print dispatch from a slicer's (#2843 follow-up).
  2. The old test was ``sequence_id != "20000"``, on the belief that 20000 was
  3. Bambuddy's alone. Measured on the wire 2026-08-17: OrcaSlicer dispatched
  4. ``20000`` and then ``20001``, while BambuStudio was on ``20009`` / ``20010`` --
  5. both counting up from the same base, which is also the value
  6. ``virtual_printer/bind_server`` documents the slicer sending during detect. So
  7. the check swallowed whichever slicer dispatch landed on 20000, which after a
  8. slicer restart is the first one.
  9. It matters beyond the log line: counting Studio-versus-Orca dispatches across
  10. support bundles is how the size of the internal-storage problem gets measured,
  11. and an undercount there is silent.
  12. """
  13. import json
  14. import logging
  15. import pytest
  16. from backend.app.services.bambu_mqtt import BambuMQTTClient
  17. @pytest.fixture
  18. def client():
  19. return BambuMQTTClient(ip_address="192.168.1.100", serial_number="TEST123", access_code="12345678")
  20. def _project_file(sequence_id, file="Cube.gcode.3mf", url="ftp://Cube.gcode.3mf", subtask="Cube"):
  21. return {
  22. "print": {
  23. "sequence_id": sequence_id,
  24. "command": "project_file",
  25. "file": file,
  26. "url": url,
  27. "subtask_name": subtask,
  28. "ams_mapping": [0],
  29. }
  30. }
  31. def _external_lines(caplog):
  32. return [r for r in caplog.records if "External project_file payload" in r.getMessage()]
  33. class TestSlicerDispatchIsReported:
  34. @pytest.mark.parametrize("seq", ["20000", "20001", "20009", "20010"])
  35. def test_every_slicer_sequence_id_is_logged(self, client, caplog, seq):
  36. """20000 included -- that is the one the old check threw away."""
  37. with caplog.at_level(logging.INFO, logger="backend.app.services.bambu_mqtt"):
  38. client._handle_request_message(_project_file(seq))
  39. assert len(_external_lines(caplog)) == 1
  40. def test_the_payload_is_logged_verbatim(self, client, caplog):
  41. """It exists to be diffed against ours, so it has to be complete."""
  42. with caplog.at_level(logging.INFO, logger="backend.app.services.bambu_mqtt"):
  43. client._handle_request_message(_project_file("20000"))
  44. logged = json.loads(_external_lines(caplog)[0].getMessage().split("payload: ", 1)[1])
  45. assert logged["url"] == "ftp://Cube.gcode.3mf"
  46. assert logged["sequence_id"] == "20000"
  47. class TestOwnDispatchIsNotReported:
  48. def test_our_own_echo_is_recognised(self, client, caplog):
  49. """Bambuddy publishes to the topic it subscribes to, so it sees its own
  50. dispatch come back."""
  51. ours = _project_file("20000", url="ftp://MyPrint.3mf", file="MyPrint.3mf", subtask="MyPrint")
  52. client._own_project_file_key = client._project_file_key(ours["print"])
  53. with caplog.at_level(logging.INFO, logger="backend.app.services.bambu_mqtt"):
  54. client._handle_request_message(ours)
  55. assert _external_lines(caplog) == []
  56. def test_the_marker_is_consumed(self, client, caplog):
  57. """One-shot. A second identical dispatch is somebody else's -- a reprint
  58. from the slicer of the same file must not hide behind our last one."""
  59. ours = _project_file("20000")
  60. client._own_project_file_key = client._project_file_key(ours["print"])
  61. with caplog.at_level(logging.INFO, logger="backend.app.services.bambu_mqtt"):
  62. client._handle_request_message(ours)
  63. client._handle_request_message(ours)
  64. assert len(_external_lines(caplog)) == 1
  65. def test_a_slicer_sharing_our_sequence_id_is_still_reported(self, client, caplog):
  66. """The exact collision that motivated this: same 20000, different file."""
  67. ours = _project_file("20000", file="Ours.3mf", url="ftp://Ours.3mf", subtask="Ours")
  68. client._own_project_file_key = client._project_file_key(ours["print"])
  69. with caplog.at_level(logging.INFO, logger="backend.app.services.bambu_mqtt"):
  70. client._handle_request_message(_project_file("20000", file="Theirs.3mf", url="brtc://emmc/Theirs.3mf"))
  71. assert len(_external_lines(caplog)) == 1
  72. class TestUnaffectedBehaviour:
  73. def test_the_project_url_is_captured_either_way(self, client):
  74. """The storage verdict must not depend on who dispatched -- it is read
  75. before the ours/theirs test and drives whether the FTPS sweep runs."""
  76. ours = _project_file("20000", url="ftp://Ours.3mf", file="Ours.3mf", subtask="Ours")
  77. client._own_project_file_key = client._project_file_key(ours["print"])
  78. client._handle_request_message(ours)
  79. assert client.state.current_project_url == "ftp://Ours.3mf"
  80. client._handle_request_message(_project_file("20009", url="brtc://emmc/Theirs.3mf"))
  81. assert client.state.current_project_url == "brtc://emmc/Theirs.3mf"
  82. def test_ams_mapping_is_captured_either_way(self, client):
  83. ours = _project_file("20000")
  84. client._own_project_file_key = client._project_file_key(ours["print"])
  85. client._handle_request_message(ours)
  86. assert client._captured_ams_mapping == [0]