use super::*;
#[test]
fn new_image_captures_start_uncompressed() -> Result<()> {
let mut db = Database::open_in_memory()?;
let snapshot = image_snapshot(1, vec![("public.png", b"not actually a png".to_vec())]);
let stored = db.store_capture(&snapshot)?;
let status: String = db.conn.query_row(
"SELECT image_compression_status FROM item_representations WHERE snapshot_id = ?1",
[stored.snapshot_id()],
|row| row.get(0),
)?;
assert_eq!(status, "uncompressed");
Ok(())
}
#[test]
fn lossless_webp_optimization_rewrites_image_once_and_preserves_pixels() -> Result<()> {
let mut db = Database::open_in_memory()?;
let original = lossless_test_tiff()?;
let original_pixels = decode_rgba(&original)?;
let snapshot = image_snapshot(1, vec![("public.tiff", original.clone())]);
let stored = db.store_capture(&snapshot)?;
let original_snapshot_sha: String = db.conn.query_row(
"SELECT sha256 FROM snapshots WHERE id = ?1",
[stored.snapshot_id()],
|row| row.get(0),
)?;
let representation_count_before: i64 =
db.conn
.query_row("SELECT COUNT(*) FROM item_representations", [], |row| {
row.get(0)
})?;
let report = db.optimize_images(false, 25, true)?;
assert_eq!(report.scanned_rows, 1);
assert_eq!(report.compressed_rows, 1);
assert_eq!(report.skipped_rows, 0);
assert!(report.logical_saved_bytes >= 64 * 1024);
assert!(!report.compact_run);
let representation_count_after: i64 =
db.conn
.query_row("SELECT COUNT(*) FROM item_representations", [], |row| {
row.get(0)
})?;
assert_eq!(representation_count_after, representation_count_before);
let (uti, status, format, original_len, original_hash, byte_len, raw_hash, blob): RepresentationCompressionRow = db.conn.query_row(
r"
SELECT
uti,
image_compression_status,
image_compression_format,
image_original_byte_len,
image_original_raw_sha256,
byte_len,
raw_sha256,
blob_value
FROM item_representations
WHERE snapshot_id = ?1
",
[stored.snapshot_id()],
|row| {
Ok((
row.get(0)?,
row.get(1)?,
row.get(2)?,
row.get(3)?,
row.get(4)?,
row.get(5)?,
row.get(6)?,
row.get(7)?,
))
},
)?;
let optimized_snapshot_sha: String = db.conn.query_row(
"SELECT sha256 FROM snapshots WHERE id = ?1",
[stored.snapshot_id()],
|row| row.get(0),
)?;
let total_bytes: i64 = db.conn.query_row(
"SELECT total_bytes FROM snapshots WHERE id = ?1",
[stored.snapshot_id()],
|row| row.get(0),
)?;
assert_eq!(uti, "org.webmproject.webp");
assert_eq!(status, "compressed");
assert_eq!(format.as_deref(), Some("webp_lossless"));
assert_eq!(original_len, Some(original.len() as i64));
assert!(original_hash.is_some());
assert_eq!(byte_len as usize, blob.len());
assert_ne!(raw_hash, original_hash.unwrap());
assert_ne!(optimized_snapshot_sha, original_snapshot_sha);
assert_eq!(total_bytes, byte_len);
assert_eq!(decode_rgba(&blob)?, original_pixels);
let second_report = db.optimize_images(false, 25, true)?;
assert_eq!(second_report.scanned_rows, 0);
assert_eq!(second_report.compressed_rows, 0);
Ok(())
}
#[test]
fn file_backed_image_optimization_compacts_without_adding_rows() -> Result<()> {
let path = temp_db_path("image-optimization-compacts");
let original = lossless_test_tiff()?;
let snapshot = image_snapshot(1, vec![("public.tiff", original)]);
let mut db = Database::open_or_init(&path)?;
let stored = db.store_capture(&snapshot)?;
let size_before = super::storage_file_sizes(&path)?.total_bytes();
let row_count_before: i64 =
db.conn
.query_row("SELECT COUNT(*) FROM item_representations", [], |row| {
row.get(0)
})?;
let report = db.optimize_images(false, 25, true)?;
let row_count_after: i64 =
db.conn
.query_row("SELECT COUNT(*) FROM item_representations", [], |row| {
row.get(0)
})?;
let size_after = super::storage_file_sizes(&path)?.total_bytes();
let status: String = db.conn.query_row(
"SELECT image_compression_status FROM item_representations WHERE snapshot_id = ?1",
[stored.snapshot_id()],
|row| row.get(0),
)?;
assert_eq!(report.scanned_rows, 1);
assert_eq!(report.compressed_rows, 1);
assert!(report.compact_run);
assert!(report.compact.is_some());
assert!(!report.compact_recommended);
assert_eq!(row_count_after, row_count_before);
assert_eq!(status, "compressed");
assert!(size_after <= size_before);
cleanup_db(&path);
Ok(())
}
#[test]
fn no_compact_optimization_leaves_reclaimable_pages_for_later_compaction() -> Result<()> {
let path = temp_db_path("image-optimization-no-compact");
let original = lossless_test_tiff()?;
let snapshot = image_snapshot(1, vec![("public.tiff", original)]);
let mut db = Database::open_or_init(&path)?;
db.store_capture(&snapshot)?;
let first = db.optimize_images(false, 25, false)?;
let freelist_after_rewrite: i64 = db
.conn
.query_row("PRAGMA freelist_count", [], |row| row.get(0))?;
let second = db.optimize_images(false, 25, true)?;
assert_eq!(first.compressed_rows, 1);
assert!(!first.compact_run);
assert!(first.compact_recommended);
assert!(freelist_after_rewrite > 0);
assert_eq!(second.scanned_rows, 0);
assert_eq!(second.compressed_rows, 0);
assert!(second.compact_run);
assert!(second.compact.is_some());
assert!(!second.compact_recommended);
cleanup_db(&path);
Ok(())
}
#[test]
fn corrupt_image_rows_are_marked_skipped_once() -> Result<()> {
let mut db = Database::open_in_memory()?;
let snapshot = image_snapshot(1, vec![("public.png", b"not actually a png".to_vec())]);
let stored = db.store_capture(&snapshot)?;
let report = db.optimize_images(false, 25, true)?;
assert_eq!(report.scanned_rows, 1);
assert_eq!(report.compressed_rows, 0);
assert_eq!(report.skipped_rows, 1);
let (status, reason): (String, Option<String>) = db.conn.query_row(
"SELECT image_compression_status, image_compression_reason FROM item_representations WHERE snapshot_id = ?1",
[stored.snapshot_id()],
|row| Ok((row.get(0)?, row.get(1)?)),
)?;
assert_eq!(status, "skipped");
assert_eq!(reason.as_deref(), Some("corrupt_or_unsupported"));
let second_report = db.optimize_images(false, 25, true)?;
assert_eq!(second_report.scanned_rows, 0);
Ok(())
}
#[test]
fn repeated_open_existing_is_idempotent_after_migration() -> Result<()> {
let path = temp_db_path("legacy-idempotent");
let parent = path.parent().expect("temporary path should have a parent");
std::fs::create_dir_all(parent)?;
let conn = rusqlite::Connection::open(&path)?;
conn.execute_batch(
r"
CREATE TABLE snapshots (
id INTEGER PRIMARY KEY,
sha256 TEXT NOT NULL UNIQUE,
snapshot_kind TEXT NOT NULL,
preview_text TEXT NOT NULL,
search_text TEXT NOT NULL,
item_count INTEGER NOT NULL,
total_bytes INTEGER NOT NULL,
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE capture_events (
id INTEGER PRIMARY KEY,
snapshot_id INTEGER NOT NULL,
observed_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
change_count INTEGER NOT NULL,
frontmost_app_bundle_id TEXT,
frontmost_app_name TEXT
);
PRAGMA user_version = 0;
",
)?;
drop(conn);
drop(Database::open_existing(&path)?);
let db = Database::open_existing(&path)?;
let version: i64 = db
.conn
.query_row("PRAGMA user_version", [], |row| row.get(0))?;
assert_eq!(version, CURRENT_SCHEMA_VERSION);
std::fs::remove_file(&path)?;
Ok(())
}