blob: c9ea5f6840bbc10488a11c895c28c9db74260b02 [file]
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
from __future__ import annotations
import io
import json
import urllib.error
from unittest.mock import patch
import pytest
from oauth_draft.message_id import main, parse_args, root_message_id
def test_parse_args_collects_thread_ids():
args = parse_args(["t1", "t2", "t3"])
assert args.thread_ids == ["t1", "t2", "t3"]
assert args.json is False
def test_parse_args_requires_at_least_one_thread():
with pytest.raises(SystemExit):
parse_args([])
# --- network-mocked helpers ------------------------------------------------
class _FakeResponse:
def __init__(self, payload: bytes):
self._payload = payload
def __enter__(self):
return self
def __exit__(self, *exc):
return False
def read(self):
return self._payload
def _thread_payload(message_id: str | None, *, messages: bool = True) -> bytes:
if not messages:
return json.dumps({"messages": []}).encode()
headers = [{"name": "Subject", "value": "report"}]
if message_id is not None:
# Mixed-case header name on purpose — lookup is case-insensitive.
headers.append({"name": "Message-Id", "value": message_id})
return json.dumps({"messages": [{"payload": {"headers": headers}}]}).encode()
def test_root_message_id_extracts_header_case_insensitively():
payload = _thread_payload("<abc@example.com>")
with patch(
"oauth_draft.message_id.urllib.request.urlopen",
return_value=_FakeResponse(payload),
) as mock_open:
mid = root_message_id("token", "tid-1")
assert mid == "<abc@example.com>"
request = mock_open.call_args.args[0]
assert "/threads/tid-1" in request.full_url
assert "format=metadata" in request.full_url
assert "metadataHeaders=Message-ID" in request.full_url
def test_root_message_id_uses_first_message_as_root():
payload = json.dumps(
{
"messages": [
{"payload": {"headers": [{"name": "Message-ID", "value": "<root@x>"}]}},
{"payload": {"headers": [{"name": "Message-ID", "value": "<reply@x>"}]}},
]
}
).encode()
with patch(
"oauth_draft.message_id.urllib.request.urlopen",
return_value=_FakeResponse(payload),
):
assert root_message_id("token", "tid") == "<root@x>"
def test_root_message_id_returns_none_when_no_messages():
with patch(
"oauth_draft.message_id.urllib.request.urlopen",
return_value=_FakeResponse(_thread_payload(None, messages=False)),
):
assert root_message_id("token", "tid") is None
def test_root_message_id_returns_none_when_header_absent():
with patch(
"oauth_draft.message_id.urllib.request.urlopen",
return_value=_FakeResponse(_thread_payload(None)),
):
assert root_message_id("token", "tid") is None
def test_root_message_id_raises_on_http_error():
err = urllib.error.HTTPError(
url="https://x",
code=404,
msg="Not Found",
hdrs=None, # type: ignore[arg-type]
fp=io.BytesIO(b'{"error": "missing"}'),
)
with patch(
"oauth_draft.message_id.urllib.request.urlopen",
side_effect=err,
):
with pytest.raises(SystemExit) as excinfo:
root_message_id("token", "tid-x")
assert "threads.get failed (404) for tid-x" in str(excinfo.value)
# --- main ------------------------------------------------------------------
def _make_creds_file(tmp_path):
p = tmp_path / "creds.json"
p.write_text(
json.dumps(
{
"client_id": "cid",
"client_secret": "secret",
"refresh_token": "refresh",
}
)
)
return p
def test_main_tsv_output(tmp_path, capsys):
creds = _make_creds_file(tmp_path)
with (
patch("oauth_draft.message_id.refresh_access_token", return_value="tok"),
patch(
"oauth_draft.message_id.root_message_id",
side_effect=["<a@x>", None],
),
):
rc = main(["--credentials", str(creds), "t1", "t2"])
assert rc == 0
out = capsys.readouterr().out.splitlines()
assert out == ["t1\t<a@x>", "t2\t"]
def test_main_json_output(tmp_path, capsys):
creds = _make_creds_file(tmp_path)
with (
patch("oauth_draft.message_id.refresh_access_token", return_value="tok"),
patch("oauth_draft.message_id.root_message_id", return_value="<a@x>"),
):
rc = main(["--credentials", str(creds), "--json", "t1"])
assert rc == 0
assert json.loads(capsys.readouterr().out) == {"t1": "<a@x>"}