"""The expectations grammar is undocumented, so the examples are the spec.""" import xml.etree.ElementTree as ET import pytest from mcbebop.protocol import xml_index from mcbebop.protocol.expectations import expectations_for, parse_expectation_text from mcbebop.protocol.types import Direction def test_every_block_is_parsed_and_only_sendable_commands_carry_one(): with_expectations = [c for c in xml_index.all_commands() if c.expectations] assert len(with_expectations) == 82 assert all(c.direction is Direction.TO_DRONE for c in with_expectations) def test_delayed_blocks(): delayed = [c.full_name for c in xml_index.all_commands() if any(e.delayed for e in c.expectations)] assert sorted(delayed) == ["ardrone3.Network.WifiScan", "ardrone3.Network.WifiAuthChannel"][::-1] def test_bare_reference(): (expectation,) = parse_expectation_text("#0-3-0") assert expectation.ids == (0, 3, 0) assert expectation.fields == {} assert expectation.alternatives == () assert expectation.delayed is False def test_this_dot_argname_means_the_drone_echoes_what_was_sent(): (expectation,) = xml_index.get("common.Settings.ProductName").expectations assert expectation.ids == (0, 3, 2) assert expectation.fields == {"name": "this.name"} def test_whitespace_separated_references_are_all_expected(): expectations = xml_index.get("common.Settings.AutoCountry").expectations assert len(expectations) == 2 assert expectations[0].ids == (0, 3, 7) assert expectations[0].fields == {"automatic": "this.automatic"} assert expectations[1].ids == (0, 3, 6) assert all(e.alternatives == () for e in expectations) def test_pipe_prefixed_references_are_alternatives(): (expectation,) = xml_index.get("ardrone3.Piloting.NavigateHome").expectations assert expectation.ids == (1, 4, 3) assert expectation.fields == {"state": "inProgress", "reason": "userRequest"} assert len(expectation.alternatives) == 2 assert [a.fields["state"] for a in expectation.alternatives] == ["pending", "available"] assert all(a.ids == (1, 4, 3) for a in expectation.alternatives) def test_alternatives_can_name_different_commands(): # StopPilotedPOI is confirmed by whichever POI event version is in play. (expectation,) = xml_index.get("ardrone3.Piloting.StopPilotedPOI").expectations assert expectation.ids == (1, 4, 14) assert [a.ids for a in expectation.alternatives] == [(1, 4, 22)] def test_literal_enum_member_values(): (expectation,) = xml_index.get("ardrone3.Piloting.StartPilotedPOI").expectations assert expectation.fields["status"] == "RUNNING" assert expectation.fields["latitude"] == "this.latitude" assert expectation.alternatives[0].fields["status"] == "PENDING" def test_takeoff_expects_two_states_not_one_of_two(): expectations = xml_index.get("ardrone3.Piloting.TakeOff").expectations assert [e.fields["state"] for e in expectations] == ["motor_ramping", "takingoff"] def test_delayed_flag_reaches_the_alternatives_too(): parsed = parse_expectation_text("#1-14-0 |#1-14-1", delayed=True) assert parsed[0].delayed is True assert parsed[0].alternatives[0].delayed is True def test_spaced_pipe_still_parses(): (expectation,) = parse_expectation_text("#0-1-2 | #0-1-3") assert [a.ids for a in expectation.alternatives] == [(0, 1, 3)] def test_empty_block_is_empty_not_an_error(): assert parse_expectation_text("\n\t\t\t") == () def test_leading_alternative_is_rejected(): with pytest.raises(ValueError, match="starts with an alternative"): parse_expectation_text("|#0-1-2") def test_text_without_a_reference_is_rejected(): with pytest.raises(ValueError, match="no #p-c-m reference"): parse_expectation_text("ProductNameChanged") def test_malformed_field_is_rejected(): with pytest.raises(ValueError, match="is not 'name: value'"): parse_expectation_text("#0-3-2(name)") def test_unknown_expectations_child_is_rejected(): cmd = ET.fromstring( "#0-0-0" ) with pytest.raises(ValueError, match="unknown expectations child"): expectations_for(cmd) def test_commands_without_a_block_have_an_empty_tuple(): assert xml_index.get("ardrone3.Piloting.PCMD").expectations == ()