use std::time::Duration;
use bytes::Bytes;
use futures::{SinkExt, StreamExt};
use pbap_core::client::PbapClient;
use pbap_core::phonebook::PhonebookPath;
use store::PbapMeta;
use super::{
body_rsp, error_rsp, fake_store, hex16, list_body, metadata_rsp, run_sync, sync_contacts, vcard,
};
use super::{Refresh, SyncReport, A, B, C, CONNECT_RSP};
#[tokio::test]
async fn first_run_fetches_and_persists_meta() -> anyhow::Result<()> {
let (db, _dir) = fake_store().await?;
let report = run_sync(
&db,
&[
metadata_rsp(A, B, C)?,
body_rsp(&list_body(&["1.vcf"]))?,
body_rsp(&vcard(Some("uid-1"), "Alice", "+15550001"))?,
],
)
.await?;
assert_eq!(
report?,
SyncReport::Refreshed(Refresh {
listed: 1,
pull_failed: 0,
no_uid: 0,
written: 1,
wiped: false,
})
);
let contact = db.get_contact("uid-1").await?.ok_or_else(|| anyhow::anyhow!("missing"))?;
assert_eq!(contact.display_name.as_deref(), Some("Alice"));
assert_eq!(
db.pbap_meta().await?,
PbapMeta {
database_id: Some(hex16(A)),
primary_version: Some(hex16(B)),
secondary_version: Some(hex16(C)),
}
);
Ok(())
}
#[tokio::test]
async fn skips_entries_without_uid() -> anyhow::Result<()> {
let (db, _dir) = fake_store().await?;
let report = run_sync(
&db,
&[
metadata_rsp(A, B, C)?,
body_rsp(&list_body(&["1.vcf", "2.vcf"]))?,
body_rsp(&vcard(None, "NoUid", "+15550001"))?,
body_rsp(&vcard(Some("uid-2"), "HasUid", "+15550002"))?,
],
)
.await?;
assert_eq!(
report?,
SyncReport::Refreshed(Refresh {
listed: 2,
pull_failed: 0,
no_uid: 1,
written: 1,
wiped: false,
})
);
assert!(db.get_contact("uid-2").await?.is_some());
Ok(())
}
#[tokio::test]
async fn counts_failed_pulls_without_aborting() -> anyhow::Result<()> {
let (db, _dir) = fake_store().await?;
let report = run_sync(
&db,
&[
metadata_rsp(A, B, C)?,
body_rsp(&list_body(&["1.vcf", "2.vcf"]))?,
error_rsp()?,
body_rsp(&vcard(Some("uid-2"), "Reached", "+15550002"))?,
],
)
.await?;
assert_eq!(
report?,
SyncReport::Refreshed(Refresh {
listed: 2,
pull_failed: 1,
no_uid: 0,
written: 1,
wiped: false,
})
);
assert!(db.get_contact("uid-2").await?.is_some());
Ok(())
}
#[tokio::test]
async fn skips_zero_vcf_entry() -> anyhow::Result<()> {
let (db, _dir) = fake_store().await?;
let (client_io, server_io) = tokio::io::duplex(8192);
let (server_result, report) = futures::join!(
async {
let mut srv = obex_core::wrap(server_io);
let _ = srv.next().await;
srv.send(Bytes::from_static(CONNECT_RSP)).await?;
let _ = srv.next().await;
srv.send(metadata_rsp(A, B, C)?).await?;
let _ = srv.next().await;
srv.send(body_rsp(&list_body(&["0.vcf"]))?).await?;
Ok::<(), anyhow::Error>(())
},
async {
let mut client = PbapClient::connect(client_io).await?;
tokio::time::timeout(
Duration::from_secs(2),
sync_contacts(&mut client, &db, PhonebookPath::Pb),
)
.await
.map_err(|_| anyhow::anyhow!("sync_contacts hung — 0.vcf was not skipped"))?
},
);
server_result?;
assert_eq!(
report?,
SyncReport::Refreshed(Refresh {
listed: 1,
pull_failed: 0,
no_uid: 0,
written: 0,
wiped: false,
})
);
Ok(())
}