1use crate::db::{
7 models::{
8 credit_card::{InternalCreditCard, UpdatableCreditCardFields},
9 Metadata,
10 },
11 schema::{CREDIT_CARD_COMMON_COLS, CREDIT_CARD_COMMON_VALS},
12};
13use crate::error::*;
14
15use jwcrypto::EncryptorDecryptor;
16use rusqlite::{Connection, Transaction};
17use sync_guid::Guid;
18use types::Timestamp;
19
20pub struct CreditCardsDeletionMetrics {
21 pub total_scrubbed_records: u64,
22}
23
24pub(crate) fn add_credit_card(
25 conn: &Connection,
26 new_credit_card_fields: UpdatableCreditCardFields,
27) -> Result<InternalCreditCard> {
28 let now = Timestamp::now();
29
30 let credit_card = InternalCreditCard {
33 guid: Guid::random(),
34 cc_name: new_credit_card_fields.cc_name,
35 cc_number_enc: new_credit_card_fields.cc_number_enc,
36 cc_number_last_4: new_credit_card_fields.cc_number_last_4,
37 cc_exp_month: new_credit_card_fields.cc_exp_month,
38 cc_exp_year: new_credit_card_fields.cc_exp_year,
39 cc_type: new_credit_card_fields.cc_type,
42 metadata: Metadata {
43 time_created: now,
44 time_last_modified: now,
45 ..Default::default()
46 },
47 };
48
49 let tx = conn.unchecked_transaction()?;
50 add_internal_credit_card(&tx, &credit_card)?;
51 tx.commit()?;
52 Ok(credit_card)
53}
54
55pub(crate) fn add_internal_credit_card(
56 tx: &Transaction<'_>,
57 card: &InternalCreditCard,
58) -> Result<()> {
59 tx.execute(
60 &format!(
61 "INSERT INTO credit_cards_data (
62 {common_cols},
63 sync_change_counter
64 ) VALUES (
65 {common_vals},
66 :sync_change_counter
67 )",
68 common_cols = CREDIT_CARD_COMMON_COLS,
69 common_vals = CREDIT_CARD_COMMON_VALS,
70 ),
71 rusqlite::named_params! {
72 ":guid": card.guid,
73 ":cc_name": card.cc_name,
74 ":cc_number_enc": card.cc_number_enc,
75 ":cc_number_last_4": card.cc_number_last_4,
76 ":cc_exp_month": card.cc_exp_month,
77 ":cc_exp_year": card.cc_exp_year,
78 ":cc_type": card.cc_type,
79 ":time_created": card.metadata.time_created,
80 ":time_last_used": card.metadata.time_last_used,
81 ":time_last_modified": card.metadata.time_last_modified,
82 ":times_used": card.metadata.times_used,
83 ":sync_change_counter": card.metadata.sync_change_counter,
84 },
85 )?;
86 Ok(())
87}
88
89pub(crate) fn get_credit_card(conn: &Connection, guid: &Guid) -> Result<InternalCreditCard> {
90 let sql = format!(
91 "SELECT
92 {common_cols},
93 sync_change_counter
94 FROM credit_cards_data
95 WHERE guid = :guid",
96 common_cols = CREDIT_CARD_COMMON_COLS
97 );
98
99 conn.query_row(&sql, [guid], InternalCreditCard::from_row)
100 .map_err(|e| match e {
101 rusqlite::Error::QueryReturnedNoRows => Error::NoSuchRecord(guid.to_string()),
102 e => e.into(),
103 })
104}
105
106pub(crate) fn get_all_credit_cards(conn: &Connection) -> Result<Vec<InternalCreditCard>> {
107 let sql = format!(
108 "SELECT
109 {common_cols},
110 sync_change_counter
111 FROM credit_cards_data",
112 common_cols = CREDIT_CARD_COMMON_COLS
113 );
114
115 let mut stmt = conn.prepare(&sql)?;
116 let credit_cards = stmt
117 .query_map([], InternalCreditCard::from_row)?
118 .collect::<std::result::Result<Vec<InternalCreditCard>, _>>()?;
119 Ok(credit_cards)
120}
121
122pub fn update_credit_card(
123 conn: &Connection,
124 guid: &Guid,
125 credit_card: &UpdatableCreditCardFields,
126) -> Result<()> {
127 let tx = conn.unchecked_transaction()?;
128 tx.execute(
129 "UPDATE credit_cards_data
130 SET cc_name = :cc_name,
131 cc_number_enc = :cc_number_enc,
132 cc_number_last_4 = :cc_number_last_4,
133 cc_exp_month = :cc_exp_month,
134 cc_exp_year = :cc_exp_year,
135 cc_type = :cc_type,
136 time_last_modified = :time_last_modified,
137 sync_change_counter = sync_change_counter + 1
138 WHERE guid = :guid",
139 rusqlite::named_params! {
140 ":cc_name": credit_card.cc_name,
141 ":cc_number_enc": credit_card.cc_number_enc,
142 ":cc_number_last_4": credit_card.cc_number_last_4,
143 ":cc_exp_month": credit_card.cc_exp_month,
144 ":cc_exp_year": credit_card.cc_exp_year,
145 ":cc_type": credit_card.cc_type,
146 ":time_last_modified": Timestamp::now(),
147 ":guid": guid,
148 },
149 )?;
150
151 tx.commit()?;
152 Ok(())
153}
154
155pub(crate) fn update_internal_credit_card(
159 tx: &Transaction<'_>,
160 card: &InternalCreditCard,
161 flag_as_changed: bool,
162) -> Result<()> {
163 let change_counter_increment = flag_as_changed as u32; tx.execute(
165 "UPDATE credit_cards_data
166 SET cc_name = :cc_name,
167 cc_number_enc = :cc_number_enc,
168 cc_number_last_4 = :cc_number_last_4,
169 cc_exp_month = :cc_exp_month,
170 cc_exp_year = :cc_exp_year,
171 cc_type = :cc_type,
172 time_created = :time_created,
173 time_last_used = :time_last_used,
174 time_last_modified = :time_last_modified,
175 times_used = :times_used,
176 sync_change_counter = sync_change_counter + :change_incr
177 WHERE guid = :guid",
178 rusqlite::named_params! {
179 ":cc_name": card.cc_name,
180 ":cc_number_enc": card.cc_number_enc,
181 ":cc_number_last_4": card.cc_number_last_4,
182 ":cc_exp_month": card.cc_exp_month,
183 ":cc_exp_year": card.cc_exp_year,
184 ":cc_type": card.cc_type,
185 ":time_created": card.metadata.time_created,
186 ":time_last_used": card.metadata.time_last_used,
187 ":time_last_modified": card.metadata.time_last_modified,
188 ":times_used": card.metadata.times_used,
189 ":change_incr": change_counter_increment,
190 ":guid": card.guid,
191 },
192 )?;
193 Ok(())
194}
195
196pub fn delete_credit_card(conn: &Connection, guid: &Guid) -> Result<bool> {
197 let tx = conn.unchecked_transaction()?;
198
199 let exists = tx.execute(
201 "DELETE FROM credit_cards_data
202 WHERE guid = :guid",
203 rusqlite::named_params! {
204 ":guid": guid.as_str(),
205 },
206 )? != 0;
207
208 tx.commit()?;
209 Ok(exists)
210}
211
212pub fn scrub_encrypted_credit_card_data(conn: &Connection) -> Result<()> {
213 let tx = conn.unchecked_transaction()?;
214 tx.execute("UPDATE credit_cards_data SET cc_number_enc = ''", [])?;
215 tx.commit()?;
216 Ok(())
217}
218
219pub fn scrub_undecryptable_credit_card_data_for_remote_replacement(
220 conn: &Connection,
221 local_encryption_key: String,
222) -> Result<CreditCardsDeletionMetrics> {
223 let tx = conn.unchecked_transaction()?;
224 let mut scrubbed_records = 0;
225 let encdec = EncryptorDecryptor::new(local_encryption_key.as_str()).unwrap();
226
227 let undecryptable_record_ids = get_all_credit_cards(conn)?
228 .into_iter()
229 .filter(|credit_card| encdec.decrypt(&credit_card.cc_number_enc).is_err())
230 .map(|credit_card| credit_card.guid)
231 .collect::<Vec<_>>();
232
233 sql_support::each_chunk(&undecryptable_record_ids, |chunk, _| -> Result<()> {
236 let scrubbed = tx.execute(
237 &format!(
238 "UPDATE credit_cards_data
239 SET cc_number_enc = '',
240 time_created = 0,
241 time_last_used = 0,
242 time_last_modified = 0,
243 times_used = 0,
244 sync_change_counter = 0
245 WHERE guid IN ({})",
246 sql_support::repeat_sql_values(chunk.len())
247 ),
248 rusqlite::params_from_iter(chunk),
249 )?;
250 scrubbed_records += scrubbed;
251 Ok(())
252 })?;
253
254 tx.commit()?;
255 Ok(CreditCardsDeletionMetrics {
256 total_scrubbed_records: scrubbed_records as u64,
257 })
258}
259
260pub fn touch(conn: &Connection, guid: &Guid) -> Result<()> {
261 let tx = conn.unchecked_transaction()?;
262 let now_ms = Timestamp::now();
263
264 tx.execute(
265 "UPDATE credit_cards_data
266 SET time_last_used = :time_last_used,
267 times_used = times_used + 1,
268 sync_change_counter = sync_change_counter + 1
269 WHERE guid = :guid",
270 rusqlite::named_params! {
271 ":time_last_used": now_ms,
272 ":guid": guid.as_str(),
273 },
274 )?;
275
276 tx.commit()?;
277 Ok(())
278}
279
280#[cfg(test)]
281pub(crate) mod tests {
282 use super::*;
283 use crate::db::test::new_mem_db;
284 use crate::encryption::EncryptorDecryptor;
285 use nss::ensure_initialized;
286 use sync15::bso::IncomingBso;
287
288 pub fn get_all(
289 conn: &Connection,
290 table_name: String,
291 ) -> rusqlite::Result<Vec<String>, rusqlite::Error> {
292 let mut stmt = conn.prepare(&format!(
293 "SELECT guid FROM {table_name}",
294 table_name = table_name
295 ))?;
296 let rows = stmt.query_map([], |row| row.get(0))?;
297
298 let mut guids = Vec::new();
299 for guid_result in rows {
300 guids.push(guid_result?);
301 }
302
303 Ok(guids)
304 }
305
306 pub fn insert_tombstone_record(
307 conn: &Connection,
308 guid: String,
309 ) -> rusqlite::Result<usize, rusqlite::Error> {
310 conn.execute(
311 "INSERT INTO credit_cards_tombstones (
312 guid,
313 time_deleted
314 ) VALUES (
315 :guid,
316 :time_deleted
317 )",
318 rusqlite::named_params! {
319 ":guid": guid,
320 ":time_deleted": Timestamp::now(),
321 },
322 )
323 }
324
325 pub(crate) fn test_insert_mirror_record(conn: &Connection, bso: IncomingBso) {
326 conn.execute(
331 "INSERT INTO credit_cards_mirror (guid, payload)
332 VALUES (:guid, :payload)",
333 rusqlite::named_params! {
334 ":guid": &bso.envelope.id,
335 ":payload": &bso.payload,
336 },
337 )
338 .expect("should insert");
339 }
340
341 #[test]
342 fn test_credit_card_create_and_read() -> Result<()> {
343 ensure_initialized();
344 let db = new_mem_db();
345
346 let saved_credit_card = add_credit_card(
347 &db,
348 UpdatableCreditCardFields {
349 cc_name: "jane doe".to_string(),
350 cc_number_enc: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX".to_string(),
351 cc_number_last_4: "1234".to_string(),
352 cc_exp_month: 3,
353 cc_exp_year: 2022,
354 cc_type: "visa".to_string(),
355 },
356 )?;
357
358 assert_ne!(Guid::default(), saved_credit_card.guid);
360
361 assert_ne!(0, saved_credit_card.metadata.time_created.as_millis());
363 assert_ne!(0, saved_credit_card.metadata.time_last_modified.as_millis());
364
365 assert_eq!(0, saved_credit_card.metadata.sync_change_counter);
367
368 let retrieved_credit_card = get_credit_card(&db, &saved_credit_card.guid)?;
370
371 assert_eq!(saved_credit_card.guid, retrieved_credit_card.guid);
372 assert_eq!(saved_credit_card.cc_name, retrieved_credit_card.cc_name);
373 assert_eq!(
374 saved_credit_card.cc_number_enc,
375 retrieved_credit_card.cc_number_enc
376 );
377 assert_eq!(
378 saved_credit_card.cc_number_last_4,
379 retrieved_credit_card.cc_number_last_4
380 );
381 assert_eq!(
382 saved_credit_card.cc_exp_month,
383 retrieved_credit_card.cc_exp_month
384 );
385 assert_eq!(
386 saved_credit_card.cc_exp_year,
387 retrieved_credit_card.cc_exp_year
388 );
389 assert_eq!(saved_credit_card.cc_type, retrieved_credit_card.cc_type);
390
391 let delete_result = delete_credit_card(&db, &saved_credit_card.guid);
393 assert!(delete_result.is_ok());
394 assert!(delete_result?);
395
396 assert!(get_credit_card(&db, &saved_credit_card.guid).is_err());
397
398 Ok(())
399 }
400
401 #[test]
402 fn test_credit_card_missing_guid() {
403 ensure_initialized();
404 let db = new_mem_db();
405 let guid = Guid::random();
406 let result = get_credit_card(&db, &guid);
407
408 assert_eq!(
409 result.unwrap_err().to_string(),
410 Error::NoSuchRecord(guid.to_string()).to_string()
411 );
412 }
413
414 #[test]
415 fn test_credit_card_read_all() -> Result<()> {
416 ensure_initialized();
417 let db = new_mem_db();
418
419 let saved_credit_card = add_credit_card(
420 &db,
421 UpdatableCreditCardFields {
422 cc_name: "jane doe".to_string(),
423 cc_number_enc: "YYYYYYYYYYYYYYYYYYYYYYYYYYYYY".to_string(),
424 cc_number_last_4: "4321".to_string(),
425 cc_exp_month: 3,
426 cc_exp_year: 2022,
427 cc_type: "visa".to_string(),
428 },
429 )?;
430
431 let saved_credit_card2 = add_credit_card(
432 &db,
433 UpdatableCreditCardFields {
434 cc_name: "john deer".to_string(),
435 cc_number_enc: "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ".to_string(),
436 cc_number_last_4: "6543".to_string(),
437 cc_exp_month: 10,
438 cc_exp_year: 2025,
439 cc_type: "mastercard".to_string(),
440 },
441 )?;
442
443 let saved_credit_card3 = add_credit_card(
445 &db,
446 UpdatableCreditCardFields {
447 cc_name: "abraham lincoln".to_string(),
448 cc_number_enc: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA".to_string(),
449 cc_number_last_4: "9876".to_string(),
450 cc_exp_month: 1,
451 cc_exp_year: 2024,
452 cc_type: "amex".to_string(),
453 },
454 )?;
455
456 let delete_result = delete_credit_card(&db, &saved_credit_card3.guid);
457 assert!(delete_result.is_ok());
458 assert!(delete_result?);
459
460 let retrieved_credit_cards = get_all_credit_cards(&db)?;
461
462 assert!(!retrieved_credit_cards.is_empty());
463 let expected_number_of_credit_cards = 2;
464 assert_eq!(
465 expected_number_of_credit_cards,
466 retrieved_credit_cards.len()
467 );
468
469 let retrieved_credit_card_guids = [
470 retrieved_credit_cards[0].guid.as_str(),
471 retrieved_credit_cards[1].guid.as_str(),
472 ];
473 assert!(retrieved_credit_card_guids.contains(&saved_credit_card.guid.as_str()));
474 assert!(retrieved_credit_card_guids.contains(&saved_credit_card2.guid.as_str()));
475
476 Ok(())
477 }
478
479 #[test]
480 fn test_credit_card_update() -> Result<()> {
481 ensure_initialized();
482 let db = new_mem_db();
483
484 let saved_credit_card = add_credit_card(
485 &db,
486 UpdatableCreditCardFields {
487 cc_name: "john deer".to_string(),
488 cc_number_enc: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA".to_string(),
489 cc_number_last_4: "4321".to_string(),
490 cc_exp_month: 10,
491 cc_exp_year: 2025,
492 cc_type: "mastercard".to_string(),
493 },
494 )?;
495
496 let expected_cc_name = "john doe".to_string();
497 let update_result = update_credit_card(
498 &db,
499 &saved_credit_card.guid,
500 &UpdatableCreditCardFields {
501 cc_name: expected_cc_name.clone(),
502 cc_number_enc: "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBB".to_string(),
503 cc_number_last_4: "1234".to_string(),
504 cc_type: "mastercard".to_string(),
505 cc_exp_month: 10,
506 cc_exp_year: 2025,
507 },
508 );
509 assert!(update_result.is_ok());
510
511 let updated_credit_card = get_credit_card(&db, &saved_credit_card.guid)?;
512
513 assert_eq!(saved_credit_card.guid, updated_credit_card.guid);
514 assert_eq!(expected_cc_name, updated_credit_card.cc_name);
515
516 assert_eq!(1, updated_credit_card.metadata.sync_change_counter);
518
519 Ok(())
520 }
521
522 #[test]
523 fn test_credit_card_update_internal_credit_card() -> Result<()> {
524 ensure_initialized();
525 let mut db = new_mem_db();
526 let tx = db.transaction()?;
527
528 let guid = Guid::random();
529 add_internal_credit_card(
530 &tx,
531 &InternalCreditCard {
532 guid: guid.clone(),
533 cc_name: "john deer".to_string(),
534 cc_number_enc: "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBB".to_string(),
535 cc_number_last_4: "1234".to_string(),
536 cc_exp_month: 10,
537 cc_exp_year: 2025,
538 cc_type: "mastercard".to_string(),
539 ..Default::default()
540 },
541 )?;
542
543 let expected_cc_exp_month = 11;
544 update_internal_credit_card(
545 &tx,
546 &InternalCreditCard {
547 guid: guid.clone(),
548 cc_name: "john deer".to_string(),
549 cc_number_enc: "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBB".to_string(),
550 cc_number_last_4: "1234".to_string(),
551 cc_exp_month: expected_cc_exp_month,
552 cc_exp_year: 2025,
553 cc_type: "mastercard".to_string(),
554 ..Default::default()
555 },
556 false,
557 )?;
558
559 let record_exists: bool = tx.query_row(
560 "SELECT EXISTS (
561 SELECT 1
562 FROM credit_cards_data
563 WHERE guid = :guid
564 AND cc_exp_month = :cc_exp_month
565 AND sync_change_counter = 0
566 )",
567 [&guid.to_string(), &expected_cc_exp_month.to_string()],
568 |row| row.get(0),
569 )?;
570 assert!(record_exists);
571
572 Ok(())
573 }
574
575 #[test]
576 fn test_credit_card_delete() -> Result<()> {
577 ensure_initialized();
578 let db = new_mem_db();
579 let encdec = EncryptorDecryptor::new_with_random_key().unwrap();
580
581 let saved_credit_card = add_credit_card(
582 &db,
583 UpdatableCreditCardFields {
584 cc_name: "john deer".to_string(),
585 cc_number_enc: encdec.encrypt("1234567812345678")?,
586 cc_number_last_4: "5678".to_string(),
587 cc_exp_month: 10,
588 cc_exp_year: 2025,
589 cc_type: "mastercard".to_string(),
590 },
591 )?;
592
593 let delete_result = delete_credit_card(&db, &saved_credit_card.guid);
594 assert!(delete_result.is_ok());
595 assert!(delete_result?);
596
597 let saved_credit_card2 = add_credit_card(
598 &db,
599 UpdatableCreditCardFields {
600 cc_name: "john doe".to_string(),
601 cc_number_enc: encdec.encrypt("1234123412341234")?,
602 cc_number_last_4: "1234".to_string(),
603 cc_exp_month: 5,
604 cc_exp_year: 2024,
605 cc_type: "visa".to_string(),
606 },
607 )?;
608
609 let cc2_guid = saved_credit_card2.guid.clone();
611 let payload = saved_credit_card2.into_test_incoming_bso(&encdec, Default::default());
612
613 test_insert_mirror_record(&db, payload);
614
615 let delete_result2 = delete_credit_card(&db, &cc2_guid);
616 assert!(delete_result2.is_ok());
617 assert!(delete_result2?);
618
619 let tombstone_exists: bool = db.query_row(
621 "SELECT EXISTS (
622 SELECT 1
623 FROM credit_cards_tombstones
624 WHERE guid = :guid
625 )",
626 [&cc2_guid],
627 |row| row.get(0),
628 )?;
629 assert!(tombstone_exists);
630
631 db.execute(
633 "DELETE FROM credit_cards_tombstones
634 WHERE guid = :guid",
635 rusqlite::named_params! {
636 ":guid": cc2_guid,
637 },
638 )?;
639
640 Ok(())
641 }
642
643 #[test]
644 fn test_scrub_encrypted_credit_card_data() -> Result<()> {
645 ensure_initialized();
646 let db = new_mem_db();
647 let encdec = EncryptorDecryptor::new_with_random_key().unwrap();
648 let mut saved_credit_cards = Vec::with_capacity(10);
649 for _ in 0..5 {
650 saved_credit_cards.push(add_credit_card(
651 &db,
652 UpdatableCreditCardFields {
653 cc_name: "john deer".to_string(),
654 cc_number_enc: encdec.encrypt("1234567812345678")?,
655 cc_number_last_4: "5678".to_string(),
656 cc_exp_month: 10,
657 cc_exp_year: 2025,
658 cc_type: "mastercard".to_string(),
659 },
660 )?);
661 }
662
663 scrub_encrypted_credit_card_data(&db)?;
664 for saved_credit_card in saved_credit_cards.into_iter() {
665 let retrieved_credit_card = get_credit_card(&db, &saved_credit_card.guid)?;
666 assert_eq!(retrieved_credit_card.cc_number_enc, "");
667 }
668
669 Ok(())
670 }
671
672 #[test]
673 fn test_scrub_undecryptable_credit_card_date_for_remote_replacement() -> Result<()> {
674 ensure_initialized();
675 let db = new_mem_db();
676 let old_key = EncryptorDecryptor::create_key()?;
677 let old_encdec = EncryptorDecryptor::new(&old_key)?;
678 let key = EncryptorDecryptor::create_key()?;
679 let encdec = EncryptorDecryptor::new(&key)?;
680
681 let undecryptable_credit_card = add_credit_card(
682 &db,
683 UpdatableCreditCardFields {
684 cc_name: "jane doe".to_string(),
685 cc_number_enc: old_encdec.encrypt("2345678923456789")?,
686 cc_number_last_4: "6789".to_string(),
687 cc_exp_month: 9,
688 cc_exp_year: 2027,
689 cc_type: "visa".to_string(),
690 },
691 )?;
692
693 let encrypted_cc_number = encdec.encrypt("567812345678123456781")?;
694 let credit_card = add_credit_card(
695 &db,
696 UpdatableCreditCardFields {
697 cc_name: "john deer".to_string(),
698 cc_number_enc: encrypted_cc_number.clone(),
699 cc_number_last_4: "6781".to_string(),
700 cc_exp_month: 10,
701 cc_exp_year: 2025,
702 cc_type: "mastercard".to_string(),
703 },
704 )?;
705
706 let metrics = scrub_undecryptable_credit_card_data_for_remote_replacement(&db.writer, key)?;
707 assert_eq!(metrics.total_scrubbed_records, 1);
708
709 let credit_cards = get_all_credit_cards(&db)?;
710 assert_eq!(credit_cards.len(), 2);
711
712 let retrieved_credit_card = get_credit_card(&db, &undecryptable_credit_card.guid)?;
713 assert_eq!(retrieved_credit_card.cc_number_enc, "");
714
715 let retrieved_credit_card2 = get_credit_card(&db, &credit_card.guid)?;
716 assert_eq!(retrieved_credit_card2.cc_number_enc, encrypted_cc_number);
717
718 Ok(())
719 }
720
721 #[test]
722 fn test_credit_card_trigger_on_create() -> Result<()> {
723 ensure_initialized();
724 let db = new_mem_db();
725 let tx = db.unchecked_transaction()?;
726 let guid = Guid::random();
727
728 insert_tombstone_record(&db, guid.to_string())?;
730
731 let credit_card = InternalCreditCard {
733 guid,
734 cc_name: "john deer".to_string(),
735 cc_number_enc: "WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW".to_string(),
736 cc_number_last_4: "6543".to_string(),
737 cc_exp_month: 10,
738 cc_exp_year: 2025,
739 cc_type: "mastercard".to_string(),
740
741 ..Default::default()
742 };
743
744 let add_credit_card_result = add_internal_credit_card(&tx, &credit_card);
745 assert!(add_credit_card_result.is_err());
746
747 let expected_error_message = "guid exists in `credit_cards_tombstones`";
748 assert!(add_credit_card_result
749 .unwrap_err()
750 .to_string()
751 .contains(expected_error_message));
752
753 Ok(())
754 }
755
756 #[test]
757 fn test_credit_card_trigger_on_delete() -> Result<()> {
758 ensure_initialized();
759 let db = new_mem_db();
760 let tx = db.unchecked_transaction()?;
761 let guid = Guid::random();
762
763 let credit_card = InternalCreditCard {
765 guid,
766 cc_name: "jane doe".to_string(),
767 cc_number_enc: "WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW".to_string(),
768 cc_number_last_4: "6543".to_string(),
769 cc_exp_month: 3,
770 cc_exp_year: 2022,
771 cc_type: "visa".to_string(),
772 ..Default::default()
773 };
774 add_internal_credit_card(&tx, &credit_card)?;
775
776 let tombstone_result = insert_tombstone_record(&db, credit_card.guid.to_string());
778
779 let expected_error_message = "guid exists in `credit_cards_data`";
780 assert!(tombstone_result
781 .unwrap_err()
782 .to_string()
783 .contains(expected_error_message));
784
785 Ok(())
786 }
787
788 #[test]
789 fn test_credit_card_touch() -> Result<()> {
790 ensure_initialized();
791 let db = new_mem_db();
792 let saved_credit_card = add_credit_card(
793 &db,
794 UpdatableCreditCardFields {
795 cc_name: "john doe".to_string(),
796 cc_number_enc: "WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW".to_string(),
797 cc_number_last_4: "6543".to_string(),
798 cc_exp_month: 5,
799 cc_exp_year: 2024,
800 cc_type: "visa".to_string(),
801 },
802 )?;
803
804 assert_eq!(saved_credit_card.metadata.sync_change_counter, 0);
805 assert_eq!(saved_credit_card.metadata.times_used, 0);
806
807 touch(&db, &saved_credit_card.guid)?;
808
809 let touched_credit_card = get_credit_card(&db, &saved_credit_card.guid)?;
810
811 assert_eq!(touched_credit_card.metadata.sync_change_counter, 1);
812 assert_eq!(touched_credit_card.metadata.times_used, 1);
813
814 Ok(())
815 }
816}