add criteria-first discovery search provider

This commit is contained in:
Marco0300
2026-09-03 20:04:08 +02:00
parent e3257f1ce9
commit 725ef0db9f
8 changed files with 202 additions and 16 deletions
+21 -1
View File
@@ -14,6 +14,8 @@ from app.main import create_server, hash_password
class ScopedDiscoveryApiTests(unittest.TestCase):
def setUp(self):
self.tmp = TemporaryDirectory()
for key in ('SEARCH_PROVIDER_URL', 'SEARCH_PROVIDER_ALLOWED_HOSTS', 'SEARCH_PROVIDER_API_KEY'):
os.environ.pop(key, None)
os.environ['BOOTSTRAP_ADMIN_EMAIL'] = 'discover-owner@example.test'
os.environ['BOOTSTRAP_ADMIN_PASSWORD'] = 'password'
self.server = create_server('127.0.0.1', 0, self.tmp.name + '/db.sqlite')
@@ -60,10 +62,28 @@ class ScopedDiscoveryApiTests(unittest.TestCase):
self.assertFalse(detail.get('outreach_enabled', False))
def test_requires_bounded_seed_allowlist_and_rejects_ssrf(self):
self.assertEqual(self.request('POST', '/api/v1/discovery', {'criteria': {'keywords': ['x']}})[0], 400)
status, body = self.request('POST', '/api/v1/discovery', {'criteria': {'keywords': ['x']}})
self.assertEqual(status, 503); self.assertEqual(body['error'], 'not_configured')
status, body = self.request('POST', '/api/v1/discovery', {'criteria': {}, 'seed_urls': ['http://127.0.0.1/'], 'idempotency_key': 'bad'})
self.assertEqual(status, 400); self.assertEqual(body['error'], 'unsafe_seed_url')
def test_criteria_first_search_results_flow_through_existing_job_persistence(self):
os.environ['SEARCH_PROVIDER_URL'] = 'https://search.example.test/query'
os.environ['SEARCH_PROVIDER_ALLOWED_HOSTS'] = 'search.example.test'
pages = {'https://acme.test/': {'status': 200, 'final_url': 'https://acme.test/', 'content_type': 'text/html', 'body': b'<title>Acme Solar</title><h1>Acme Solar</h1><p>solar</p>'}}
def fetch(url, **_):
value = pages[url]; return dict(value, redirect_chain=[], elapsed_ms=1, tls=True, certificate_status='valid')
with patch('app.discovery.search_provider', return_value=['https://acme.test/']) as provider, patch('app.discovery._fetch', side_effect=fetch), patch('app.discovery.validate_url', side_effect=lambda url, **_: url), patch('app.main.validate_url', side_effect=lambda url, **_: url):
status, created = self.request('POST', '/api/v1/discovery', {'criteria': {'keywords': ['solar']}, 'max_candidates': 1, 'idempotency_key': 'criteria-1'})
self.assertEqual(status, 202)
for _ in range(50):
_, job = self.request('GET', '/api/v1/jobs/' + str(created['id']))
if job['status'] in ('succeeded', 'failed'): break
time.sleep(.02)
self.assertEqual(job['status'], 'succeeded'); provider.assert_called_once_with({'keywords': ['solar']}, 1)
run = self.request('GET', '/api/v1/discovery-runs')[1]['items'][0]
self.assertEqual(run['seed_urls'], []); self.assertEqual(run['result']['candidates'][0]['provenance']['mechanism'], 'criteria_search_provider')
def test_results_are_tenant_isolated(self):
ph, salt = hash_password('other-password')
db = sqlite3.connect(self.server.db_path); db.execute("INSERT INTO organizations VALUES ('other-tenant','Other',CURRENT_TIMESTAMP)"); db.execute("INSERT INTO users (organization_id,email,password_hash,password_salt,role) VALUES (?,?,?,?,?)", ('other-tenant','other@example.test',ph,salt,'owner')); db.commit(); db.close()