diff --git a/bugbot/rules/assignee_no_login.py b/bugbot/rules/assignee_no_login.py index 456c49488..26aef6097 100644 --- a/bugbot/rules/assignee_no_login.py +++ b/bugbot/rules/assignee_no_login.py @@ -137,8 +137,8 @@ def get_priority_change_date(self, bug): for change in reversed(bug["history"]): if ( - change["field_name"] == "priority" - and change["added"] == current_priority + change.get("field_name") == "priority" + and change.get("added") == current_priority ): return datetime.strptime(change["when"], "%Y-%m-%dT%H:%M:%SZ") return None diff --git a/tests/rules/test_assignee_no_login_priority.py b/tests/rules/test_assignee_no_login_priority.py new file mode 100644 index 000000000..4dc3ad055 --- /dev/null +++ b/tests/rules/test_assignee_no_login_priority.py @@ -0,0 +1,108 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this file, +# You can obtain one at http://mozilla.org/MPL/2.0/. + +"""Regression tests for the AssigneeNoLogin rule's priority-change lookup. + +The production fix changed ``change["field_name"]`` and ``change["added"]`` to +``change.get("field_name")`` and ``change.get("added")`` in +``bugbot/rules/assignee_no_login.py:get_priority_change_date``. These tests +exercise both the pre-fix failure mode (a KeyError when history entries are +missing one of the expected keys) and the fixed behaviour. +""" + +from datetime import datetime, timezone + +from bugbot.rules.assignee_no_login import AssigneeNoLogin + + +def _make_rule(): + return AssigneeNoLogin( + username="test@example.com", + user_contact={"cf_id": 0, "url": "https://example.com/"}, + ) + + +def test_get_priority_change_date_returns_date_when_history_entry_matches(): + rule = _make_rule() + when = "2024-01-15T12:00:00Z" + bug = { + "priority": "P1", + "history": [ + {"field_name": "priority", "added": "P3", "when": "2023-01-01T00:00:00Z"}, + {"field_name": "priority", "added": "P1", "when": when}, + ], + } + + result = rule.get_priority_change_date(bug) + + assert result == datetime(2024, 1, 15, 12, 0, 0, tzinfo=timezone.utc) + + +def test_get_priority_change_date_handles_missing_field_name_without_keyerror(): + """A history entry without ``field_name`` must not crash the lookup.""" + rule = _make_rule() + when = "2024-05-20T08:30:00Z" + bug = { + "priority": "P2", + "history": [ + # Missing both "field_name" and "added" - this is what crashed + # before the fix. + {"when": "2024-04-01T00:00:00Z"}, + {"field_name": "status", "added": "NEW", "when": "2024-04-15T00:00:00Z"}, + {"field_name": "priority", "added": "P2", "when": when}, + ], + } + + result = rule.get_priority_change_date(bug) + + assert result == datetime(2024, 5, 20, 8, 30, 0, tzinfo=timezone.utc) + + +def test_get_priority_change_date_returns_none_when_no_match(): + rule = _make_rule() + bug = { + "priority": "P1", + "history": [ + {"field_name": "priority", "added": "P2", "when": "2024-01-01T00:00:00Z"}, + { + "field_name": "status", + "added": "RESOLVED", + "when": "2024-02-01T00:00:00Z", + }, + ], + } + + assert rule.get_priority_change_date(bug) is None + + +def test_get_priority_change_date_skips_entries_missing_field_name(): + """Entries that lack ``field_name`` must be skipped, not raise KeyError.""" + rule = _make_rule() + bug = { + "priority": "P1", + "history": [ + # No "field_name" key at all. + {"added": "P1", "when": "2024-03-10T00:00:00Z"}, + ], + } + + # Should not raise KeyError. The fixed implementation falls back to None + # when the field_name check fails (None != "priority"). + assert rule.get_priority_change_date(bug) is None + + +def test_get_priority_change_date_picks_most_recent_match(): + rule = _make_rule() + bug = { + "priority": "P3", + "history": [ + {"field_name": "priority", "added": "P1", "when": "2023-06-01T00:00:00Z"}, + {"field_name": "priority", "added": "P2", "when": "2024-02-01T00:00:00Z"}, + {"field_name": "priority", "added": "P3", "when": "2024-08-15T00:00:00Z"}, + ], + } + + result = rule.get_priority_change_date(bug) + + assert result == datetime(2024, 8, 15, 0, 0, 0, tzinfo=timezone.utc)