//! Guards against changes that would make previously written data unreadable. use cipherbox::{CipherBox, Error}; use serde::{Deserialize, Serialize}; use zeroize::Zeroize; /// Password for the fixture. It contains a precomposed "é" (U+00E9), so reading /// it back with the decomposed form also pins password normalization. const FIXTURE_PASSWORD: &str = "fixture p\u{e9}ssword"; /// A value exercising each part of the serde data model that the fixture /// pins down. #[derive(Debug, PartialEq, Serialize, Deserialize, Zeroize)] struct Fixture { text: String, bytes: Vec, small: u8, large: u64, negative: i64, flag: bool, missing: Option, present: Option, modes: Vec, } #[derive(Debug, PartialEq, Serialize, Deserialize, Zeroize)] enum Mode { Plain, Tagged(u32), Named { depth: u8 }, } fn fixture() -> Fixture { Fixture { text: String::from("The quick brown fox jumps over the lazy dog"), bytes: vec![0, 1, 2, 254, 255], small: 7, large: u64::MAX, negative: -42, flag: true, missing: None, present: Some(1_000_000), modes: vec![Mode::Plain, Mode::Tagged(9), Mode::Named { depth: 3 }], } } /// `tests/data/v1.cb` was written by format version 1 and must remain readable /// by every future release. Never regenerate it: if this test fails, the /// change that broke it has made existing user data unreadable. A new format /// version gets a new fixture alongside this one. #[test] fn format_v1_fixture_remains_readable() { let file = include_bytes!("data/v1.cb"); let restored: CipherBox = CipherBox::read(FIXTURE_PASSWORD, &file[..]).unwrap(); assert_eq!(*restored, fixture()); let decomposed = FIXTURE_PASSWORD.replace('\u{e9}', "e\u{301}"); let restored: CipherBox = CipherBox::read(&decomposed, &file[..]).unwrap(); assert_eq!(*restored, fixture()); } #[derive(Serialize, Deserialize, Zeroize)] struct SettingsV1 { username: String, token: String, retired: u32, } #[derive(Debug, PartialEq, Serialize, Deserialize, Zeroize)] struct SettingsV2 { username: String, token: String, #[serde(default)] server: Option, } /// Applications evolve the types they store. Adding a defaulted field or /// removing a field must not make existing data unreadable. #[test] fn stored_types_can_evolve() { let mut file = Vec::new(); CipherBox::new(SettingsV1 { username: String::from("alice"), token: String::from("hunter2"), retired: 5, }) .write("password", &mut file) .unwrap(); let restored: CipherBox = CipherBox::read("password", file.as_slice()).unwrap(); assert_eq!( *restored, SettingsV2 { username: String::from("alice"), token: String::from("hunter2"), server: None, } ); } #[test] fn mismatched_type_is_a_deserialization_error() { let mut file = Vec::new(); CipherBox::new(String::from("not a number")) .write("password", &mut file) .unwrap(); assert!(matches!( CipherBox::::read("password", file.as_slice()), Err(Error::Deserialization(_)) )); }