database_migration/
proptest_support.rs1use crate::checksum::Checksum;
2use crate::config::{DEFAULT_MIGRATIONS_FOLDER, MIGRATION_KEY_FORMAT_STR};
3use crate::definition::{
4 DOWN_SCRIPT_FILE_EXTENSION, SCRIPT_FILE_EXTENSION, UP_SCRIPT_FILE_EXTENSION,
5};
6use crate::migration::{Migration, MigrationKind};
7use chrono::{NaiveDate, NaiveDateTime, NaiveTime};
8use proptest::prelude::*;
9use proptest::string::string_regex;
10use std::path::PathBuf;
11
12pub fn any_checksum() -> impl Strategy<Value = Checksum> {
13 (0..=0x_FFFF_FFFF_u32).prop_map(Checksum)
14}
15
16#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
17fn days_in_month(year: i32, month: u32) -> u32 {
18 let current_month =
19 NaiveDate::from_ymd_opt(year, month, 1).expect("year or month out of range");
20 let (next_year, next_month) = match month {
21 12 => (year + 1, 1),
22 _ => (year, month + 1),
23 };
24 let next_month = NaiveDate::from_ymd_opt(next_year, next_month, 1)
25 .expect("next_year or next_month out of range");
26 next_month.signed_duration_since(current_month).num_days() as u32
27}
28
29pub fn any_key() -> impl Strategy<Value = NaiveDateTime> {
30 (1970..=9999, 1..=12_u32)
31 .prop_flat_map(|(year, month)| {
32 (
33 Just(year),
34 Just(month),
35 1..=days_in_month(year, month),
36 1..=23_u32,
37 1..=59_u32,
38 1..=59_u32,
39 )
40 })
41 .prop_map(|(year, month, day, hour, minute, second)| {
42 NaiveDateTime::new(
43 NaiveDate::from_ymd_opt(year, month, day).expect("year, month or day out of range"),
44 NaiveTime::from_hms_opt(hour, minute, second)
45 .expect("hour, minute or second out of range"),
46 )
47 })
48}
49
50pub fn any_title() -> impl Strategy<Value = String> {
51 string_regex(r"([\w][\w\-_ ]{0,200})?").expect("invalid regex for title")
52}
53
54pub fn any_migration_kind() -> impl Strategy<Value = MigrationKind> {
55 prop_oneof![
56 Just(MigrationKind::Up),
57 Just(MigrationKind::Down),
58 Just(MigrationKind::Baseline)
59 ]
60}
61
62pub fn any_direction() -> impl Strategy<Value = MigrationKind> {
63 prop_oneof![Just(MigrationKind::Up), Just(MigrationKind::Down),]
64}
65
66pub fn any_filename() -> impl Strategy<Value = String> {
67 (any_key(), any_title(), any_direction(), any::<bool>()).prop_map(
68 |(key, title, direction, include_direction)| {
69 let mut filename = key.format(MIGRATION_KEY_FORMAT_STR).to_string();
70 filename.push('_');
71 filename.push_str(&title);
72 match (include_direction, direction) {
73 (true, MigrationKind::Down) => filename.push_str(DOWN_SCRIPT_FILE_EXTENSION),
74 (true, _) => filename.push_str(UP_SCRIPT_FILE_EXTENSION),
75 (false, _) => filename.push_str(SCRIPT_FILE_EXTENSION),
76 }
77 filename
78 },
79 )
80}
81
82pub fn any_script_path() -> impl Strategy<Value = PathBuf> {
83 (
84 string_regex(r"/?[\w][\w\-_ ]{1,50}((/[\w])?[\w\-_ ]{1,50}){1,3}")
85 .expect("invalid regex for workdir path"),
86 any_filename(),
87 any::<bool>(),
88 )
89 .prop_map(|(workdir, filename, default_migrations_folder)| {
90 let mut path = PathBuf::from(workdir);
91 if default_migrations_folder {
92 path.push(DEFAULT_MIGRATIONS_FOLDER);
93 }
94 path.push(filename);
95 path
96 })
97}
98
99pub fn any_script_content() -> impl Strategy<Value = String> {
100 string_regex(r#"(([\w][\w\-_ \(\)\[\]\{\}#'"]){1,100};\n){1,12}"#)
101 .expect("invalid regex for script content")
102}
103
104pub fn any_migration() -> impl Strategy<Value = Migration> {
105 (any_key(), any_title(), any_direction(), any_script_path()).prop_map(
106 |(key, title, kind, script_path)| Migration {
107 key,
108 title,
109 kind,
110 script_path,
111 },
112 )
113}