ci: Add and configure mypy, and make it happy

This commit is contained in:
2023-11-01 07:22:29 +01:00
parent f2a54b5ce6
commit 1c1e57d868
13 changed files with 374 additions and 214 deletions

View File

@@ -50,7 +50,7 @@ SERIALIZED_M1 = b"""{
@pytest.fixture()
def local_feed():
def local_feed() -> LocalFeed:
"""Fixture providing a local feed"""
secret = b64decode("Mz2qkNOP2K6upnqibWrR+z8pVUI1ReA1MLc7QMtF2qQ=")
@@ -58,14 +58,14 @@ def local_feed():
@pytest.fixture()
def remote_feed():
def remote_feed() -> Feed:
"""Fixture providing a remote feed"""
public = b64decode("I/4cyN/jPBbDsikbHzAEvmaYlaJK33lW3UhWjNXjyrU=")
return Feed(VerifyKey(public))
def test_local_feed():
def test_local_feed() -> None:
"""Test a local feed"""
secret = b64decode("Mz2qkNOP2K6upnqibWrR+z8pVUI1ReA1MLc7QMtF2qQ=")
@@ -75,7 +75,7 @@ def test_local_feed():
assert feed.id == "@I/4cyN/jPBbDsikbHzAEvmaYlaJK33lW3UhWjNXjyrU=.ed25519"
def test_remote_feed():
def test_remote_feed() -> None:
"""Test a remote feed"""
public = b64decode("I/4cyN/jPBbDsikbHzAEvmaYlaJK33lW3UhWjNXjyrU=")
@@ -85,35 +85,21 @@ def test_remote_feed():
m1 = Message(
feed,
OrderedDict(
[
("type", "about"),
("about", feed.id),
("name", "neo"),
("description", "The Chosen One"),
]
),
OrderedDict([("type", "about"), ("about", feed.id), ("name", "neo"), ("description", "The Chosen One")]),
"foo",
timestamp=1495706260190,
)
with pytest.raises(NoPrivateKeyException):
feed.sign(m1)
feed.sign(m1.serialize())
def test_local_message(local_feed): # pylint: disable=redefined-outer-name
def test_local_message(local_feed: LocalFeed) -> None: # pylint: disable=redefined-outer-name
"""Test a local message"""
m1 = LocalMessage(
local_feed,
OrderedDict(
[
("type", "about"),
("about", local_feed.id),
("name", "neo"),
("description", "The Chosen One"),
]
),
OrderedDict([("type", "about"), ("about", local_feed.id), ("name", "neo"), ("description", "The Chosen One")]),
timestamp=1495706260190,
)
assert m1.timestamp == 1495706260190
@@ -148,20 +134,13 @@ def test_local_message(local_feed): # pylint: disable=redefined-outer-name
assert m2.key == "%nx13uks5GUwuKJC49PfYGMS/1pgGTtwwdWT7kbVaroM=.sha256"
def test_remote_message(remote_feed): # pylint: disable=redefined-outer-name
def test_remote_message(remote_feed: Feed) -> None: # pylint: disable=redefined-outer-name
"""Test a remote message"""
signature = "lPsQ9P10OgeyH6u0unFgiI2wV/RQ7Q2x2ebxnXYCzsJ055TBMXphRADTKhOMS2EkUxXQ9k3amj5fnWPudGxwBQ==.sig.ed25519"
m1 = Message(
remote_feed,
OrderedDict(
[
("type", "about"),
("about", remote_feed.id),
("name", "neo"),
("description", "The Chosen One"),
]
),
OrderedDict([("type", "about"), ("about", remote_feed.id), ("name", "neo"), ("description", "The Chosen One")]),
signature,
timestamp=1495706260190,
)
@@ -175,12 +154,7 @@ def test_remote_message(remote_feed): # pylint: disable=redefined-outer-name
m2 = Message(
remote_feed,
OrderedDict(
[
("type", "about"),
("about", remote_feed.id),
("name", "morpheus"),
("description", "Dude with big jaw"),
]
[("type", "about"), ("about", remote_feed.id), ("name", "morpheus"), ("description", "Dude with big jaw")]
),
signature,
previous=m1,
@@ -194,54 +168,37 @@ def test_remote_message(remote_feed): # pylint: disable=redefined-outer-name
assert m2.key == "%nx13uks5GUwuKJC49PfYGMS/1pgGTtwwdWT7kbVaroM=.sha256"
def test_remote_no_signature(remote_feed): # pylint: disable=redefined-outer-name
def test_remote_no_signature(remote_feed: Feed) -> None: # pylint: disable=redefined-outer-name
"""Test remote feed without a signature"""
with pytest.raises(ValueError):
Message(
remote_feed,
OrderedDict(
[
("type", "about"),
("about", remote_feed.id),
("name", "neo"),
("description", "The Chosen One"),
]
[("type", "about"), ("about", remote_feed.id), ("name", "neo"), ("description", "The Chosen One")]
),
None,
timestamp=1495706260190,
)
def test_serialize(local_feed): # pylint: disable=redefined-outer-name
def test_serialize(local_feed: LocalFeed) -> None: # pylint: disable=redefined-outer-name
"""Test feed serialization"""
m1 = LocalMessage(
local_feed,
OrderedDict(
[
("type", "about"),
("about", local_feed.id),
("name", "neo"),
("description", "The Chosen One"),
]
),
OrderedDict([("type", "about"), ("about", local_feed.id), ("name", "neo"), ("description", "The Chosen One")]),
timestamp=1495706260190,
)
assert m1.serialize() == SERIALIZED_M1
def test_parse(local_feed): # pylint: disable=redefined-outer-name
def test_parse(local_feed: LocalFeed) -> None: # pylint: disable=redefined-outer-name
"""Test feed parsing"""
m1 = LocalMessage.parse(SERIALIZED_M1, local_feed)
assert m1.content == {
"type": "about",
"about": local_feed.id,
"name": "neo",
"description": "The Chosen One",
}
assert m1.content == {"type": "about", "about": local_feed.id, "name": "neo", "description": "The Chosen One"}
assert m1.timestamp == 1495706260190
@@ -252,15 +209,15 @@ def test_local_unsigned(local_feed: LocalFeed, mocker: MockerFixture) -> None:
mocked_dt.utcnow = mocker.MagicMock(return_value=datetime(2023, 3, 7, 11, 45, 54, 0, tzinfo=timezone.utc))
mocker.patch("ssb.feed.models.datetime", mocked_dt)
msg = LocalMessage(local_feed, b"test")
msg = LocalMessage(local_feed, OrderedDict({"test": True}))
assert msg.feed == local_feed
assert msg.content == b"test"
assert msg.content == {"test": True}
assert msg.sequence == 1
assert msg.previous is None
assert msg.timestamp == 1678189554000
assert msg.signature == (
"SxZsBINzsuQqmB6JLmXyr22+FRY33bp3wj1MwjAOU3MqifGqfc3W/2T5D4qel5mqrgJt9IT8c3QayB1suj82AQ==.sig.ed25519"
"WjkA5rjzsYDHqeavEPcbNAbRMp5NRFDBNATMWgcsccso8sfwhaWnIEvQW79fA5YgKKybzlIsCMWHherToEI2DA==.sig.ed25519"
)

View File

@@ -23,18 +23,23 @@
"""Tests for the packet stream"""
from asyncio import Event, ensure_future, gather
from asyncio.events import AbstractEventLoop
import json
from typing import Any, AsyncIterator, Awaitable, Callable, Generator, List, Optional
import pytest
from pytest_mock import MockerFixture
from secret_handshake.network import SHSDuplexStream
from ssb.packet_stream import PacketStream, PSMessageType
from ssb.packet_stream import PacketStream, PSMessage, PSMessageType
async def _collect_messages(generator):
async def _collect_messages(generator: AsyncIterator[Optional[PSMessage]]) -> List[Optional["PSMessage"]]:
results = []
async for msg in generator:
results.append(msg)
return results
@@ -61,45 +66,47 @@ MSG_BODY_2 = (
class MockSHSSocket(SHSDuplexStream):
"""A mocked SHS socket"""
def __init__(self, *args, **kwargs): # pylint: disable=unused-argument
def __init__(self, *args: Any, **kwargs: Any): # pylint: disable=unused-argument
super().__init__()
self.input = []
self.output = []
self.is_connected = False
self._on_connect = []
self.input: List[bytes] = []
self.output: List[bytes] = []
self.is_connected: bool = False
self._on_connect: List[Callable[[], Awaitable[None]]] = []
def on_connect(self, cb):
def on_connect(self, cb: Callable[[], Awaitable[None]]) -> None:
"""Set the on_connect callback"""
self._on_connect.append(cb)
async def read(self):
async def read(self) -> Optional[bytes]:
"""Read data from the socket"""
if not self.input:
raise StopAsyncIteration
return None
return self.input.pop(0)
def write(self, data):
def write(self, data: bytes) -> None:
"""Write data to the socket"""
self.output.append(data)
def feed(self, input_):
"""Get the connections feed"""
def feed(self, input_: List[bytes]) -> None:
"""Feed data into the connection"""
self.input += input_
def get_output(self):
def get_output(self) -> Generator[bytes, None, None]:
"""Get the output of a call"""
while True:
if not self.output:
break
yield self.output.pop(0)
def disconnect(self):
def disconnect(self) -> None:
"""Disconnect from the remote party"""
self.is_connected = False
@@ -108,7 +115,7 @@ class MockSHSSocket(SHSDuplexStream):
class MockSHSClient(MockSHSSocket):
"""A mocked SHS client"""
async def connect(self):
async def connect(self) -> None:
"""Connect to a SHS server"""
self.is_connected = True
@@ -120,7 +127,7 @@ class MockSHSClient(MockSHSSocket):
class MockSHSServer(MockSHSSocket):
"""A mocked SHS server"""
def listen(self):
def listen(self) -> None:
"""Listen for new connections"""
self.is_connected = True
@@ -130,26 +137,26 @@ class MockSHSServer(MockSHSSocket):
@pytest.fixture
def ps_client(event_loop): # pylint: disable=unused-argument
def ps_client(event_loop: AbstractEventLoop) -> MockSHSClient: # pylint: disable=unused-argument
"""Fixture to provide a mocked SHS client"""
return MockSHSClient()
@pytest.fixture
def ps_server(event_loop): # pylint: disable=unused-argument
def ps_server(event_loop: AbstractEventLoop) -> MockSHSServer: # pylint: disable=unused-argument
"""Fixture to provide a mocked SHS server"""
return MockSHSServer()
@pytest.mark.asyncio
async def test_on_connect(ps_server): # pylint: disable=redefined-outer-name
async def test_on_connect(ps_server: MockSHSServer) -> None: # pylint: disable=redefined-outer-name
"""Test the on_connect callback functionality"""
called = Event()
async def _on_connect():
async def _on_connect() -> None:
called.set()
ps_server.on_connect(_on_connect)
@@ -159,7 +166,7 @@ async def test_on_connect(ps_server): # pylint: disable=redefined-outer-name
@pytest.mark.asyncio
async def test_message_decoding(ps_client): # pylint: disable=redefined-outer-name
async def test_message_decoding(ps_client: MockSHSClient) -> None: # pylint: disable=redefined-outer-name
"""Test message decoding"""
await ps_client.connect()
@@ -178,6 +185,7 @@ async def test_message_decoding(ps_client): # pylint: disable=redefined-outer-n
messages = await _collect_messages(ps)
assert len(messages) == 1
assert messages[0]
assert messages[0].type == PSMessageType.JSON
assert messages[0].body == {
"name": ["createHistoryStream"],
@@ -194,7 +202,7 @@ async def test_message_decoding(ps_client): # pylint: disable=redefined-outer-n
@pytest.mark.asyncio
async def test_message_encoding(ps_client): # pylint: disable=redefined-outer-name
async def test_message_encoding(ps_client: MockSHSClient) -> None: # pylint: disable=redefined-outer-name
"""Test message encoding"""
await ps_client.connect()
@@ -237,7 +245,9 @@ async def test_message_encoding(ps_client): # pylint: disable=redefined-outer-n
@pytest.mark.asyncio
async def test_message_stream(ps_client, mocker): # pylint: disable=redefined-outer-name
async def test_message_stream(
ps_client: MockSHSClient, mocker: MockerFixture # pylint: disable=redefined-outer-name
) -> None: # pylint: disable=redefined-outer-name
"""Test requesting a history stream"""
await ps_client.connect()
@@ -264,7 +274,7 @@ async def test_message_stream(ps_client, mocker): # pylint: disable=redefined-o
)
assert ps.req_counter == 2
assert ps.register_handler.call_count == 1 # pylint: disable=no-member
assert ps.register_handler.call_count == 1 # type: ignore[attr-defined] # pylint: disable=no-member
handler = list(ps._event_map.values())[0][1] # pylint: disable=protected-access
mock_process = mocker.patch.object(handler, "process")
@@ -273,6 +283,8 @@ async def test_message_stream(ps_client, mocker): # pylint: disable=redefined-o
assert mock_process.await_count == 1
# responses have negative req
assert msg
assert isinstance(msg.body, dict)
assert msg.req == -1
assert msg.body["previous"] == "%KTGP6W8vF80McRAZHYDWuKOD0KlNyKSq6Gb42iuV7Iw=.sha256"
@@ -295,7 +307,7 @@ async def test_message_stream(ps_client, mocker): # pylint: disable=redefined-o
)
assert ps.req_counter == 3
assert ps.register_handler.call_count == 2 # pylint: disable=no-member
assert ps.register_handler.call_count == 2 # type: ignore[attr-defined] # pylint: disable=no-member
handler = list(ps._event_map.values())[1][1] # pylint: disable=protected-access
mock_process = mocker.patch.object(handler, "process", wraps=handler.process)
@@ -318,11 +330,14 @@ async def test_message_stream(ps_client, mocker): # pylint: disable=redefined-o
for msg in handled:
# responses have negative req
assert msg
assert msg.req == -2
@pytest.mark.asyncio
async def test_message_request(ps_server, mocker): # pylint: disable=redefined-outer-name
async def test_message_request(
ps_server: MockSHSServer, mocker: MockerFixture # pylint: disable=redefined-outer-name
) -> None: # pylint: disable=redefined-outer-name
"""Test message sending"""
ps_server.listen()
@@ -338,7 +353,7 @@ async def test_message_request(ps_server, mocker): # pylint: disable=redefined-
assert json.loads(body.decode("utf-8")) == {"name": ["whoami"], "args": []}
assert ps.req_counter == 2
assert ps.register_handler.call_count == 1 # pylint: disable=no-member
assert ps.register_handler.call_count == 1 # type: ignore[attr-defined] # pylint: disable=no-member
handler = list(ps._event_map.values())[0][1] # pylint: disable=protected-access
mock_process = mocker.patch.object(handler, "process")
ps_server.feed(
@@ -351,6 +366,8 @@ async def test_message_request(ps_server, mocker): # pylint: disable=redefined-
assert mock_process.await_count == 1
# responses have negative req
assert msg
assert isinstance(msg.body, dict)
assert msg.req == -1
assert msg.body["id"] == "@1+Iwm79DKvVBqYKFkhT6fWRbAVvNNVH4F2BSxwhYmx8=.ed25519"
assert ps.req_counter == 2

View File

@@ -42,7 +42,7 @@ CONFIG_FILE = """
CONFIG_FILE_INVALID = CONFIG_FILE.replace("ed25519", "foo")
def test_load_secret():
def test_load_secret() -> None:
"""Test loading the SSB secret from a file"""
with patch("ssb.util.open", mock_open(read_data=CONFIG_FILE), create=True):
@@ -55,7 +55,7 @@ def test_load_secret():
assert bytes(secret["keypair"].verify_key) == b64decode("rsYpBIcXsxjQAf0JNes+MHqT2DL+EfopWKAp4rGeEPQ=")
def test_load_exception():
def test_load_exception() -> None:
"""Test configuration loading if there is a problem with the file"""
with pytest.raises(ConfigException):