From aca4b1a4da49d8a72068f7095f27d828d9bd180c Mon Sep 17 00:00:00 2001 From: holger krekel Date: Thu, 7 Mar 2024 11:27:08 +0100 Subject: [PATCH] write a happy path test --- chatmaild/src/chatmaild/metadata.py | 62 ++++++++++++------- .../src/chatmaild/tests/test_metadata.py | 41 ++++++++++++ 2 files changed, 79 insertions(+), 24 deletions(-) create mode 100644 chatmaild/src/chatmaild/tests/test_metadata.py diff --git a/chatmaild/src/chatmaild/metadata.py b/chatmaild/src/chatmaild/metadata.py index f11027ae..6420943d 100644 --- a/chatmaild/src/chatmaild/metadata.py +++ b/chatmaild/src/chatmaild/metadata.py @@ -11,28 +11,58 @@ import os import requests +DICTPROXY_LOOKUP_CHAR = "L" +DICTPROXY_SET_CHAR = "S" +DICTPROXY_BEGIN_TRANSACTION_CHAR = "B" +DICTPROXY_COMMIT_TRANSACTION_CHAR = "C" +DICTPROXY_TRANSACTION_CHARS = "SBC" + + def handle_dovecot_protocol(rfile, wfile, tokens, requests_session, config: Config): # HELLO message, ignored. msg = rfile.readline().strip().decode() - transactions = {} + def post_with_token(token): + response = requests_session.post( + "https://notifications.delta.chat/notify", + data=token, + timeout=60, + ) + if response.status_code == 410: + # 410 Gone status code + # means the token is no longer valid. + del tokens[guid] + transactions = {} while True: msg = rfile.readline().strip().decode() if not msg: break - res = handle_dovecot_request(msg, tokens, requests_session, config) + res = handle_dovecot_request(msg, transactions, tokens, post_with_token) if res: wfile.write(res.encode("ascii")) wfile.flush() -def handle_dovecot_request(msg, tokens, requests_session, config: Config): +def handle_dovecot_request(msg, transactions, tokens, post_with_token): + # see https://doc.dovecot.org/3.0/developer_manual/design/dict_protocol/ short_command = msg[0] - if short_command == "L": + parts = msg[1:].split("\t") + if short_command == DICTPROXY_LOOKUP_CHAR: return b"N\n" - elif short_command == "S": + + if short_command not in (DICTPROXY_TRANSACTION_CHARS): + return + + transaction_id = parts[0] + + if short_command == DICTPROXY_BEGIN_TRANSACTION_CHAR: + transactions[transaction_id] = b"O\n" + elif short_command == DICTPROXY_COMMIT_TRANSACTION_CHAR: + # returns whether it failed or succeeded. + return transactions.pop(transaction_id, b"N\n") + elif short_command == DICTPROXY_SET_CHAR: # See header of # # for the documentation on the structure of the key. @@ -45,8 +75,6 @@ def handle_dovecot_request(msg, tokens, requests_session, config: Config): # results in # priv/dd72550f05eadc65542a1200cac67ad7/vendor/vendor.dovecot/pvt/server/chatmail - parts = msg[1:].split("\t") - transaction_id = parts[0] keyname = parts[1].split("/") value = parts[2] if len(parts) > 2 else "" if keyname[0] == "priv" and keyname[2] == "devicetoken": @@ -55,26 +83,10 @@ def handle_dovecot_request(msg, tokens, requests_session, config: Config): guid = keyname[1] token = tokens.get(guid) if token: - response = requests_session.post( - "https://notifications.delta.chat/notify", - data=token, - timeout=60, - ) - if response.status_code == 410: - # 410 Gone status code - # means the token is no longer valid. - del tokens[guid] + post_with_token(token) else: # Transaction failed. transactions[transaction_id] = b"F\n" - elif short_command == "B": - # Begin transaction. - transaction_id = msg[1:].split("\t")[0] - transactions[transaction_id] = b"O\n" - elif short_command == "C": - # Commit transaction. - transaction_id = msg[1:].split("\t")[0] - return transactions.pop(transaction_id, b"N\n") class ThreadedUnixStreamServer(ThreadingMixIn, UnixStreamServer): @@ -84,6 +96,8 @@ class ThreadedUnixStreamServer(ThreadingMixIn, UnixStreamServer): def main(): socket, username, config = sys.argv[1:] passwd_entry = pwd.getpwnam(username) + + # XXX config is not currently used config = read_config(config) tokens = {} requests_session = requests.Session() diff --git a/chatmaild/src/chatmaild/tests/test_metadata.py b/chatmaild/src/chatmaild/tests/test_metadata.py new file mode 100644 index 00000000..3173563c --- /dev/null +++ b/chatmaild/src/chatmaild/tests/test_metadata.py @@ -0,0 +1,41 @@ +from chatmaild.metadata import ( + handle_dovecot_request, +) + + +def test_handle_dovecot_request_happy_path(): + tokens = {} + transactions = {} + + # lookups return the same NOTFOUND result + res = handle_dovecot_request("Lpriv/123/chatmail", transactions, tokens, None) + assert res == b"N\n" + assert not tokens and not transactions + + # set device token in a transaction + tx = "1111" + msg = f"B{tx}\tuser" + res = handle_dovecot_request(msg, transactions, tokens, None) + assert not res and not tokens + assert transactions == {tx: b"O\n"} + + msg = f"S{tx}\tpriv/guid00/devicetoken\t01234" + res = handle_dovecot_request(msg, transactions, tokens, None) + assert not res + assert len(transactions) == 1 + assert len(tokens) == 1 and tokens["guid00"] == "01234" + + msg = f"C{tx}" + res = handle_dovecot_request(msg, transactions, tokens, None) + assert res == b"O\n" + assert len(transactions) == 0 + assert tokens["guid00"] == "01234" + + # trigger notification for incoming message + assert handle_dovecot_request(f"B{tx}\tuser", transactions, tokens, None) is None + msg = f"S{tx}\tpriv/guid00/messagenew" + requests = [] + assert handle_dovecot_request(msg, transactions, tokens, requests.append) is None + assert requests == ["01234"] + assert handle_dovecot_request(f"C{tx}\tuser", transactions, tokens, None) == b"O\n" + assert not transactions