diff --git a/plugins/knowledgebase/api/routes.py b/plugins/knowledgebase/api/routes.py index fcd35fc..16c95d1 100644 --- a/plugins/knowledgebase/api/routes.py +++ b/plugins/knowledgebase/api/routes.py @@ -29,12 +29,19 @@ def list_articles(): query = KnowledgeBase.query.filter_by(isactive=True) - # Search + # Search: title, keywords, and the topic (its Application's name). The topic + # is matched via an appid subquery instead of a join so it does not collide + # with the sort=='topic' join below; articles with no app just miss that + # clause and still match on title/keywords. if search := request.args.get('search'): + like = f'%{search}%' + topic_appids = db.session.query(Application.appid).filter( + Application.appname.ilike(like)) query = query.filter( db.or_( - KnowledgeBase.shortdescription.ilike(f'%{search}%'), - KnowledgeBase.keywords.ilike(f'%{search}%') + KnowledgeBase.shortdescription.ilike(like), + KnowledgeBase.keywords.ilike(like), + KnowledgeBase.appid.in_(topic_appids) ) ) diff --git a/tests/test_core/test_knowledgebase.py b/tests/test_core/test_knowledgebase.py index 6cb2c20..967d09c 100644 --- a/tests/test_core/test_knowledgebase.py +++ b/tests/test_core/test_knowledgebase.py @@ -32,3 +32,36 @@ def test_create_requires_shortdescription(client, db, auth_headers): json={'linkurl': 'https://kb.example/x'}, headers=auth_headers) assert resp.status_code == 400 + + +def test_search_matches_topic_appname(client, db, auth_headers): + """Searching a topic (Application name) returns every article under it, not + just the one whose title/keywords happen to contain the word. + + Regression: KB list search omitted the topic, so a topic-only match (e.g. + 'Spotfire' as the app) surfaced a single article instead of all of them. + """ + from shopdb.core.models import Application + + app_row = Application(appname='Spotfire') + db.session.add(app_row) + db.session.flush() + + # Two articles whose title/keywords do NOT contain 'Spotfire' - only the + # topic (appid) ties them to it - plus one that does mention it directly. + for payload in [ + {'shortdescription': 'Dashboard login steps', 'keywords': 'login', + 'linkurl': 'https://kb.example/a', 'appid': app_row.appid}, + {'shortdescription': 'Export a visualization', 'keywords': 'export', + 'linkurl': 'https://kb.example/b', 'appid': app_row.appid}, + {'shortdescription': 'Spotfire install guide', 'keywords': 'install', + 'linkurl': 'https://kb.example/c', 'appid': app_row.appid}, + ]: + resp = client.post('/api/knowledgebase', json=payload, headers=auth_headers) + assert resp.status_code == 201, resp.get_json() + + listing = client.get('/api/knowledgebase?search=Spotfire', headers=auth_headers) + assert listing.status_code == 200 + urls = {a['linkurl'] for a in listing.get_json()['data']} + assert {'https://kb.example/a', 'https://kb.example/b', + 'https://kb.example/c'} <= urls