diff --git a/shopdb/core/api/search.py b/shopdb/core/api/search.py index a308bc5b..d023304c 100644 --- a/shopdb/core/api/search.py +++ b/shopdb/core/api/search.py @@ -658,7 +658,10 @@ def _search_subnets(query): 'id': subnet.subnetid, 'title': f'{subnet.name} ({subnet.cidr})', 'subtitle': subnet.description or subnet.subnettype, - 'url': f'/network', + # the matched subnet, not the hub: searching an IP + # already identified WHICH subnet, so landing on the + # hub makes the reader find it again by hand + 'url': f'/networks/{subnet.subnetid}', 'relevance': 70 }) except ValueError: @@ -779,7 +782,9 @@ def _search_notifications(query, search_term): 'id': notif.notificationid, 'title': notif.title, 'subtitle': notif.notificationtype.typename if notif.notificationtype else None, - 'url': f'/notifications', + # the notification itself, not the list it sits in: the + # `notification-detail` route renders one by id + 'url': f'/notifications/{notif.notificationid}', 'relevance': min(int(base_relevance), 100), 'ticketnumber': notif.ticketnumber, 'iscurrent': notif.is_current diff --git a/tests/test_core/test_search_coverage.py b/tests/test_core/test_search_coverage.py index 3250e957..57c53115 100644 --- a/tests/test_core/test_search_coverage.py +++ b/tests/test_core/test_search_coverage.py @@ -199,3 +199,70 @@ def test_a_machine_is_found_by_its_maintenance_reference(client, taggedmachine): type - a field the UI collects and search could not find.""" rows = [r for r in search(client, 'ZZMAINT888') if r['title'] == 'ZZMACH01'] assert rows, 'a maintenance reference matched nothing' + + +# --- a search result must land on the record, not its list ------------------- +# +# A result carries an id, so sending the reader to the plugin's list page makes +# them find by hand the row search had already identified. Both cases below +# shipped that way: the id was in the payload and the URL ignored it. + +@pytest.fixture +def noticerow(db): + """One notification with a distinctive body and its own type.""" + from plugins.notifications.models import Notification, NotificationType + notice = NotificationType(typename='Notice', typecolor='#17a2b8', + isactive=True) + db.session.add(notice) + db.session.flush() + row = Notification( + notificationtypeid=notice.notificationtypeid, + notification='ZZNOTICE7788 press brake down for repair', + ticketnumber='INC7788', + isactive=True, + ) + db.session.add(row) + db.session.commit() + return row.notificationid + + +def test_a_notice_result_links_to_the_notice(client, noticerow): + """Clicking a NOTICE opens that notification, not /notifications. + + `/notifications/` is the `notification-detail` route; the list page + cannot show which of its rows the search matched. + """ + rows = [r for r in search(client, 'ZZNOTICE7788') + if r['type'] == 'notification'] + assert len(rows) == 1 + assert rows[0]['id'] == noticerow + assert rows[0]['url'] == f'/notifications/{noticerow}' + + +def test_a_notice_found_by_ticket_number_links_the_same_way(client, noticerow): + """The ticket number is the strongest match and must land as precisely.""" + rows = [r for r in search(client, 'INC7788') + if r['type'] == 'notification'] + assert len(rows) == 1 + assert rows[0]['url'] == f'/notifications/{noticerow}' + + +@pytest.fixture +def subnetrow(db): + """A subnet an IP search can fall inside.""" + from plugins.network.models import Subnet + row = Subnet(cidr='10.77.88.0/24', name='ZZ Test subnet', isactive=True) + db.session.add(row) + db.session.commit() + return row.subnetid + + +def test_an_ip_search_links_to_the_matched_subnet(client, subnetrow): + """Searching an IP already picked WHICH subnet; land on it. + + The detail route is `/networks/` (plural) - `/network` is the hub. + """ + rows = [r for r in search(client, '10.77.88.42') if r['type'] == 'subnet'] + assert len(rows) == 1 + assert rows[0]['id'] == subnetrow + assert rows[0]['url'] == f'/networks/{subnetrow}'