From 106449ef9b71a517007fe4ee253089b49a7e423a Mon Sep 17 00:00:00 2001 From: Aman Sachan Date: Mon, 1 Jun 2026 00:34:49 +0000 Subject: [PATCH 1/4] fix: guard against KeyError in get_priority_change_date by using .get() The change history entries may not always contain 'field_name' or 'added' keys, causing a KeyError when iterating through bug history. Using .get() with None default prevents the crash while preserving the original logic: only entries with field_name=='priority' and matching added value are used. Fixes mozilla/bugbot#2607 --- bugbot/rules/assignee_no_login.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 From 658e5a1cea0bc553e6bb268cfb069f71da259ccc Mon Sep 17 00:00:00 2001 From: Aman Sachan Date: Sat, 6 Jun 2026 01:41:52 +0000 Subject: [PATCH 2/4] test: add regression tests for get_priority_change_date KeyError fix Adds regression coverage for the change in mozilla/bugbot#2901 that replaced change['field_name']/change['added'] with change.get(...) in AssigneeNoLogin.get_priority_change_date. Tests cover: - matching the most recent priority change - history entries missing field_name or added (the original crash) - returning None when no priority change matches - skipping malformed entries without raising Helps restore the test coverage that dropped after #2901. --- .../rules/test_assignee_no_login_priority.py | 104 ++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 bugbot/tests/rules/test_assignee_no_login_priority.py diff --git a/bugbot/tests/rules/test_assignee_no_login_priority.py b/bugbot/tests/rules/test_assignee_no_login_priority.py new file mode 100644 index 000000000..0a0264f61 --- /dev/null +++ b/bugbot/tests/rules/test_assignee_no_login_priority.py @@ -0,0 +1,104 @@ +# 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, timedelta, 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) From 4b6adb3b0cba32d4eee9a4fe0b8dfc176dfbab66 Mon Sep 17 00:00:00 2001 From: Aman Sachan Date: Sat, 6 Jun 2026 01:42:38 +0000 Subject: [PATCH 3/4] test: place new test file in correct path --- {bugbot/tests => tests}/rules/test_assignee_no_login_priority.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {bugbot/tests => tests}/rules/test_assignee_no_login_priority.py (100%) diff --git a/bugbot/tests/rules/test_assignee_no_login_priority.py b/tests/rules/test_assignee_no_login_priority.py similarity index 100% rename from bugbot/tests/rules/test_assignee_no_login_priority.py rename to tests/rules/test_assignee_no_login_priority.py From 3bca3c28c3bae4c476ac7be5fedbdcb5f2bc3764 Mon Sep 17 00:00:00 2001 From: Aman Sachan Date: Mon, 8 Jun 2026 01:18:52 +0000 Subject: [PATCH 4/4] style: fix ruff and ruff-format issues in test_assignee_no_login_priority - Remove unused 'timedelta' import (ruff F401) - Wrap long dict literal in test_get_priority_change_date_returns_none_when_no_match across multiple lines to satisfy ruff-format line length These fixes resolve the pre-commit hook failures that were blocking CI on mozilla/bugbot#2901. The pre-commit hooks were auto-fixing the file but still exited non-zero when files were modified, which failed the check. --- tests/rules/test_assignee_no_login_priority.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/rules/test_assignee_no_login_priority.py b/tests/rules/test_assignee_no_login_priority.py index 0a0264f61..4dc3ad055 100644 --- a/tests/rules/test_assignee_no_login_priority.py +++ b/tests/rules/test_assignee_no_login_priority.py @@ -11,7 +11,7 @@ missing one of the expected keys) and the fixed behaviour. """ -from datetime import datetime, timedelta, timezone +from datetime import datetime, timezone from bugbot.rules.assignee_no_login import AssigneeNoLogin @@ -65,7 +65,11 @@ def test_get_priority_change_date_returns_none_when_no_match(): "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"}, + { + "field_name": "status", + "added": "RESOLVED", + "when": "2024-02-01T00:00:00Z", + }, ], }