use mempill_core::application::{IngestClaimRequest, QueryMemoryRequest};
use mempill_sqlite::open_default_in_memory;
use mempill_types::{
AgentId, Cardinality, Confidence, Criticality, DateGranularity, ExternalKind, ProvenanceLabel,
ValidTime,
};
fn make_ingest_req(
agent: AgentId,
subject: &str,
predicate: &str,
valid_time: ValidTime,
) -> IngestClaimRequest {
IngestClaimRequest {
agent_id: agent,
subject: subject.into(),
predicate: predicate.into(),
value: serde_json::json!("test-value"),
provenance: ProvenanceLabel::External(ExternalKind::UserAsserted),
cardinality: Cardinality::Functional,
valid_time: Some(valid_time),
confidence: Confidence { value_confidence: 0.9, valid_time_confidence: 0.8 },
criticality: Criticality::Medium,
derived_from: vec![],
}
}
#[tokio::test]
async fn sqlite_granularity_month_start_open_end_round_trips() {
let engine = open_default_in_memory().expect("in-memory engine must open");
let agent = AgentId("gran-rt-agent-1".into());
let (start_dt, gran) = mempill_types::parse_valid_time_date("2020-03").unwrap();
assert_eq!(gran, DateGranularity::Month);
let valid_time = ValidTime {
start: Some(start_dt),
end: None,
valid_time_confidence: 0.9,
start_granularity: Some(DateGranularity::Month),
end_granularity: None,
};
let ingest_resp = engine
.ingest_claim(make_ingest_req(agent.clone(), "person:42", "birth_month", valid_time))
.await
.expect("ingest must succeed");
let query_resp = engine
.query_memory(QueryMemoryRequest {
agent_id: agent.clone(),
subject: "person:42".into(),
predicate: "birth_month".into(),
as_of_tx_time: None,
valid_at: None,
})
.await
.expect("query must succeed");
let primary = query_resp.belief.primary.expect("primary belief must be present");
assert_eq!(primary.claim_ref, ingest_resp.claim_ref, "claim_ref must match");
let vt = primary.valid_time;
assert_eq!(
vt.start_granularity,
Some(DateGranularity::Month),
"start_granularity must round-trip as Month; got {:?}",
vt.start_granularity
);
assert_eq!(
vt.end_granularity, None,
"end_granularity must be None for open end; got {:?}",
vt.end_granularity
);
assert_eq!(vt.start, Some(start_dt), "start datetime must be preserved");
}
#[tokio::test]
async fn sqlite_granularity_day_start_year_end_round_trips() {
let engine = open_default_in_memory().expect("in-memory engine must open");
let agent = AgentId("gran-rt-agent-2".into());
let (start_dt, _) = mempill_types::parse_valid_time_date("2019-06-15").unwrap();
let (end_dt, _) = mempill_types::parse_valid_time_date("2023").unwrap();
let valid_time = ValidTime {
start: Some(start_dt),
end: Some(end_dt),
valid_time_confidence: 0.8,
start_granularity: Some(DateGranularity::Day),
end_granularity: Some(DateGranularity::Year),
};
let ingest_resp = engine
.ingest_claim(make_ingest_req(agent.clone(), "project:99", "active_window", valid_time))
.await
.expect("ingest must succeed");
let query_resp = engine
.query_memory(QueryMemoryRequest {
agent_id: agent.clone(),
subject: "project:99".into(),
predicate: "active_window".into(),
as_of_tx_time: None,
valid_at: None,
})
.await
.expect("query must succeed");
let primary = query_resp.belief.primary.expect("primary belief must be present");
assert_eq!(primary.claim_ref, ingest_resp.claim_ref);
let vt = primary.valid_time;
assert_eq!(
vt.start_granularity,
Some(DateGranularity::Day),
"start_granularity must round-trip as Day; got {:?}",
vt.start_granularity
);
assert_eq!(
vt.end_granularity,
Some(DateGranularity::Year),
"end_granularity must round-trip as Year; got {:?}",
vt.end_granularity
);
}
#[tokio::test]
async fn sqlite_granularity_none_round_trips() {
let engine = open_default_in_memory().expect("in-memory engine must open");
let agent = AgentId("gran-rt-agent-3".into());
let valid_time = ValidTime {
start: None,
end: None,
valid_time_confidence: 0.0,
start_granularity: None,
end_granularity: None,
};
engine
.ingest_claim(make_ingest_req(agent.clone(), "event:1", "timestamp", valid_time))
.await
.expect("ingest must succeed");
let query_resp = engine
.query_memory(QueryMemoryRequest {
agent_id: agent.clone(),
subject: "event:1".into(),
predicate: "timestamp".into(),
as_of_tx_time: None,
valid_at: None,
})
.await
.expect("query must succeed");
let primary = query_resp.belief.primary.expect("primary belief must be present");
let vt = primary.valid_time;
assert_eq!(vt.start_granularity, None, "start_granularity must remain None");
assert_eq!(vt.end_granularity, None, "end_granularity must remain None");
}
#[tokio::test]
async fn sqlite_granularity_null_columns_read_as_none() {
use mempill_sqlite::connection::open_in_memory;
use mempill_sqlite::SqlitePersistenceStore;
use mempill_core::ports::persistence::PersistencePort as _;
use mempill_types::{
claim::{Claim, Confidence, Criticality, Cardinality, Fact},
identity::ClaimRef,
provenance::{ExternalAnchor, ProvenanceLabel, ExternalKind},
time::{TransactionTime, ValidTime},
};
let conn = open_in_memory().expect("in-memory connection must open");
let store = SqlitePersistenceStore::new(conn);
let agent = AgentId("gran-upgrade-agent".into());
let now = chrono::Utc::now();
let claim = Claim::new(
ClaimRef::new_random(),
agent.clone(),
Fact {
subject: "legacy:subject".into(),
predicate: "legacy:predicate".into(),
value: serde_json::json!("legacy-value"),
},
Cardinality::Functional,
ProvenanceLabel::External(ExternalKind::UserAsserted),
ExternalAnchor { nearest_external_anchor: None, derivation_depth: 0 },
TransactionTime(now),
ValidTime {
start: Some(now - chrono::Duration::days(30)),
end: None,
valid_time_confidence: 0.7,
start_granularity: None,
end_granularity: None,
},
Confidence { value_confidence: 0.9, valid_time_confidence: 0.7 },
Criticality::Medium,
vec![],
None,
None,
);
let claim_ref = claim.claim_ref().clone();
let mut txn = store.begin_atomic(&agent).expect("begin_atomic must succeed");
store.append_claim(&mut txn, &claim).expect("append_claim must succeed");
store.commit(txn).expect("commit must succeed");
let loaded = store
.load_claim(&agent, &claim_ref)
.expect("load_claim must succeed")
.expect("claim must be present");
assert_eq!(
loaded.valid_time().start_granularity,
None,
"legacy row with NULL start_granularity must read back as None"
);
assert_eq!(
loaded.valid_time().end_granularity,
None,
"legacy row with NULL end_granularity must read back as None"
);
}