test_drying_preflight.py 3.5 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586
  1. """The shared drying preflight's blocked-reason rules (#2638).
  2. An AMS can report several ``dry_sf_reason`` codes at once, and the two callers
  3. render that differently: the immediate endpoint raises the message, the
  4. scheduler stores a token the frontend translates. Both pick the code through
  5. ``primary_reason_code`` so one blocked AMS is not described two ways.
  6. """
  7. import pytest
  8. from backend.app.services import drying_preflight as preflight
  9. pytestmark = pytest.mark.unit
  10. class TestPrimaryReasonCode:
  11. def test_a_single_code_is_returned_as_is(self):
  12. assert preflight.primary_reason_code([2]) == 2
  13. @pytest.mark.parametrize("power_code", sorted(preflight.POWER_REASON_CODES))
  14. def test_power_outranks_a_transient_code(self, power_code):
  15. """ "AMS is busy" clears by itself; "plug the PSU in" does not, and the
  16. user can only act on one of them."""
  17. assert preflight.primary_reason_code([2, power_code]) == power_code
  18. assert preflight.primary_reason_code([power_code, 2]) == power_code
  19. def test_power_outranks_retract(self):
  20. assert preflight.primary_reason_code([preflight.RETRACT_REASON_CODE, 1]) == 1
  21. def test_retract_outranks_a_transient_code(self):
  22. assert preflight.primary_reason_code([0, preflight.RETRACT_REASON_CODE]) == preflight.RETRACT_REASON_CODE
  23. def test_transient_codes_keep_the_reported_order(self):
  24. assert preflight.primary_reason_code([6, 2, 4]) == 6
  25. def test_no_codes_is_no_answer(self):
  26. assert preflight.primary_reason_code([]) is None
  27. class TestWaitingReason:
  28. def test_power(self):
  29. assert preflight.waiting_reason_for_codes([2, 8]) == preflight.WAITING_REASON_POWER
  30. def test_retract(self):
  31. assert preflight.waiting_reason_for_codes([2, 3]) == preflight.WAITING_REASON_RETRACT
  32. def test_transient_falls_through_to_the_generic_token(self):
  33. assert preflight.waiting_reason_for_codes([2]) == preflight.WAITING_REASON_BLOCKED
  34. def test_no_codes_is_not_a_specific_reason(self):
  35. """Callers only ask when something is blocking, but answering with a
  36. power alert for an empty list would be worse than saying nothing
  37. specific."""
  38. assert preflight.waiting_reason_for_codes([]) == preflight.WAITING_REASON_BLOCKED
  39. class TestBothPathsAgree:
  40. """The endpoint quotes a message and the scheduler stores a token. Whatever
  41. they pick, it has to be the same code underneath."""
  42. @pytest.mark.parametrize(
  43. "codes,expected_token",
  44. [
  45. ([2, 1], preflight.WAITING_REASON_POWER),
  46. ([2, 8], preflight.WAITING_REASON_POWER),
  47. ([0, 3], preflight.WAITING_REASON_RETRACT),
  48. ([6, 2], preflight.WAITING_REASON_BLOCKED),
  49. ],
  50. )
  51. def test_the_message_and_the_token_describe_one_code(self, codes, expected_token):
  52. code = preflight.primary_reason_code(codes)
  53. # What the immediate endpoint raises.
  54. assert code in preflight.DRY_SF_REASON_MESSAGES
  55. # What the scheduled row records, for the same code.
  56. assert preflight.waiting_reason_for_codes(codes) == expected_token
  57. class TestBlockingReasonCodes:
  58. def test_unknown_and_malformed_codes_are_dropped(self):
  59. """A firmware that grows a code we have no message for must not block a
  60. run with an unexplainable reason."""
  61. assert preflight.blocking_reason_codes({"dry_sf_reason": [2, 99, "x", None]}) == [2]
  62. def test_a_missing_unit_is_not_blocking(self):
  63. assert preflight.blocking_reason_codes(None) == []