use super::super::*;
use crate::storage::init_test_db;
use crate::storage::provenance::{self, ProvenanceRef};
#[tokio::test]
async fn get_in_range_includes_unscheduled_by_created_at() {
let pool = init_test_db().await.expect("init db");
let id = insert(&pool, "tweet", "No schedule time", None)
.await
.expect("insert");
let item = get_by_id(&pool, id).await.expect("get").expect("exists");
let items = get_in_range(&pool, "2020-01-01T00:00:00Z", "2099-12-31T23:59:59Z")
.await
.expect("range");
assert!(
items.iter().any(|i| i.id == id),
"unscheduled item should be found by created_at"
);
assert!(item.scheduled_for.is_none());
}
#[tokio::test]
async fn reschedule_draft_for_account_isolation() {
let pool = init_test_db().await.expect("init db");
let acct_a = "acct-resched-a";
let acct_b = "acct-resched-b";
crate::storage::accounts::create_account(&pool, acct_a, "A")
.await
.expect("create a");
crate::storage::accounts::create_account(&pool, acct_b, "B")
.await
.expect("create b");
let id_a = insert_for(
&pool,
acct_a,
"tweet",
"Reschedule me",
Some("2026-07-01T10:00:00Z"),
)
.await
.expect("insert a");
let changed = reschedule_draft_for(&pool, acct_b, id_a, "2026-08-01T10:00:00Z")
.await
.expect("reschedule wrong acct");
assert!(!changed, "cross-account reschedule should fail");
let changed = reschedule_draft_for(&pool, acct_a, id_a, "2026-08-01T10:00:00Z")
.await
.expect("reschedule correct acct");
assert!(changed);
let item = get_by_id_for(&pool, acct_a, id_a)
.await
.expect("get")
.expect("exists");
assert_eq!(item.scheduled_for.as_deref(), Some("2026-08-01T10:00:00Z"));
}
#[tokio::test]
async fn unschedule_draft_for_account_isolation() {
let pool = init_test_db().await.expect("init db");
let acct_a = "acct-unsched-a";
let acct_b = "acct-unsched-b";
crate::storage::accounts::create_account(&pool, acct_a, "A")
.await
.expect("create a");
crate::storage::accounts::create_account(&pool, acct_b, "B")
.await
.expect("create b");
let id_a = insert_for(
&pool,
acct_a,
"tweet",
"Will be unscheduled",
Some("2026-07-01T10:00:00Z"),
)
.await
.expect("insert a");
let changed = unschedule_draft_for(&pool, acct_b, id_a)
.await
.expect("unsched wrong");
assert!(!changed);
let changed = unschedule_draft_for(&pool, acct_a, id_a)
.await
.expect("unsched correct");
assert!(changed);
let item = get_by_id_for(&pool, acct_a, id_a)
.await
.expect("get")
.expect("exists");
assert_eq!(item.status, "draft");
assert!(item.scheduled_for.is_none());
}
#[tokio::test]
async fn archive_draft_for_account_isolation() {
let pool = init_test_db().await.expect("init db");
let acct_a = "acct-archive-a";
let acct_b = "acct-archive-b";
crate::storage::accounts::create_account(&pool, acct_a, "A")
.await
.expect("create a");
crate::storage::accounts::create_account(&pool, acct_b, "B")
.await
.expect("create b");
let id_a = insert_draft_for(&pool, acct_a, "tweet", "Archive test", "manual")
.await
.expect("insert a");
let archived = archive_draft_for(&pool, acct_b, id_a)
.await
.expect("archive wrong");
assert!(!archived);
let archived = archive_draft_for(&pool, acct_a, id_a)
.await
.expect("archive correct");
assert!(archived);
let archived_list = list_archived_drafts_for(&pool, acct_a).await.expect("list");
assert_eq!(archived_list.len(), 1);
assert_eq!(archived_list[0].content, "Archive test");
let restored = restore_draft_for(&pool, acct_b, id_a)
.await
.expect("restore wrong");
assert!(!restored);
let restored = restore_draft_for(&pool, acct_a, id_a)
.await
.expect("restore correct");
assert!(restored);
}
#[tokio::test]
async fn duplicate_draft_for_account_isolation() {
let pool = init_test_db().await.expect("init db");
let acct = "acct-dup";
crate::storage::accounts::create_account(&pool, acct, "Dup")
.await
.expect("create");
let id = insert_draft_for(&pool, acct, "tweet", "Original", "manual")
.await
.expect("insert");
let dup = duplicate_draft_for(&pool, "wrong-acct", id)
.await
.expect("dup wrong");
assert!(dup.is_none());
let dup = duplicate_draft_for(&pool, acct, id)
.await
.expect("dup correct");
assert!(dup.is_some());
let dup_item = get_by_id_for(&pool, acct, dup.unwrap())
.await
.expect("get")
.expect("exists");
assert_eq!(dup_item.content, "Original");
assert_eq!(dup_item.title.as_deref(), Some("(copy)"));
}
#[tokio::test]
async fn update_draft_meta_for_account_isolation() {
let pool = init_test_db().await.expect("init db");
let acct = "acct-meta";
crate::storage::accounts::create_account(&pool, acct, "Meta")
.await
.expect("create");
let id = insert_draft_for(&pool, acct, "tweet", "Meta test", "manual")
.await
.expect("insert");
let changed = update_draft_meta_for(&pool, "wrong", id, Some("Title"), Some("Notes"))
.await
.expect("meta wrong");
assert!(!changed);
let changed = update_draft_meta_for(&pool, acct, id, Some("My Title"), Some("My Notes"))
.await
.expect("meta correct");
assert!(changed);
let item = get_by_id_for(&pool, acct, id)
.await
.expect("get")
.expect("exists");
assert_eq!(item.title.as_deref(), Some("My Title"));
assert_eq!(item.notes.as_deref(), Some("My Notes"));
}
#[tokio::test]
async fn autosave_draft_for_account_isolation() {
let pool = init_test_db().await.expect("init db");
let acct = "acct-autosave";
crate::storage::accounts::create_account(&pool, acct, "AS")
.await
.expect("create");
let id = insert_draft_for(&pool, acct, "tweet", "Before save", "manual")
.await
.expect("insert");
let item = get_by_id_for(&pool, acct, id)
.await
.expect("get")
.expect("exists");
let result = autosave_draft_for(&pool, acct, id, "After save", "tweet", &item.updated_at)
.await
.expect("autosave");
assert!(result.is_some(), "autosave should succeed");
let updated = get_by_id_for(&pool, acct, id)
.await
.expect("get")
.expect("exists");
assert_eq!(updated.content, "After save");
let stale_result = autosave_draft_for(
&pool,
acct,
id,
"Stale attempt",
"tweet",
"1970-01-01 00:00:00", )
.await
.expect("autosave stale");
assert!(stale_result.is_none(), "stale autosave should return None");
}
fn sample_provenance_refs() -> Vec<ProvenanceRef> {
vec![
ProvenanceRef {
node_id: None,
chunk_id: None,
seed_id: None,
source_path: Some("notes/rust-async.md".to_string()),
heading_path: Some("# Async > ## Tokio".to_string()),
snippet: Some("Tokio runtime patterns...".to_string()),
edge_type: None,
edge_label: None,
angle_kind: None,
signal_kind: None,
signal_text: None,
source_role: None,
},
ProvenanceRef {
node_id: None,
chunk_id: None,
seed_id: None,
source_path: Some("notes/testing.md".to_string()),
heading_path: None,
snippet: Some("Property-based testing...".to_string()),
edge_type: None,
edge_label: None,
angle_kind: None,
signal_kind: None,
signal_text: None,
source_role: None,
},
]
}
#[tokio::test]
async fn draft_created_with_provenance_has_links() {
let pool = init_test_db().await.expect("init db");
let acct = "acct-prov-create";
crate::storage::accounts::create_account(&pool, acct, "P")
.await
.expect("create");
let refs = sample_provenance_refs();
let id =
insert_draft_with_provenance_for(&pool, acct, "tweet", "Provenance draft", "assist", &refs)
.await
.expect("insert with provenance");
let links = provenance::get_links_for(&pool, acct, "scheduled_content", id)
.await
.expect("get links");
assert_eq!(links.len(), 2);
assert!(links[0].node_id.is_none());
assert!(links[0].chunk_id.is_none());
assert_eq!(links[0].source_path.as_deref(), Some("notes/rust-async.md"));
assert_eq!(links[1].source_path.as_deref(), Some("notes/testing.md"));
assert_eq!(links[1].source_path.as_deref(), Some("notes/testing.md"));
}
#[tokio::test]
async fn draft_duplication_preserves_provenance_when_copied() {
let pool = init_test_db().await.expect("init db");
let acct = "acct-prov-dup";
crate::storage::accounts::create_account(&pool, acct, "PD")
.await
.expect("create");
let refs = sample_provenance_refs();
let original_id = insert_draft_with_provenance_for(
&pool,
acct,
"tweet",
"Original with prov",
"assist",
&refs,
)
.await
.expect("insert");
let dup_id = duplicate_draft_for(&pool, acct, original_id)
.await
.expect("duplicate")
.expect("dup exists");
provenance::copy_links_for(
&pool,
acct,
"scheduled_content",
original_id,
"scheduled_content",
dup_id,
)
.await
.expect("copy links");
let dup_links = provenance::get_links_for(&pool, acct, "scheduled_content", dup_id)
.await
.expect("get dup links");
assert_eq!(dup_links.len(), 2);
assert_eq!(
dup_links[0].source_path.as_deref(),
Some("notes/rust-async.md")
);
assert_eq!(
dup_links[1].source_path.as_deref(),
Some("notes/testing.md")
);
assert_eq!(dup_links[0].entity_id, dup_id);
}
#[tokio::test]
async fn provenance_survives_schedule_unschedule() {
let pool = init_test_db().await.expect("init db");
let acct = "acct-prov-sched";
crate::storage::accounts::create_account(&pool, acct, "PS")
.await
.expect("create");
let refs = sample_provenance_refs();
let id = insert_draft_with_provenance_for(&pool, acct, "tweet", "Schedule me", "assist", &refs)
.await
.expect("insert");
schedule_draft_for(&pool, acct, id, "2026-08-01T10:00:00Z")
.await
.expect("schedule");
let links = provenance::get_links_for(&pool, acct, "scheduled_content", id)
.await
.expect("get links after schedule");
assert_eq!(links.len(), 2, "provenance should survive scheduling");
let unscheduled = unschedule_draft_for(&pool, acct, id)
.await
.expect("unschedule");
assert!(unscheduled);
let links = provenance::get_links_for(&pool, acct, "scheduled_content", id)
.await
.expect("get links after unschedule");
assert_eq!(links.len(), 2, "provenance should survive unscheduling");
}
#[tokio::test]
async fn provenance_survives_autosave() {
let pool = init_test_db().await.expect("init db");
let acct = "acct-prov-autosave";
crate::storage::accounts::create_account(&pool, acct, "PA")
.await
.expect("create");
let refs = sample_provenance_refs();
let id =
insert_draft_with_provenance_for(&pool, acct, "tweet", "Before autosave", "assist", &refs)
.await
.expect("insert");
let item = get_by_id_for(&pool, acct, id)
.await
.expect("get")
.expect("exists");
let result = autosave_draft_for(&pool, acct, id, "After autosave", "tweet", &item.updated_at)
.await
.expect("autosave");
assert!(result.is_some());
let links = provenance::get_links_for(&pool, acct, "scheduled_content", id)
.await
.expect("get links after autosave");
assert_eq!(links.len(), 2, "provenance should survive autosave");
assert_eq!(links[0].source_path.as_deref(), Some("notes/rust-async.md"));
}
#[tokio::test]
async fn provenance_approval_to_scheduled_bridge() {
let pool = init_test_db().await.expect("init db");
let acct = "acct-prov-bridge";
crate::storage::accounts::create_account(&pool, acct, "PB")
.await
.expect("create");
let refs = sample_provenance_refs();
let aq_id = 999_i64;
provenance::insert_links_for(&pool, acct, "approval_queue", aq_id, &refs)
.await
.expect("insert approval provenance");
let sc_id = insert_for(
&pool,
acct,
"tweet",
"Approved content",
Some("2026-08-01T10:00:00Z"),
)
.await
.expect("insert sc");
provenance::copy_links_for(
&pool,
acct,
"approval_queue",
aq_id,
"scheduled_content",
sc_id,
)
.await
.expect("copy links");
let sc_links = provenance::get_links_for(&pool, acct, "scheduled_content", sc_id)
.await
.expect("get sc links");
assert_eq!(sc_links.len(), 2);
assert_eq!(sc_links[0].entity_type, "scheduled_content");
assert_eq!(sc_links[0].entity_id, sc_id);
assert_eq!(
sc_links[0].source_path.as_deref(),
Some("notes/rust-async.md")
);
let aq_links = provenance::get_links_for(&pool, acct, "approval_queue", aq_id)
.await
.expect("get aq links");
assert_eq!(aq_links.len(), 2);
}