use proptest::prelude::*;
use regex::Regex;
const MAX_MEMORY_NAME_LEN: usize = 80;
const MAX_MEMORY_BODY_LEN: usize = 20_000;
const NAME_SLUG_REGEX: &str = r"^[a-z][a-z0-9-]{0,78}[a-z0-9]$|^[a-z0-9]$";
fn proptest_config() -> ProptestConfig {
let cases = std::env::var("PROPTEST_CASES")
.ok()
.and_then(|v| v.parse::<u32>().ok())
.unwrap_or(256);
ProptestConfig::with_cases(cases)
}
proptest! {
#![proptest_config(proptest_config())]
#[test]
fn name_slug_regex_aceita_kebab_case(
name in "[a-z][a-z0-9-]{0,78}[a-z0-9]"
) {
let re = Regex::new(NAME_SLUG_REGEX).unwrap();
prop_assert!(
re.is_match(&name),
"Nome kebab valido rejeitado: {:?}",
name
);
}
#[test]
fn name_slug_regex_aceita_char_unico_minusculo(
c in "[a-z0-9]"
) {
let re = Regex::new(NAME_SLUG_REGEX).unwrap();
prop_assert!(
re.is_match(&c),
"Char unico valido rejeitado: {:?}",
c
);
}
#[test]
fn name_slug_regex_rejeita_uppercase(
upper in "[A-Z]{1,5}[a-z0-9-]{0,10}"
) {
let re = Regex::new(NAME_SLUG_REGEX).unwrap();
prop_assert!(
!re.is_match(&upper),
"Uppercase incorretamente aceito: {:?}",
upper
);
}
#[test]
fn name_slug_regex_rejeita_underscore(
prefix in "[a-z]{1,10}",
suffix in "[a-z]{1,10}"
) {
let nome_com_underscore = format!("{prefix}_{suffix}");
let re = Regex::new(NAME_SLUG_REGEX).unwrap();
prop_assert!(
!re.is_match(&nome_com_underscore),
"Underscore incorretamente aceito: {:?}",
nome_com_underscore
);
}
#[test]
fn name_slug_regex_rejeita_espaco(
a in "[a-z]{1,10}",
b in "[a-z]{1,10}"
) {
let nome_com_espaco = format!("{a} {b}");
let re = Regex::new(NAME_SLUG_REGEX).unwrap();
prop_assert!(
!re.is_match(&nome_com_espaco),
"Espaco incorretamente aceito: {:?}",
nome_com_espaco
);
}
#[test]
fn body_length_boundary_unicode_acima_do_limite(
extra in "[\\p{L}]{1,500}"
) {
let padding: String = "a".repeat(MAX_MEMORY_BODY_LEN);
let body = format!("{padding}{extra}");
prop_assert!(
body.chars().count() > MAX_MEMORY_BODY_LEN,
"Body deveria exceder limite mas tem {} chars",
body.chars().count()
);
}
#[test]
fn body_length_boundary_unicode_no_limite(
chars in "[\\p{L}\\p{N}]{1,20000}"
) {
let truncated: String = chars.chars().take(MAX_MEMORY_BODY_LEN).collect();
prop_assert!(
truncated.chars().count() <= MAX_MEMORY_BODY_LEN,
"Truncado deveria ser <= {} mas tem {} chars",
MAX_MEMORY_BODY_LEN,
truncated.chars().count()
);
}
#[test]
fn name_comprimento_valido_dentro_do_limite(
name in "[a-z][a-z0-9-]{0,78}[a-z0-9]"
) {
prop_assert!(
!name.is_empty() && name.len() <= MAX_MEMORY_NAME_LEN,
"Nome {:?} tem comprimento {} fora do range [1, {}]",
name,
name.len(),
MAX_MEMORY_NAME_LEN
);
}
#[test]
fn embedding_determinism_blake3_mesmo_hash_para_mesmo_input(
body in "[\\p{L}\\p{N} .,!?]{1,1000}"
) {
let hash_a = blake3::hash(body.as_bytes());
let hash_b = blake3::hash(body.as_bytes());
prop_assert_eq!(
hash_a,
hash_b,
"BLAKE3 nao e determinístico para input {:?}",
&body[..body.len().min(40)]
);
}
#[test]
fn embedding_determinism_blake3_inputs_distintos_hashes_distintos(
a in "[a-z]{10,50}",
b in "[A-Z]{10,50}"
) {
let hash_a = blake3::hash(a.as_bytes());
let hash_b = blake3::hash(b.as_bytes());
prop_assert_ne!(
hash_a,
hash_b,
"Colisão de BLAKE3 inesperada entre {:?} e {:?}",
a,
b
);
}
#[test]
fn json_round_trip_nome_descricao_body(
name in "[a-z][a-z0-9-]{0,30}[a-z0-9]",
description in "[\\p{L}\\p{N} .,!?]{1,200}",
body in "[\\p{L}\\p{N} .,!?\n]{1,500}"
) {
#[derive(serde::Serialize, serde::Deserialize, Debug, PartialEq)]
struct Payload {
name: String,
description: String,
body: String,
}
let original = Payload {
name: name.clone(),
description: description.clone(),
body: body.clone(),
};
let json = serde_json::to_string(&original).unwrap();
let restored: Payload = serde_json::from_str(&json).unwrap();
prop_assert_eq!(
&original,
&restored,
"Round-trip JSON falhou para nome={:?}",
name
);
}
}
#[cfg(test)]
mod testes_unitarios {
use super::*;
#[test]
fn name_slug_regex_aceita_exemplos_canonicos() {
let re = Regex::new(NAME_SLUG_REGEX).unwrap();
let validos = [
"a",
"z",
"0",
"abc",
"my-memory",
"projeto-rust-2026",
"a0",
"a-b-c",
&"a".repeat(79)
.as_str()
.chars()
.chain(std::iter::once('b'))
.collect::<String>(),
];
for nome in &validos {
assert!(re.is_match(nome), "Nome canonico rejeitado: {nome:?}");
}
}
#[test]
fn name_slug_regex_rejeita_exemplos_invalidos() {
let re = Regex::new(NAME_SLUG_REGEX).unwrap();
let invalidos = [
"",
"A",
"My-Memory",
"my_memory",
"my memory",
"-starts-with-dash",
"ends-with-dash-",
"__reserved",
];
for nome in &invalidos {
assert!(
!re.is_match(nome),
"Nome invalido incorretamente aceito: {nome:?}"
);
}
}
#[test]
fn blake3_hash_bytes_tem_32_bytes() {
let h = blake3::hash(b"sqlite-graphrag");
assert_eq!(h.as_bytes().len(), 32);
}
#[test]
fn body_limite_exato_aceito() {
let body: String = "x".repeat(MAX_MEMORY_BODY_LEN);
assert_eq!(body.chars().count(), MAX_MEMORY_BODY_LEN);
}
#[test]
fn body_um_acima_do_limite_detectado() {
let body: String = "x".repeat(MAX_MEMORY_BODY_LEN + 1);
assert!(body.chars().count() > MAX_MEMORY_BODY_LEN);
}
}