#![allow(clippy::unwrap_used, clippy::doc_markdown)] //! M0–M10 review pass 2, zone C regression: FOR SESSION missing-session handling //! must NOT diverge between RETRIEVE and SEARCH. //! //! A RETRIEVE/SEARCH with `FOR SESSION ` pointing at an expired/evicted //! session is a WELL-FORMED query. Per CODING_GUIDELINES §6 ("graceful //! degradation, never failure") the engine must execute it WITHOUT the session //! boost rather than hard-erroring. Before the fix RETRIEVE returned //! `Err(SessionNotFound)` while the structurally-identical SEARCH degraded — a //! confusing surface-specific outage the moment a session was swept. This test //! proves both surfaces now degrade identically. use std::{collections::HashMap, time::Duration}; use tidaldb::{ SessionId, TidalDb, query::{retrieve::Retrieve, search::Search}, schema::{DecaySpec, EntityId, EntityKind, SchemaBuilder, Timestamp, Window}, }; fn test_schema() -> tidaldb::schema::Schema { let mut builder = SchemaBuilder::new(); let _ = builder .signal( "view", EntityKind::Item, DecaySpec::Exponential { half_life: Duration::from_secs(7 * 24 * 3600), }, ) .windows(&[Window::AllTime]) .add(); builder.build().unwrap() } fn test_db() -> TidalDb { TidalDb::builder() .ephemeral() .with_schema(test_schema()) .open() .unwrap() } #[test] fn retrieve_and_search_degrade_identically_on_missing_session() { let db = test_db(); // Seed one item with a `view` signal so both queries have something to rank. let now = Timestamp::now(); db.signal("view", EntityId::new(1), 1.0, now).unwrap(); let mut meta = HashMap::new(); meta.insert("title".to_string(), "jazz piano".to_string()); db.write_item_with_metadata(EntityId::new(1), &meta) .unwrap(); // A session id that was never started (the "swept session" case). let missing = SessionId::from_raw(9_999_999); // RETRIEVE FOR SESSION must NOT error — it degrades to no-boost. let retrieve = Retrieve::builder() .profile("new") .for_session(missing) .limit(10) .build() .unwrap(); let r = db.retrieve(&retrieve); assert!( r.is_ok(), "RETRIEVE with a missing session must degrade gracefully, got {:?}", r.err() ); // SEARCH FOR SESSION must likewise degrade (this already worked; // asserted here to lock the two surfaces together). let search = Search::builder() .query("jazz") .using_profile("search") .for_session(missing) .limit(10) .build() .unwrap(); let s = db.search(&search); assert!( s.is_ok(), "SEARCH with a missing session must degrade gracefully, got {:?}", s.err() ); db.close().unwrap(); }