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