A search engine for MMP's eASLRB.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
asl-rulebook2/asl_rulebook2/webapp/tests/test_search.py

421 lines
14 KiB

""" Test search. """
import logging
from selenium.webdriver.common.keys import Keys
from asl_rulebook2.webapp.search import load_search_config, _make_fts_query_string
from asl_rulebook2.webapp.startup import StartupMsgs
from asl_rulebook2.webapp.tests.utils import init_webapp, make_webapp_main_url, \
select_tabbed_page, get_curr_target, get_classes, \
wait_for, wait_for_elem, find_child, find_children, unload_elem, unload_sr_text
# ---------------------------------------------------------------------
def test_search( webapp, webdriver ):
"""Test search."""
# initialize
webapp.control_tests.set_data_dir( "simple" )
init_webapp( webapp, webdriver )
# test a search that finds nothing
results = do_search( "oogah, boogah!" )
assert results is None
# test error handling
results = do_search( "!:simulated-error:!" )
assert "Simulated error." in results
# do a search
results = do_search( "enemy" )
assert results == [
{ "sr_type": "index",
"title": "CCPh", "subtitle": "Close Combat Phase",
"ruleids": [ "A3.8" ],
"rulerefs": [
{ "caption": "((ENEMY)) Attacks", "ruleids": [ "S11.5" ] },
{ "caption": "dropping SW before CC", "ruleids": [ "A4.43" ] },
]
},
{ "sr_type": "index",
"title": "Double Time",
"content": "Also known as \"running really fast.\"",
"see_also": [ "CX" ],
"ruleids": [ "A4.5-.51", "S6.222" ],
"rulerefs": [
{ "caption": "((ENEMY)) Guard Automatic Action", "ruleids": [ "S6.303" ] },
{ "ruleids": [ "C10.3" ] },
{ "caption": "NA in Advance Phase", "ruleids": [ "A4.7" ] },
{ "caption": "'S?' is \"<NA>\"" },
]
},
]
# do another search
results = do_search( "gap" )
assert results == [
{ "sr_type": "index",
"title": "((Gaps)), Convoy",
"ruleids": [ "E11.21" ],
},
]
# ---------------------------------------------------------------------
def test_content_fixup( webapp, webdriver ):
"""Test fixing up of content returned by the search engine."""
# initialize
webapp.control_tests.set_data_dir( "simple" )
init_webapp( webapp, webdriver )
# search for a fraction
results = do_search( "3/4" )
assert len(results) == 1
assert results[0]["content"] == "HTML content: 2((\u00be)) MP"
# search for something that ends with a hash
results = do_search( "H#" )
assert len(results) == 1
assert results[0]["title"] == "((H#))"
# search for "U.S."
results = do_search( "U.S." )
assert len(results) == 1
assert results[0]["content"] == "The ((U.S.)) has lots of this."
# ---------------------------------------------------------------------
def test_targets( webapp, webdriver ):
"""Test clicking on search results."""
# initialize
webapp.control_tests.set_data_dir( "simple" )
init_webapp( webapp, webdriver, no_content=1, add_empty_doc=1 )
def do_test( query_string, sel, expected ):
# select the dummy document
select_tabbed_page( "content", "empty" )
# do the search
do_search( query_string )
# click on a target
elem = find_child( "#search-results {}".format( sel ) )
elem.click()
wait_for( 2, lambda: get_curr_target() == ( "simple!", expected ) )
# do the tests
do_test( "CC", ".sr .ruleids .ruleid a", "A3.8" )
do_test( "time", ".sr .rulerefs .ruleid a", "A4.7" )
# ---------------------------------------------------------------------
def test_target_search( webapp, webdriver ):
"""Test searching for targets."""
# initialize
webapp.control_tests.set_data_dir( "simple" )
init_webapp( webapp, webdriver )
# search for a target
results = do_search( "cc" )
assert len(results) > 0
results = do_search( "D1.4" )
assert len(results) == 0 # nb: previous search results should be removed
wait_for( 2, lambda: get_curr_target() == ( "simple!", "D1.4" ) )
# search for a target
results = do_search( "astral plane" )
assert results is None # nb: this is the "no results" message
results = do_search( "E11.21" )
assert len(results) == 0 # nb: the "no results" message should be cleared
wait_for( 2, lambda: get_curr_target() == ( "simple!", "E11.21" ) )
# search for a target
results = do_search( "*" )
assert isinstance( results, str ) # nb: this is an error message
results = do_search( "a4.7" )
assert len(results) == 0 # nb: the error message should be cleared
wait_for( 2, lambda: get_curr_target() == ( "simple!", "A4.7" ) )
# ---------------------------------------------------------------------
def test_see_also( webapp, webdriver ):
"""Test searching for "see also" fields."""
# initialize
webapp.control_tests.set_data_dir( "see-also" )
init_webapp( webapp, webdriver )
# do a search
results = do_search( "foo" )
assert results == [ {
"sr_type": "index",
"title": "((Foo))",
"see_also": [ "Bar", "Baz Baz" ]
} ]
def click_see_also( caption ):
elems = {
e.text: e
for e in find_children( "#search-results .see-also a" )
}
assert len(elems) == 2
elems[ caption ].click()
expected = '"{}"'.format( caption ) if " " in caption else caption
wait_for( 2, lambda: find_child( "input#query-string" ).get_attribute( "value" ) == expected )
return unload_search_results()
# click on the "Bar" link and check that it gets searched for
results = click_see_also( "Bar" )
assert results == [ {
"sr_type": "index",
"title": "((Bar))"
} ]
# search for "foo" again, and click on the "Baz Baz" link
do_search( "foo" )
results = click_see_also( "Baz Baz" )
assert results is None
# ---------------------------------------------------------------------
def test_bad_sr_highlights( webapp, webdriver ):
"""Test fixing up highlight markers that have been incorrectly inserted into search result content."""
# initialize
webapp.control_tests.set_data_dir( "bad-sr-highlights" )
init_webapp( webapp, webdriver )
# bring up an index search result with a problem in its content
results = do_search( "highlight" )
assert results == [ {
"sr_type": "index",
"title": "Bad ((highlight))",
"content": "before link after",
} ]
# bring up a Q+A entry with a problem in its question
results = do_search( "foo" )
assert len(results) == 1
assert results[0]["content"][0]["question"] == "((foo)) link ((foo))"
# bring up a Q+A entry with a problem in one of its answers
results = do_search( "bar" )
assert len(results) == 1
assert results[0]["content"][0]["answers"][0][0] == "..((bar)).. link ((bar))"
# ---------------------------------------------------------------------
def test_startup_search_query( webapp, webdriver ):
"""Test starting up with a search query."""
# initialize
webapp.control_tests.set_data_dir( "full" )
# test starting up with a query
init_webapp( webapp, webdriver, q="cc" )
wait_for( 2, lambda: len( unload_search_results() ) > 0 )
# test starting up with a query
init_webapp( webapp, webdriver, query="wp" )
wait_for_elem( 2,"#rule-info" )
find_child( ".close-rule-info" ).click()
assert len( unload_search_results() ) > 0
# test starting up with a query
url = make_webapp_main_url( {} )
assert "#" not in url
url += "#bu"
webdriver.get( url )
wait_for( 2, lambda: len( unload_search_results() ) > 0 )
# ---------------------------------------------------------------------
def test_make_fts_query_string():
"""Test generating the FTS query string."""
# initialize
startup_msgs = StartupMsgs()
load_search_config( startup_msgs, logging.getLogger("_unknown_") )
def check( query, expected ):
fts_query_string, _ = _make_fts_query_string(query)
assert fts_query_string == expected
assert not startup_msgs.msgs
# test some query strings
check( "", "" )
check( "hello", "hello" )
check( " hello, world! ", "hello AND world" )
check(
"foo 1+2 A-T K# bar",
'foo AND "1+2" AND "a-t" AND "k#" AND bar'
)
check(
"a'b a''b",
"\"a'b\" AND \"a''b\""
)
check(
'foo "set dc" bar',
'foo AND "set dc" AND bar'
)
# test some quoted phrases
check( '""', '' )
check( ' " " ', '' )
check(
'"hello world"',
'"hello world"'
)
check(
' foo "hello world" bar ',
'foo AND "hello world" AND bar'
)
check(
' foo " xyz " bar ',
'foo AND xyz AND bar'
)
check(
' foo " xyz 123 " bar ',
'foo AND "xyz 123" AND bar'
)
# test some incorrectly quoted phrases
check( '"', '' )
check( ' " " " ', '' )
check( ' a "b c d e', 'a AND "b c d e"' )
check( ' a b" c d e ', 'a AND b AND c AND d AND e' )
# test pass-through
check( "AND", "AND" )
check( " OR", "OR" )
check( "OR ", "OR" )
check( "foo OR bar", "foo OR bar" )
check( "(a OR b)", "(a OR b)" )
# test search replacements
check( "1/2 3/4 3/8 5/8", '"&frac12;" AND "&frac34;" AND "&frac38;" AND "&frac58;"' )
check( "(r)", '"&reg;"' )
# test search aliases
check( "entrenchment", "( ditch OR entrenchment OR foxhole OR trench )" )
check( "entrenchments", "( ditch OR entrenchments OR foxhole OR trench )" )
check( "foxhole", "foxhole" )
# test search synonyms
check( "armor", "( armor OR armour )" )
check( "american big armor", '( america OR american OR "u.s." ) AND big AND ( armor OR armour )' )
# ---------------------------------------------------------------------
def do_search( query_string ):
"""Do a search."""
def get_seq_no():
return find_child( "#search-results" ).get_attribute( "data-seqno" )
# submit the search
select_tabbed_page( "nav", "search" )
elem = find_child( "input#query-string" )
elem.clear()
elem.send_keys( query_string )
seq_no = get_seq_no()
elem.send_keys( Keys.RETURN )
# unload the results
wait_for( 2, lambda: get_seq_no() > seq_no )
return unload_search_results()
def unload_search_results():
"""Unload the search results."""
# check if there were any search results
elem = find_child( "#search-results .error" )
if elem:
return elem.text # nb: string = error message
elem = find_child( "#search-results .no-results" )
if elem:
if elem.text == "Nothing was found.":
return None # nb: None = no results
return elem.text
def unload_ruleids( result, key, parent ):
"""Unload a list of ruleid's."""
if not parent:
return
ruleids = []
for elem in find_children( ".ruleid", parent ):
ruleid = unload_sr_text( elem )
if ruleid.startswith( "[" ) and ruleid.endswith( "]" ):
ruleid = ruleid[1:-1]
if ruleid.endswith( "," ):
ruleid = ruleid[:-1]
ruleids.append( ruleid )
if ruleids:
result[key] = ruleids
def unload_rulerefs( result, key, parent ):
"""Unload a list of ruleref's."""
if not parent:
return
rulerefs = []
for elem in find_children( "li", parent ):
ruleref = {}
unload_elem( ruleref, "caption", find_child(".caption",elem), adjust_hilites=True )
unload_ruleids( ruleref, "ruleids", elem )
rulerefs.append( ruleref )
if rulerefs:
result[key] = rulerefs
def unload_index_sr( sr ): #pylint: disable=possibly-unused-variable
"""Unload an "index" search result."""
result = {}
unload_elem( result, "title", find_child("span.title",sr), adjust_hilites=True )
unload_elem( result, "subtitle", find_child(".subtitle",sr), adjust_hilites=True )
unload_elem( result, "content", find_child(".content",sr), adjust_hilites=True )
if unload_elem( result, "see_also", find_child(".see-also",sr) ):
assert result["see_also"].startswith( "See also:" )
result["see_also"] = [ s.strip() for s in result["see_also"][9:].split( "," ) ]
unload_ruleids( result, "ruleids", find_child(".ruleids",sr) )
unload_rulerefs( result, "rulerefs", find_child(".rulerefs",sr) )
return result
def unload_qa_sr( sr ): #pylint: disable=possibly-unused-variable
"""Unload a "qa" search result."""
from asl_rulebook2.webapp.tests.test_qa import unload_qa
return unload_qa( sr )
def unload_anno_sr( sr ): #pylint: disable=possibly-unused-variable
"""Unload an "anno" search result."""
from asl_rulebook2.webapp.tests.test_annotations import unload_anno
return unload_anno( sr )
def unload_asop_entry_sr( sr ): #pylint: disable=possibly-unused-variable
"""Unload an "ASOP entry" search result."""
result = {}
unload_elem( result, "caption", find_child(".caption",sr), adjust_hilites=True )
unload_elem( result, "content", find_child(".content",sr), adjust_hilites=True )
return result
# unload the search results
results = []
for sr in find_children( "#search-results .sr"):
if not sr.is_displayed():
continue
classes = get_classes( sr )
classes.remove( "sr" )
classes = [ c for c in classes if c in ["index-sr","qa","anno","asop-entry-sr"] ]
assert len(classes) == 1
sr_type = classes[0]
if sr_type.endswith( "-sr" ):
sr_type = sr_type[:-3]
func = locals()[ "unload_{}_sr".format( sr_type.replace("-","_") ) ]
sr = func( sr )
sr["sr_type"] = sr_type
results.append( sr )
return results