lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README

open_options.rs (4967B)


      1 use std::fs;
      2 use std::time::Duration;
      3 
      4 use radroots_storage::event::SourceGeneration;
      5 use radroots_storage::status::WriterPolicy;
      6 use radroots_storage_sqlite::{Error, OpenMode, OpenOptions, Paths};
      7 
      8 #[test]
      9 fn paths_derive_only_runtime_and_private_database_ownership() {
     10     let directory = tempfile::tempdir().expect("temporary directory");
     11     let paths = Paths::from_directory(directory.path()).expect("owned paths");
     12 
     13     assert_eq!(paths.runtime(), directory.path().join("runtime.sqlite"));
     14     assert_eq!(paths.private(), directory.path().join("private.sqlite"));
     15     assert!(!format!("{paths:?}").contains("studio.sqlite"));
     16 }
     17 
     18 #[test]
     19 fn paths_reject_relative_traversal_and_wrong_owned_names() {
     20     assert!(matches!(
     21         Paths::from_directory("relative"),
     22         Err(Error::InvalidPath(_))
     23     ));
     24 
     25     let directory = tempfile::tempdir().expect("temporary directory");
     26     assert!(matches!(
     27         Paths::from_files(
     28             directory.path().join("../runtime.sqlite"),
     29             directory.path().join("private.sqlite"),
     30         ),
     31         Err(Error::InvalidPath(_))
     32     ));
     33     assert!(matches!(
     34         Paths::from_files(
     35             directory.path().join("studio.sqlite"),
     36             directory.path().join("private.sqlite"),
     37         ),
     38         Err(Error::UnexpectedFileName { .. })
     39     ));
     40     assert!(matches!(
     41         Paths::from_files("/", directory.path().join("private.sqlite")),
     42         Err(Error::UnexpectedFileName { .. })
     43     ));
     44 }
     45 
     46 #[test]
     47 fn create_mode_accepts_missing_files_but_existing_modes_do_not() {
     48     let directory = tempfile::tempdir().expect("temporary directory");
     49     let paths = Paths::from_directory(directory.path()).expect("owned paths");
     50 
     51     OpenOptions::new(paths.clone(), OpenMode::Create)
     52         .validate_filesystem()
     53         .expect("create plan");
     54     assert!(matches!(
     55         OpenOptions::new(paths.clone(), OpenMode::ReadOnly).validate_filesystem(),
     56         Err(Error::MissingFile(_))
     57     ));
     58     assert!(matches!(
     59         OpenOptions::new(paths, OpenMode::ReadWriteExisting).validate_filesystem(),
     60         Err(Error::MissingFile(_))
     61     ));
     62 }
     63 
     64 #[test]
     65 fn existing_modes_accept_only_regular_owned_files() {
     66     let directory = tempfile::tempdir().expect("temporary directory");
     67     let paths = Paths::from_directory(directory.path()).expect("owned paths");
     68     fs::write(paths.runtime(), []).expect("runtime file");
     69     fs::write(paths.private(), []).expect("private file");
     70 
     71     for mode in [OpenMode::ReadOnly, OpenMode::ReadWriteExisting] {
     72         OpenOptions::new(paths.clone(), mode)
     73             .validate_filesystem()
     74             .expect("existing plan");
     75     }
     76 }
     77 
     78 #[test]
     79 fn options_fix_connection_invariants_and_bound_busy_timeout() {
     80     let directory = tempfile::tempdir().expect("temporary directory");
     81     let paths = Paths::from_directory(directory.path()).expect("owned paths");
     82     let read_only = OpenOptions::new(paths.clone(), OpenMode::ReadOnly);
     83     assert_eq!(read_only.busy_timeout(), Duration::from_secs(5));
     84     assert!(read_only.foreign_keys_enabled());
     85     assert!(!read_only.wal_enabled());
     86     assert_eq!(read_only.writer_policy(), WriterPolicy::NoWriter);
     87     assert!(!read_only.mode().is_writable());
     88     assert!(!read_only.mode().may_create());
     89 
     90     let create = OpenOptions::new(paths, OpenMode::Create)
     91         .with_busy_timeout(Duration::from_secs(30))
     92         .expect("bounded timeout");
     93     assert_eq!(create.busy_timeout(), Duration::from_secs(30));
     94     assert!(create.foreign_keys_enabled());
     95     assert!(create.wal_enabled());
     96     assert_eq!(create.writer_policy(), WriterPolicy::AdvisoryProcessLock);
     97     assert!(create.mode().is_writable());
     98     assert!(create.mode().may_create());
     99 
    100     for invalid in [Duration::ZERO, Duration::from_secs(61)] {
    101         assert!(matches!(
    102             OpenOptions::new(create.paths().clone(), OpenMode::Create).with_busy_timeout(invalid),
    103             Err(Error::InvalidBusyTimeout { .. })
    104         ));
    105     }
    106 
    107     let generation = SourceGeneration::new([9; 32]).expect("source generation");
    108     let bootstrapped = OpenOptions::new(create.paths().clone(), OpenMode::Create)
    109         .with_source_generation(generation, 42)
    110         .expect("source generation bootstrap");
    111     assert_eq!(bootstrapped.source_generation(), Some(generation));
    112     assert_eq!(
    113         bootstrapped.source_generation_created_at_unix_ms(),
    114         Some(42)
    115     );
    116     assert!(matches!(
    117         OpenOptions::new(create.paths().clone(), OpenMode::Create)
    118             .with_source_generation(generation, 0),
    119         Err(Error::InvalidSourceGenerationTimestamp { actual: 0 })
    120     ));
    121     let beyond_sqlite_integer = u64::try_from(i64::MAX).expect("positive i64 maximum") + 1;
    122     assert!(matches!(
    123         OpenOptions::new(create.paths().clone(), OpenMode::Create)
    124             .with_source_generation(generation, beyond_sqlite_integer),
    125         Err(Error::InvalidSourceGenerationTimestamp { actual })
    126             if actual == beyond_sqlite_integer
    127     ));
    128 }