From 16888d5a3aa9623f1ee41aba291d656e6b233c6f Mon Sep 17 00:00:00 2001 From: adlee-was-taken Date: Wed, 29 Apr 2026 22:57:37 -0400 Subject: [PATCH] =?UTF-8?q?feat(core):=20import=5Flastpass=20=E2=80=94=20g?= =?UTF-8?q?roup,=20favorite,=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Map LastPass grouping/fav/extra columns to relicario item metadata. Grouping becomes item.group, fav="1" sets item.favorite, extra becomes item.notes. Multi-line extra via CSV quoting round-trips correctly. Co-Authored-By: Claude Opus 4.7 --- crates/relicario-core/src/import_lastpass.rs | 8 +-- .../relicario-core/tests/import_lastpass.rs | 54 +++++++++++++++++++ 2 files changed, 59 insertions(+), 3 deletions(-) diff --git a/crates/relicario-core/src/import_lastpass.rs b/crates/relicario-core/src/import_lastpass.rs index e7a2204..2385553 100644 --- a/crates/relicario-core/src/import_lastpass.rs +++ b/crates/relicario-core/src/import_lastpass.rs @@ -94,8 +94,8 @@ fn map_row(record: &csv::StringRecord, row: usize) -> std::result::Result std::result::Result String { warnings.first().expect("expected at least one warning").message.clone() } + +#[test] +fn grouping_maps_to_item_group() { + let csv = format!("{HEADER}\nhttps://x,u,p,,,Bank,Finance,"); + let (items, warnings) = parse_lastpass_csv(csv.as_bytes()).unwrap(); + assert!(warnings.is_empty()); + assert_eq!(items[0].group.as_deref(), Some("Finance")); +} + +#[test] +fn empty_grouping_yields_none() { + let csv = format!("{HEADER}\nhttps://x,u,p,,,Bank,,"); + let (items, _) = parse_lastpass_csv(csv.as_bytes()).unwrap(); + assert!(items[0].group.is_none()); +} + +#[test] +fn fav_one_marks_favorite() { + let csv = format!("{HEADER}\nhttps://x,u,p,,,Bank,,1"); + let (items, _) = parse_lastpass_csv(csv.as_bytes()).unwrap(); + assert!(items[0].favorite); +} + +#[test] +fn fav_zero_or_blank_not_favorite() { + let csv = format!( + "{HEADER}\n\ + https://x,u,p,,,Zero,,0\n\ + https://x,u,p,,,Blank,,", + ); + let (items, _) = parse_lastpass_csv(csv.as_bytes()).unwrap(); + assert_eq!(items.len(), 2); + assert!(!items[0].favorite); + assert!(!items[1].favorite); +} + +#[test] +fn extra_becomes_notes_for_login() { + let csv = format!("{HEADER}\nhttps://x,u,p,,a hint,Bank,,"); + let (items, _) = parse_lastpass_csv(csv.as_bytes()).unwrap(); + assert_eq!(items[0].notes.as_deref(), Some("a hint")); +} + +#[test] +fn multiline_extra_round_trips_via_quoting() { + // CSV double-quotes escape embedded newlines. + let csv = format!( + "{HEADER}\n\ + https://x,u,p,,\"line1\nline2\nline3\",Bank,,", + ); + let (items, warnings) = parse_lastpass_csv(csv.as_bytes()).unwrap(); + assert!(warnings.is_empty(), "multi-line extra should parse cleanly"); + assert_eq!(items[0].notes.as_deref(), Some("line1\nline2\nline3")); +}