1
Fork 0
mirror of https://github.com/thegeneralist01/archivr synced 2026-10-09 12:55:00 +02:00

Merge branch 'summary-search' into integration-all-three

This commit is contained in:
archivr-qa 2026-08-23 20:44:33 +02:00
commit 55f96c66e9
No known key found for this signature in database

View file

@ -773,7 +773,14 @@ pub fn search_entries(
sql.push_str(&format!( sql.push_str(&format!(
" AND (LOWER(e.title) LIKE ?{n} OR LOWER(si.canonical_url) LIKE ?{n} \ " AND (LOWER(e.title) LIKE ?{n} OR LOWER(si.canonical_url) LIKE ?{n} \
OR LOWER(e.entry_uid) LIKE ?{n} OR LOWER(e.source_kind) LIKE ?{n} \ OR LOWER(e.entry_uid) LIKE ?{n} OR LOWER(e.source_kind) LIKE ?{n} \
OR LOWER(e.entity_kind) LIKE ?{n} OR LOWER(e.visibility) LIKE ?{n})" OR LOWER(e.entity_kind) LIKE ?{n} OR LOWER(e.visibility) LIKE ?{n} \
OR LOWER(COALESCE((\
SELECT s.summary_text FROM entry_summaries s \
WHERE s.entry_id = e.id AND s.status = 'completed' \
AND s.summary_text IS NOT NULL \
ORDER BY s.completed_at DESC, s.updated_at DESC, s.id DESC \
LIMIT 1\
), '')) LIKE ?{n})"
)); ));
params.push(term); params.push(term);
} }
@ -1497,6 +1504,192 @@ mod tests {
assert_eq!(results.len(), 1); assert_eq!(results.len(), 1);
} }
fn entry_id_by_title(conn: &rusqlite::Connection, title: &str) -> i64 {
conn.query_row(
"SELECT id FROM archived_entries WHERE title = ?1",
[title],
|row| row.get(0),
)
.unwrap()
}
fn complete_summary(
conn: &rusqlite::Connection,
entry_id: i64,
cache_key: &str,
summary_text: &str,
) -> String {
let uid = database::upsert_pending_entry_summary(
conn,
entry_id,
"test_provider",
None,
"v1",
cache_key,
)
.unwrap();
database::update_entry_summary_status(conn, &uid, "completed", Some(summary_text), None)
.unwrap();
uid
}
fn set_summary_timestamps(
conn: &rusqlite::Connection,
summary_uid: &str,
timestamp: &str,
) {
conn.execute(
"UPDATE entry_summaries SET completed_at = ?1, updated_at = ?1 WHERE summary_uid = ?2",
rusqlite::params![timestamp, summary_uid],
)
.unwrap();
}
#[test]
fn search_summary_json_tags_match_and_unrelated_text_is_absent() {
let conn = make_test_db_with_entries();
let entry_id = entry_id_by_title(&conn, "Resume Templates");
complete_summary(
&conn,
entry_id,
"tags",
r#"{"tags":["skincare","dermatology"]}"#,
);
let matches = search_entries(
&conn,
&SearchEntriesQuery {
q: Some("skincare".to_string()),
..Default::default()
},
)
.unwrap();
assert_eq!(matches.len(), 1);
assert_eq!(matches[0].title.as_deref(), Some("Resume Templates"));
let unrelated = search_entries(
&conn,
&SearchEntriesQuery {
q: Some("neurology".to_string()),
..Default::default()
},
)
.unwrap();
assert!(unrelated.is_empty());
}
#[test]
fn search_summary_uses_only_the_newest_completed_row() {
let conn = make_test_db_with_entries();
let entry_id = entry_id_by_title(&conn, "Resume Templates");
let older = complete_summary(&conn, entry_id, "older", "legacy-skincare-term");
set_summary_timestamps(&conn, &older, "2026-01-01T00:00:00Z");
let newer = complete_summary(&conn, entry_id, "newer", "current-dermatology-term");
set_summary_timestamps(&conn, &newer, "2026-02-01T00:00:00Z");
let old_matches = search_entries(
&conn,
&SearchEntriesQuery {
q: Some("legacy-skincare-term".to_string()),
..Default::default()
},
)
.unwrap();
assert!(old_matches.is_empty());
let current_matches = search_entries(
&conn,
&SearchEntriesQuery {
q: Some("current-dermatology-term".to_string()),
..Default::default()
},
)
.unwrap();
assert_eq!(current_matches.len(), 1);
}
#[test]
fn search_summary_keeps_latest_completed_when_newer_rows_are_pending_or_failed() {
let conn = make_test_db_with_entries();
let entry_id = entry_id_by_title(&conn, "Resume Templates");
let completed = complete_summary(&conn, entry_id, "completed", "retained-skincare-term");
set_summary_timestamps(&conn, &completed, "2026-01-01T00:00:00Z");
let pending = database::upsert_pending_entry_summary(
&conn,
entry_id,
"test_provider",
None,
"v1",
"pending",
)
.unwrap();
set_summary_timestamps(&conn, &pending, "2026-03-01T00:00:00Z");
let failed = database::upsert_pending_entry_summary(
&conn,
entry_id,
"test_provider",
None,
"v1",
"failed",
)
.unwrap();
database::update_entry_summary_status(&conn, &failed, "failed", None, Some("boom")).unwrap();
set_summary_timestamps(&conn, &failed, "2026-04-01T00:00:00Z");
let matches = search_entries(
&conn,
&SearchEntriesQuery {
q: Some("retained-skincare-term".to_string()),
..Default::default()
},
)
.unwrap();
assert_eq!(matches.len(), 1);
}
#[test]
fn search_summary_preserves_prefix_collection_and_visibility_scope() {
let conn = make_test_db_with_entries();
let entry_id = entry_id_by_title(&conn, "Polymarket tweet");
complete_summary(&conn, entry_id, "scoped", "scoped-skincare-term");
let tag = create_tag(&conn, "/summary-scope").unwrap();
database::assign_entry_to_tag(
&conn,
entry_id,
database::get_tag_by_uid(&conn, &tag.tag_uid).unwrap().unwrap().id,
)
.unwrap();
let collection = database::create_collection(&conn, "Summary scope", "summary-scope", 2, false)
.unwrap();
database::add_entry_to_collection(&conn, collection.id, entry_id, 2).unwrap();
let query = SearchEntriesQuery {
q: Some("scoped-skincare-term".to_string()),
source_kind: Some("x".to_string()),
entity_kind: Some("tweet".to_string()),
url: Some("x.com".to_string()),
title: Some("polymarket".to_string()),
after: Some("2020-01-01T00:00:00Z".to_string()),
before: Some("9999-01-01T00:00:00Z".to_string()),
tag: Some("/summary-scope".to_string()),
caller_bits: 1,
collection_id: Some(collection.id),
};
assert!(search_entries(&conn, &query).unwrap().is_empty());
let matches = search_entries(
&conn,
&SearchEntriesQuery {
caller_bits: 2,
..query
},
)
.unwrap();
assert_eq!(matches.len(), 1);
assert_eq!(matches[0].title.as_deref(), Some("Polymarket tweet"));
}
// ---- tag API tests ---- // ---- tag API tests ----
fn make_tag_test_db() -> (rusqlite::Connection, i64, i64) { fn make_tag_test_db() -> (rusqlite::Connection, i64, i64) {