From 2dbf31ea169665d3dd01d2bf9b01f915cbfe88b3 Mon Sep 17 00:00:00 2001 From: holger krekel Date: Tue, 25 Aug 2026 10:54:45 +0200 Subject: [PATCH] test: integrate lua testing into regular pytest run for push notifications turns out Python has the nice https://pypi.org/project/lupa/ that allows us to quite easily test dovecot LUA parts without figuring out errors on deploy. --- cmdeploy/pyproject.toml | 1 + cmdeploy/src/cmdeploy/tests/conftest.py | 25 ++++++ cmdeploy/src/cmdeploy/tests/test_pushlua.py | 85 +++++++++++++++++++++ 3 files changed, 111 insertions(+) create mode 100644 cmdeploy/src/cmdeploy/tests/conftest.py create mode 100644 cmdeploy/src/cmdeploy/tests/test_pushlua.py diff --git a/cmdeploy/pyproject.toml b/cmdeploy/pyproject.toml index bedc2bd4..1ddd4323 100644 --- a/cmdeploy/pyproject.toml +++ b/cmdeploy/pyproject.toml @@ -19,6 +19,7 @@ dependencies = [ "pytest-xdist", "execnet", "imap_tools", + "lupa", "deltachat-rpc-client", "deltachat-rpc-server", ] diff --git a/cmdeploy/src/cmdeploy/tests/conftest.py b/cmdeploy/src/cmdeploy/tests/conftest.py new file mode 100644 index 00000000..857e8ed6 --- /dev/null +++ b/cmdeploy/src/cmdeploy/tests/conftest.py @@ -0,0 +1,25 @@ +"""Run the lua scripts we ship under lupa, which bundles Lua 5.4 like dovecot.""" + +import pytest +from lupa import lua54 + +from cmdeploy.basedeploy import get_resource + + +class Lua: + """A Lua runtime to load shipped scripts and mocks into.""" + + def __init__(self): + self.rt = lua54.LuaRuntime(unpack_returned_tuples=True) + self.g = self.rt.globals() + + def load(self, path): + self.rt.execute(get_resource(path).read_text()) + + def table(self, **kwargs): + return self.rt.table(**kwargs) + + +@pytest.fixture +def lua(): + return Lua() diff --git a/cmdeploy/src/cmdeploy/tests/test_pushlua.py b/cmdeploy/src/cmdeploy/tests/test_pushlua.py new file mode 100644 index 00000000..b2c2440a --- /dev/null +++ b/cmdeploy/src/cmdeploy/tests/test_pushlua.py @@ -0,0 +1,85 @@ +"""Test the push_notification.lua we ship, against a mocked dovecot mail API.""" + +import textwrap + +import pytest + +USER1 = "user12345@chat.example.org" +USER2 = "user67890@chat.example.org" + +DOVECOT_MOCKS = textwrap.dedent(""" + function make_user(username) + local function mailbox(_, name) + record("mailbox " .. name) + return { + sync = function() record("sync") end, + metadata_set = function(_, k, v) + record("metadata_set " .. k .. "=" .. v) + end, + free = function() record("free") end, + } + end + return {username = username, mailbox = mailbox} + end +""") + + +@pytest.fixture +def script(lua): + lua.rt.execute(DOVECOT_MOCKS) + lua.load("dovecot/push_notification.lua") + return lua + + +@pytest.fixture +def deliver(script): + def deliver(recipient, sender): + calls = [] + script.g.record = calls.append + user = script.g.make_user(recipient) + ctx = script.g.dovecot_lua_notify_begin_txn(user) + event = script.table(mailbox="INBOX", from_address=sender) + script.g.dovecot_lua_notify_event_message_new(ctx, event) + script.g.dovecot_lua_notify_end_txn(ctx, True) + return calls + + return deliver + + +def test_entry_points_have_the_names_dovecot_calls(script): + assert script.g.dovecot_lua_notify_begin_txn is not None + assert script.g.dovecot_lua_notify_event_message_new is not None + assert script.g.dovecot_lua_notify_end_txn is not None + + +def test_begin_txn_returns_the_user_as_event_context(script): + user = script.g.make_user(USER1) + ctx = script.g.dovecot_lua_notify_begin_txn(user) + ctx.marker = "seen" + assert user.marker == "seen" + + +def test_incoming_message_notifies_metadata_server(deliver): + assert deliver(USER1, sender=USER2) == [ + "mailbox INBOX", + "sync", + "metadata_set /private/messagenew=", + "free", + ] + + +def test_own_message_does_not_wake_the_sending_device(deliver): + assert deliver(USER1, sender=USER1) == [ + "mailbox INBOX", + "sync", + "free", + ] + + +def test_message_without_from_address_is_notified(deliver): + assert deliver(USER1, sender=None) == [ + "mailbox INBOX", + "sync", + "metadata_set /private/messagenew=", + "free", + ]