fn test_delete_visits_for()

in components/places/src/storage/history.rs [2116:2307]


    fn test_delete_visits_for() -> Result<()> {
        use crate::storage::bookmarks::{
            self, BookmarkPosition, BookmarkRootGuid, InsertableBookmark,
        };

        let db = PlacesDb::open_in_memory(ConnectionType::ReadWrite)?;

        struct TestPage {
            href: &'static str,
            synced: bool,
            bookmark_title: Option<&'static str>,
            keyword: Option<&'static str>,
        }

        fn page_has_tombstone(conn: &PlacesDb, guid: &SyncGuid) -> Result<bool> {
            let exists = conn
                .try_query_one::<bool, _>(
                    "SELECT EXISTS(SELECT 1 FROM moz_places_tombstones
                                   WHERE guid = :guid)",
                    rusqlite::named_params! { ":guid" : guid },
                    false,
                )?
                .unwrap_or_default();
            Ok(exists)
        }

        fn page_has_visit_tombstones(conn: &PlacesDb, page_id: RowId) -> Result<bool> {
            let exists = conn
                .try_query_one::<bool, _>(
                    "SELECT EXISTS(SELECT 1 FROM moz_historyvisit_tombstones
                                   WHERE place_id = :page_id)",
                    rusqlite::named_params! { ":page_id": page_id },
                    false,
                )?
                .unwrap_or_default();
            Ok(exists)
        }

        let pages = &[
            // A is synced and has a bookmark, so we should insert tombstones
            // for all its visits.
            TestPage {
                href: "http://example.com/a",
                synced: true,
                bookmark_title: Some("A"),
                keyword: None,
            },
            // B is synced but only has visits, so we should insert a tombstone
            // for the page.
            TestPage {
                href: "http://example.com/b",
                synced: true,
                bookmark_title: None,
                keyword: None,
            },
            // C isn't synced but has a keyword, so we should delete all its
            // visits, but not the page.
            TestPage {
                href: "http://example.com/c",
                synced: false,
                bookmark_title: None,
                keyword: Some("one"),
            },
            // D isn't synced and only has visits, so we should delete it
            // entirely.
            TestPage {
                href: "http://example.com/d",
                synced: false,
                bookmark_title: None,
                keyword: None,
            },
        ];
        for page in pages {
            let url = Url::parse(page.href)?;
            let obs = VisitObservation::new(url.clone())
                .with_visit_type(VisitType::Link)
                .with_at(Some(SystemTime::now().into()));
            apply_observation(&db, obs)?;

            if page.synced {
                db.execute_cached(
                    &format!(
                        "UPDATE moz_places
                             SET sync_status = {}
                         WHERE url_hash = hash(:url) AND
                               url = :url",
                        (SyncStatus::Normal as u8)
                    ),
                    &[(":url", &url.as_str())],
                )?;
            }

            if let Some(title) = page.bookmark_title {
                bookmarks::insert_bookmark(
                    &db,
                    InsertableBookmark {
                        parent_guid: BookmarkRootGuid::Unfiled.into(),
                        position: BookmarkPosition::Append,
                        date_added: None,
                        last_modified: None,
                        guid: None,
                        url: url.clone(),
                        title: Some(title.to_owned()),
                    }
                    .into(),
                )?;
            }

            if let Some(keyword) = page.keyword {
                // We don't have a public API for inserting keywords, so just
                // write to the database directly.
                db.execute_cached(
                    "INSERT INTO moz_keywords(place_id, keyword)
                     SELECT id, :keyword
                     FROM moz_places
                     WHERE url_hash = hash(:url) AND
                           url = :url",
                    &[(":url", &url.as_str()), (":keyword", &keyword)],
                )?;
            }

            // Now delete all visits.
            let (info, _) =
                fetch_visits(&db, &url, 0)?.expect("Should return visits for test page");
            delete_visits_for(&db, &info.guid)?;

            match (
                page.synced,
                page.bookmark_title.is_some() || page.keyword.is_some(),
            ) {
                (true, true) => {
                    let (_, visits) = fetch_visits(&db, &url, 0)?
                        .expect("Shouldn't delete synced page with foreign count");
                    assert!(
                        visits.is_empty(),
                        "Should delete all visits from synced page with foreign count"
                    );
                    assert!(
                        !page_has_tombstone(&db, &info.guid)?,
                        "Shouldn't insert tombstone for synced page with foreign count"
                    );
                    assert!(
                        page_has_visit_tombstones(&db, info.row_id)?,
                        "Should insert visit tombstones for synced page with foreign count"
                    );
                }
                (true, false) => {
                    assert!(
                        fetch_visits(&db, &url, 0)?.is_none(),
                        "Should delete synced page"
                    );
                    assert!(
                        page_has_tombstone(&db, &info.guid)?,
                        "Should insert tombstone for synced page"
                    );
                    assert!(
                        !page_has_visit_tombstones(&db, info.row_id)?,
                        "Shouldn't insert visit tombstones for synced page"
                    );
                }
                (false, true) => {
                    let (_, visits) = fetch_visits(&db, &url, 0)?
                        .expect("Shouldn't delete page with foreign count");
                    assert!(
                        visits.is_empty(),
                        "Should delete all visits from page with foreign count"
                    );
                    assert!(
                        !page_has_tombstone(&db, &info.guid)?,
                        "Shouldn't insert tombstone for page with foreign count"
                    );
                    assert!(
                        !page_has_visit_tombstones(&db, info.row_id)?,
                        "Shouldn't insert visit tombstones for page with foreign count"
                    );
                }
                (false, false) => {
                    assert!(fetch_visits(&db, &url, 0)?.is_none(), "Should delete page");
                    assert!(
                        !page_has_tombstone(&db, &info.guid)?,
                        "Shouldn't insert tombstone for page"
                    );
                    assert!(
                        !page_has_visit_tombstones(&db, info.row_id)?,
                        "Shouldn't insert visit tombstones for page"
                    );
                }
            }
        }

        Ok(())
    }