summaryrefslogtreecommitdiffstats
path: root/packages/meshbay-common/tests/test_version_negotiation.py
blob: 50bdf549dd8fed1d9082f844abc4c6027e5a60e1 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
"""
The version range, checked rather than merely written.

Before MNP 1.0 every message carried a `v` that no one read, so a mismatch
surfaced as a *missing field*: an 0.x client reading a 1.0 handshake ack finds no
`enabled_apps` and applies its documented fallback — show every app — which is a
wrong answer rather than an error. The three failure modes 1.0's flag day was
called for all present that way, which is why negotiation ships in the same
deployment rather than after it (phase 15.6, decision D2).
"""

import pytest
from meshbay_common import MNP_VERSION
from meshbay_common.handshake import (
    MNP_MIN_SUPPORTED,
    HandshakeError,
    check_version,
    parse_version,
)


def test_this_build_accepts_itself():
    check_version(MNP_VERSION, MNP_MIN_SUPPORTED)


def test_a_peer_that_declares_no_minimum_is_read_as_speaking_only_its_own():
    """
    Which is the right reading of every 0.x peer: none of them declared a range,
    because none of them checked one.
    """
    check_version(MNP_VERSION)


def test_an_older_peer_is_refused_with_a_code():
    with pytest.raises(HandshakeError) as caught:
        check_version("0.15")
    assert caught.value.code == "version_too_old"
    # The text is for a human and may be reworded; the client matches the code.
    assert "0.15" in str(caught.value)


def test_a_peer_requiring_more_than_we_speak_is_refused_with_a_code():
    ours = parse_version(MNP_VERSION)
    future = f"{ours[0] + 1}.0"
    with pytest.raises(HandshakeError) as caught:
        check_version(future, future)
    assert caught.value.code == "version_too_new"


def test_a_newer_peer_that_still_accepts_us_is_allowed():
    """
    The point of a range rather than an equality: a 1.4 node that still speaks to
    1.0 clients must not refuse one.
    """
    ours = parse_version(MNP_VERSION)
    check_version(f"{ours[0]}.{ours[1] + 4}", MNP_MIN_SUPPORTED)


@pytest.mark.parametrize("bad", ["", "one.two", "1", None, "1.0.0", "v1.0"])
def test_an_unreadable_version_is_refused_not_guessed(bad):
    with pytest.raises(HandshakeError) as caught:
        check_version(bad)
    assert caught.value.code == "version_unreadable"


def test_versions_order_numerically_not_lexically():
    """`"0.9" < "0.15"` as strings, and the opposite as versions."""
    assert parse_version("0.9") < parse_version("0.15") < parse_version("1.0")


def test_mnp_1_0_is_a_major_bump():
    """
    Recorded as a test because the number is the only thing that says "this one is
    different". 1.0 seals the index and the ack under the group key: no 0.x peer
    can open either, and there is nothing to be compatible with.
    """
    assert parse_version(MNP_VERSION) >= (1, 0)
    assert parse_version(MNP_MIN_SUPPORTED) >= (1, 0)