forked from pisilinux-rs/yali-rs
fix(P1): kare başına alt süreçler, eksik i18n anahtarları, boş kullanıcı adı, GeoIP dil hatası
## 1. Kare başına alt süreç (3 yer)
- installer.rs: WelcomeStep::show her karede detect_existing_linux()
çağırıyordu → blkid -o list, yani saniyede ~60 tam blok aygıt
taraması. Sonuç rescue_available alanında bir kez hesaplanıyor.
- steps/network.rs: `nmcli dev status` show() içinde, kare başına.
status_text alanına alındı; on_enter'da ve tarama/bağlanma
sonrasında tazeleniyor.
## 2. Eksik çeviri anahtarları
- mp_duplicate_mountpoint: iki locale'de de YOKTU → 8633422'de
eklenen duplicate mountpoint kontrolü hata durumunda ekranda
çıplak anahtar gösteriyordu
- job_netinstall, job_setup_lvm: en.toml'da YOKTU → İngilizce
kurulumda çıplak anahtar
- username_empty: aşağıdaki boş kullanıcı adı düzeltmesi için
Doğrulama: 274 kullanılan anahtarın her ikisinde de karşılığı var
(0 eksik). job_* anahtarları t!(job.name()) ile dolaylı kullanılıyor,
doğrulama betiği bunları göremiyor ama mevcut.
## 3. Boş kullanıcı adı doğrulamadan geçiyordu (steps/users.rs)
validate_username("") None (hata yok) dönüyordu. Geçerli bir hostname
ile boş kullanıcı adı sihirbazı geçip CreateUserJob'a ulaşıyor ve
`useradd ""` çalıştırıyordu. Artık hata dönüyor.
username_empty anahtarı iki locale'e de eklendi.
## 4. GeoIP var olmayan dillere geçiyordu (steps/location.rs)
countryCode eşlemesi 8 ülkeyi (de/fr/it/es/ru/nl/pl) 8 dile
bağlıyordu ama locales/ altında yalnızca tr.toml ve en.toml var.
set_locale("de") hata vermez, sessizce kabul edilir ve t!() karşılığı
olmayan her anahtar için anahtarın KENDİSİNİ döndürür — Alman
kullanıcı tüm arayüzü "location_title", "next", "partition" gibi
çıplak metinlerle görüyordu.
- language_for_country() yalnızca gerçekten var olan dilleri döndürüyor
- funct::sanitize_locale() + AVAILABLE_LANGUAGES: set_locale
çağrılarından ÖNCE koruma. Desteklenmeyen kod sessizce yedeğe
düşüyor. 3 çağrı da korumaya alındı.
- ZA (Güney Afrika) bilinçli olarak listede değil: 11 resmî dili var
## Testler
steps/users.rs ve steps/location.rs ve funct.rs'te test modu yoktu.
Eklenenler:
users : 8 test (boş ad, geçerli adlar, yasaklı sistem adları,
başlangıç/karakter kuralları, hostname kuralları,
Türkçe transliterasyon, boş dönmezlik, uzunluk)
location: 4 test (her eşlemenin karşılığı olan locale dosyasına
sahip olduğunu doğrular — asıl regresyon testi)
funct : 3 test (locale koruması + yedeğin kendisi destekleniyor)
cargo check --all-targets : 0 hata, 0 uyarı
cargo test : 43/43 geçti (28 → 43)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -62,6 +62,7 @@ password_confirm_label = "Confirm password"
|
||||
hostname_label = "Computer name"
|
||||
username_invalid = "Invalid username. Must start with a lowercase letter or underscore, max 32 characters."
|
||||
username_too_long = "Username too long (maximum 32 characters)"
|
||||
username_empty = "Username cannot be empty"
|
||||
username_forbidden = "This username is reserved by the system and is forbidden"
|
||||
username_invalid_start = "Username must start with a lowercase letter or underscore"
|
||||
username_invalid_chars = "Only lowercase letters, digits, underscore and hyphen are allowed"
|
||||
@@ -146,6 +147,7 @@ mp_create_here = "Create Here"
|
||||
mp_will_delete = "will be deleted"
|
||||
mp_restore = "Restore"
|
||||
mp_free_space = "Free Space"
|
||||
mp_duplicate_mountpoint = "Two partitions share the same mount point; each partition must have a distinct mount point"
|
||||
mp_bar_now = "Current Layout"
|
||||
mp_bar_after = "After Installation"
|
||||
|
||||
@@ -216,6 +218,8 @@ job_install_packages = "Installing selected packages"
|
||||
job_ldconfig = "Configuring shared libraries (ldconfig)"
|
||||
job_update_env = "Updating system environment"
|
||||
job_cleanup_live = "Cleaning up live environment traces"
|
||||
job_netinstall = "Installing extra packages"
|
||||
job_setup_lvm = "Configuring LVM"
|
||||
|
||||
# ── Manual Partition UI ────────────────────────────────────
|
||||
manual_partition_title = "Manual Partitioning"
|
||||
|
||||
@@ -62,6 +62,7 @@ password_confirm_label = "Parola (tekrar)"
|
||||
hostname_label = "Bilgisayar adı"
|
||||
username_invalid = "Geçersiz kullanıcı adı. Küçük harf veya alt çizgi ile başlamalı, en fazla 32 karakter."
|
||||
username_too_long = "Kullanıcı adı çok uzun (maksimum 32 karakter)"
|
||||
username_empty = "Kullanıcı adı boş olamaz"
|
||||
username_forbidden = "Bu kullanıcı adı sistem tarafından kullanılıyor ve yasaklı"
|
||||
username_invalid_start = "Kullanıcı adı küçük harf veya alt çizgi ile başlamalı"
|
||||
username_invalid_chars = "Sadece küçük harf, rakam, alt çizgi ve tire kullanılabilir"
|
||||
@@ -122,6 +123,7 @@ mp_create_here = "Buraya Oluştur"
|
||||
mp_will_delete = "silinecek"
|
||||
mp_restore = "Geri Al"
|
||||
mp_free_space = "Boş Alan"
|
||||
mp_duplicate_mountpoint = "Aynı bağlama noktası iki bölüme birden verilmiş; her bölümün farklı bir bağlama noktası olmalı"
|
||||
mp_bar_now = "Mevcut Durum"
|
||||
mp_bar_after = "Kurulum Sonrası"
|
||||
|
||||
|
||||
+56
-1
@@ -1,5 +1,28 @@
|
||||
use eframe::egui;
|
||||
|
||||
/// Uygulamanın desteklediği diller (`locales/` altındaki dosyalarla eşleşir).
|
||||
///
|
||||
/// `locales/` klasörüne yeni bir dosya eklendiğinde buraya da eklenmelidir.
|
||||
pub const AVAILABLE_LANGUAGES: &[&str] = &["tr", "en"];
|
||||
|
||||
/// Yedek dil: desteklenmeyen bir kod gelirse kullanılır.
|
||||
pub const FALLBACK_LANGUAGE: &str = "en";
|
||||
|
||||
/// `rust_i18n::set_locale` çağrısından ÖNCE kullanılır.
|
||||
///
|
||||
/// Neden: `set_locale("de")` hata vermez, sessizce kabul eder ve `t!()`
|
||||
/// karşılığı olmayan her anahtar için anahtarın KENDİSİNİ döndürür —
|
||||
/// arayüz "location_title", "next", "partition" gibi çıplak metinlere
|
||||
/// dolar. O yüzden karar burada verilir: dil desteklenmiyorsa hiç
|
||||
/// değiştirilmez, kullanıcının seçimi korunur.
|
||||
pub fn sanitize_locale(locale: &str) -> &str {
|
||||
if AVAILABLE_LANGUAGES.contains(&locale) {
|
||||
locale
|
||||
} else {
|
||||
FALLBACK_LANGUAGE
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! draw_icon {
|
||||
($ui:expr, $path:literal, $size:expr, $color:expr) => {
|
||||
@@ -150,4 +173,36 @@ pub fn language_selector_ui(ui: &mut eframe::egui::Ui, state: &mut crate::instal
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn supported_locales_pass_through() {
|
||||
assert_eq!(sanitize_locale("tr"), "tr");
|
||||
assert_eq!(sanitize_locale("en"), "en");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unsupported_locales_fall_back() {
|
||||
// Bu diller için locale dosyası YOK. set_locage'e sızsaydı
|
||||
// arayüz çıplak anahtarlarla dolardı.
|
||||
for code in ["de", "fr", "it", "es", "ru", "nl", "pl", "xx", ""] {
|
||||
assert_eq!(
|
||||
sanitize_locale(code),
|
||||
FALLBACK_LANGUAGE,
|
||||
"{} desteklenmiyor, yedeğe düşmeli", code
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fallback_language_is_itself_supported() {
|
||||
// Yedeğin de desteklenmemesi durumunda sonsuz hata durumu oluşur.
|
||||
assert!(
|
||||
AVAILABLE_LANGUAGES.contains(&FALLBACK_LANGUAGE),
|
||||
"yedek dil {} desteklenen listede olmalı", FALLBACK_LANGUAGE
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+17
-4
@@ -399,6 +399,9 @@ pub struct WelcomeStep {
|
||||
checks: Vec<CheckResult>,
|
||||
checked: bool,
|
||||
last_language: String,
|
||||
/// Kurtarma modunun kullanılabilir olup olmadığı. `on_enter`'da bir kez
|
||||
/// hesaplanır — `blkid` her karede çalıştırılırsa UI kilitlenir.
|
||||
rescue_available: bool,
|
||||
}
|
||||
|
||||
impl Default for WelcomeStep {
|
||||
@@ -407,6 +410,7 @@ impl Default for WelcomeStep {
|
||||
checks: Vec::new(),
|
||||
checked: false,
|
||||
last_language: String::new(),
|
||||
rescue_available: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -416,8 +420,9 @@ impl InstallerStep for WelcomeStep {
|
||||
|
||||
fn on_enter(&mut self, state: &mut GlobalState) {
|
||||
if !self.checked || self.last_language != state.language {
|
||||
rust_i18n::set_locale(&state.language);
|
||||
rust_i18n::set_locale(crate::funct::sanitize_locale(&state.language));
|
||||
self.checks = run_all();
|
||||
self.rescue_available = detect_existing_linux();
|
||||
self.checked = true;
|
||||
self.last_language = state.language.clone();
|
||||
}
|
||||
@@ -426,9 +431,13 @@ impl InstallerStep for WelcomeStep {
|
||||
fn show(&mut self, ui: &mut egui::Ui, state: &mut GlobalState) {
|
||||
use crate::ui::theme;
|
||||
|
||||
// on_enter ile aynı koşul, aynı etki: dil değiştiyse kontrolleri ve
|
||||
// kurtarma kullanılabilirliğini tazele. (show() içinde yalnızca bu
|
||||
// senkron blok var; artık disk taraması yapmıyor.)
|
||||
if self.last_language != state.language {
|
||||
rust_i18n::set_locale(&state.language);
|
||||
rust_i18n::set_locale(crate::funct::sanitize_locale(&state.language));
|
||||
self.checks = run_all();
|
||||
self.rescue_available = detect_existing_linux();
|
||||
self.last_language = state.language.clone();
|
||||
}
|
||||
|
||||
@@ -568,8 +577,12 @@ impl InstallerStep for WelcomeStep {
|
||||
.inner_margin(egui::Margin::symmetric(8.0, 4.0))
|
||||
.show(ui, |ui| {
|
||||
ui.horizontal(|ui| {
|
||||
let rescue_available = detect_existing_linux();
|
||||
if rescue_available {
|
||||
// `detect_existing_linux()` bir `blkid -o list` süreci
|
||||
// başlatır — TÜM blok aygıtlarını tarar. Bunu çizim
|
||||
// (show) içinde çağırmak saniyede ~60 kez süreç
|
||||
// doğuruyor, yani kare hızında tam disk taraması.
|
||||
// Sonuç `on_enter`'da bir kez hesaplanıp saklanıyor.
|
||||
if self.rescue_available {
|
||||
if theme::light_gray_btn(ui, &t!("rescue_button")).clicked() {
|
||||
state.rescue_mode = true;
|
||||
}
|
||||
|
||||
+1
-1
@@ -295,7 +295,7 @@ impl Default for YaliApp {
|
||||
|
||||
impl eframe::App for YaliApp {
|
||||
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
||||
rust_i18n::set_locale(&self.state.language);
|
||||
rust_i18n::set_locale(crate::funct::sanitize_locale(&self.state.language));
|
||||
|
||||
// Dil değişimi kontrolü -> branding dosyasını dinamik olarak yeniden yükle
|
||||
if self.loaded_language != self.state.language {
|
||||
|
||||
+80
-10
@@ -97,6 +97,31 @@ impl Default for LocationStep {
|
||||
}
|
||||
|
||||
impl LocationStep {
|
||||
/// Ülke kodundan dil koduna çevirir.
|
||||
///
|
||||
/// YALIDIR: yalnızca `locales/` altında GERÇEKTEN bulunan diller
|
||||
/// döndürür. Daha önce 8 ülke eşleniyordu (de/fr/it/es/ru/nl/pl) ama
|
||||
/// projede yalnızca tr.toml ve en.toml var. `set_locale("de")`
|
||||
/// çağrıldığında rust-i18n sessizce anahtarın kendisini döndürür —
|
||||
/// yani Alman kullanıcı "location_title", "next", "partition" gibi
|
||||
/// çıplak anahtarlarla dolu bir arayüz görüyordu.
|
||||
fn language_for_country(country: &str) -> Option<&'static str> {
|
||||
match country {
|
||||
"TR" => Some("tr"),
|
||||
// AB'nin İngilizce konuşulan bölümleri ve İngilizce resmî
|
||||
// dili olan ülkeler. Bunlar en.toml'a düşer.
|
||||
//
|
||||
// Not: Güney Afrika (ZA) bilinçli olarak listede değil — 11
|
||||
// resmî dili var ve İngilizce baskın değil. Benzer durumdaki
|
||||
// ülkeler de (Kanada, İsviçre) yalnızca İngilizce resmî dile
|
||||
// sahip oldukları için listede.
|
||||
"GB" | "IE" | "MT" | "US" | "CA" | "AU" | "NZ" | "IN" | "SG" |
|
||||
"NG" | "KE" | "GH" | "HK" | "JM" | "BS" | "BB" |
|
||||
"BZ" | "GY" | "TT" | "BW" | "ZW" | "ZM" | "FJI" => Some("en"),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn geoip_lookup(state: &mut GlobalState) {
|
||||
let out = std::process::Command::new("curl")
|
||||
.args(["-s", "--connect-timeout", "3", "http://ip-api.com/json"])
|
||||
@@ -111,16 +136,10 @@ impl LocationStep {
|
||||
})
|
||||
};
|
||||
if let Some(country) = extract("countryCode") {
|
||||
match country.as_str() {
|
||||
"TR" => state.language = "tr".to_string(),
|
||||
"DE" => state.language = "de".to_string(),
|
||||
"FR" => state.language = "fr".to_string(),
|
||||
"IT" => state.language = "it".to_string(),
|
||||
"ES" => state.language = "es".to_string(),
|
||||
"RU" => state.language = "ru".to_string(),
|
||||
"NL" => state.language = "nl".to_string(),
|
||||
"PL" => state.language = "pl".to_string(),
|
||||
_ => {}
|
||||
// Mevcut locale dosyası olmayan bir dile geçilmezse
|
||||
// kullanıcının seçtiği dil korunur.
|
||||
if let Some(lang) = Self::language_for_country(&country) {
|
||||
state.language = lang.to_string();
|
||||
}
|
||||
}
|
||||
if let Some(tz) = extract("timezone") {
|
||||
@@ -347,3 +366,54 @@ impl InstallerStep for LocationStep {
|
||||
!state.timezone.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn geoip_only_maps_to_locales_that_exist() {
|
||||
// Regresyon: de/fr/it/es/ru/nl/pl eşleniyordu ama locales/ altında
|
||||
// yalnızca tr.toml ve en.toml var. set_locale("de") sessizce
|
||||
// kabul edildiği için Alman kullanıcı tüm arayüzü çıplak
|
||||
// anahtar olarak görüyordu.
|
||||
for code in ["DE", "FR", "IT", "ES", "RU", "NL", "PL"] {
|
||||
let mapped = LocationStep::language_for_country(code);
|
||||
if let Some(lang) = mapped {
|
||||
assert!(
|
||||
crate::funct::AVAILABLE_LANGUAGES.contains(&lang),
|
||||
"{} -> {} eşlendi ama {} locale dosyası yok",
|
||||
code, lang, lang
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn turkey_maps_to_turkish() {
|
||||
assert_eq!(LocationStep::language_for_country("TR"), Some("tr"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn english_speaking_countries_map_to_english() {
|
||||
for code in ["GB", "US", "IE", "AU", "CA", "NZ", "IN"] {
|
||||
assert_eq!(
|
||||
LocationStep::language_for_country(code),
|
||||
Some("en"),
|
||||
"{} İngilizce'ye eşlenmeli", code
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unknown_countries_leave_language_alone() {
|
||||
// Eşleme yok → None → kullanıcının seçtiği dil korunur.
|
||||
for code in ["JP", "CN", "BR", "ZA", "XX", ""] {
|
||||
assert_eq!(
|
||||
LocationStep::language_for_country(code),
|
||||
None,
|
||||
"{} eşlenmemeli", code
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+44
-15
@@ -9,6 +9,9 @@ pub struct NetworkStep {
|
||||
scanning: bool,
|
||||
scan_done: bool,
|
||||
proxy_enabled: bool,
|
||||
/// `nmcli dev status` çıktısı. Adıma girişte bir kez okunur —
|
||||
/// show() içinde okunursa her karede alt süreç doğar.
|
||||
status_text: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
@@ -66,6 +69,15 @@ impl NetworkStep {
|
||||
Err(String::from_utf8_lossy(&out.stderr).trim().to_string())
|
||||
}
|
||||
}
|
||||
/// `nmcli dev status` çıktısını tazeler. Adıma girişte ve Wi-Fi
|
||||
/// bağlantı denemelerinden sonra çağrılır.
|
||||
fn refresh_status(&mut self) {
|
||||
self.status_text = std::process::Command::new("nmcli")
|
||||
.args(["-t", "-f", "DEVICE,TYPE,STATE,CONNECTION", "dev", "status"])
|
||||
.output()
|
||||
.ok()
|
||||
.map(|out| String::from_utf8_lossy(&out.stdout).into_owned());
|
||||
}
|
||||
}
|
||||
|
||||
impl InstallerStep for NetworkStep {
|
||||
@@ -73,28 +85,41 @@ impl InstallerStep for NetworkStep {
|
||||
t!("network").to_string()
|
||||
}
|
||||
|
||||
fn on_enter(&mut self, _state: &mut GlobalState) {
|
||||
self.refresh_status();
|
||||
}
|
||||
|
||||
fn show(&mut self, ui: &mut egui::Ui, state: &mut GlobalState) {
|
||||
ui.heading(t!("network_title"));
|
||||
ui.label(t!("network_description"));
|
||||
ui.add_space(12.0);
|
||||
|
||||
// Mevcut bağlantı durumu
|
||||
if let Ok(out) = std::process::Command::new("nmcli")
|
||||
.args(["-t", "-f", "DEVICE,TYPE,STATE,CONNECTION", "dev", "status"])
|
||||
.output()
|
||||
{
|
||||
let text = String::from_utf8_lossy(&out.stdout);
|
||||
let connected: Vec<&str> = text.lines()
|
||||
.filter(|l| l.contains(":connected:") || l.contains(":connecting:"))
|
||||
.collect();
|
||||
if !connected.is_empty() {
|
||||
//
|
||||
// `nmcli dev status` bir alt süreçtir ve ~50-100 ms sürer. Burada
|
||||
// show() içinde, yani saniyede ~60 kez çağrılıyordu. Durum
|
||||
// adımlara girişte bir kez okunur; bağlantı değişikliği
|
||||
// `scan_wifi`/`connect` sonrası elle tazelenir.
|
||||
match &self.status_text {
|
||||
Some(text) => {
|
||||
let connected: Vec<&str> = text.lines()
|
||||
.filter(|l| l.contains(":connected:") || l.contains(":connecting:"))
|
||||
.collect();
|
||||
if !connected.is_empty() {
|
||||
ui.colored_label(
|
||||
egui::Color32::from_rgb(80, 170, 80),
|
||||
format!("{} {}", t!("network_connected"), connected.join(", ")),
|
||||
);
|
||||
} else {
|
||||
ui.colored_label(
|
||||
egui::Color32::from_rgb(200, 140, 40),
|
||||
t!("network_disconnected"),
|
||||
);
|
||||
}
|
||||
}
|
||||
None => {
|
||||
ui.colored_label(
|
||||
egui::Color32::from_rgb(80, 170, 80),
|
||||
format!("{} {}", t!("network_connected"), connected.join(", ")),
|
||||
);
|
||||
} else {
|
||||
ui.colored_label(
|
||||
egui::Color32::from_rgb(200, 140, 40),
|
||||
egui::Color32::from_rgb(120, 120, 140),
|
||||
t!("network_disconnected"),
|
||||
);
|
||||
}
|
||||
@@ -128,6 +153,7 @@ impl InstallerStep for NetworkStep {
|
||||
if !self.scan_done {
|
||||
if ui.add(egui::Button::new(t!("network_scan"))).clicked() {
|
||||
self.scan_wifi();
|
||||
self.refresh_status();
|
||||
}
|
||||
} else {
|
||||
ui.horizontal(|ui| {
|
||||
@@ -185,6 +211,9 @@ impl InstallerStep for NetworkStep {
|
||||
Ok(msg) => {
|
||||
state.install_log.push(format!("Wi-Fi: {} — {}", net.ssid, msg));
|
||||
self.scan_done = false;
|
||||
// Bağlantı durumu değişti; üstteki
|
||||
// "bağlı/bağlı değil" satırı tazelensin.
|
||||
self.refresh_status();
|
||||
}
|
||||
Err(e) => {
|
||||
state.install_log.push(format!("Wi-Fi error: {}", e));
|
||||
|
||||
+84
-1
@@ -66,7 +66,10 @@ fn derive_username(real_name: &str) -> String {
|
||||
/// Hata varsa hata mesajını döndürür, yoksa None döndürür.
|
||||
fn validate_username(s: &str) -> Option<String> {
|
||||
if s.is_empty() {
|
||||
return None;
|
||||
// Boş kullanıcı adı GEÇERLİ DEĞİLDİR. Daha önce burada `None`
|
||||
// (hata yok) dönüyordu, bu yüzden geçerli bir hostname ile boş
|
||||
// kullanıcı adı sihirbazı geçip `useradd ""` çalıştırıyordu.
|
||||
return Some(t!("username_empty").to_string());
|
||||
}
|
||||
if s.len() > 32 {
|
||||
return Some(t!("username_too_long").to_string());
|
||||
@@ -425,3 +428,83 @@ impl InstallerStep for UsersStep {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn empty_username_is_rejected() {
|
||||
// Regresyon: validate_username("") None (hata yok) dönüyordu, bu
|
||||
// yüzden geçerli bir hostname ile boş kullanıcı adı sihirbazı
|
||||
// geçip CreateUserJob'a ulaşıyordu.
|
||||
assert!(
|
||||
validate_username("").is_some(),
|
||||
"boş kullanıcı adı hata dönmeli"
|
||||
);
|
||||
assert!(!is_valid_username(""), "boş kullanıcı adı geçerli olmamalı");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn valid_usernames_pass() {
|
||||
for name in ["user", "ahmet", "a", "_root_", "user-1", "u_2026"] {
|
||||
assert!(
|
||||
validate_username(name).is_none(),
|
||||
"{} geçerli olmalı", name
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn system_usernames_are_forbidden() {
|
||||
for name in ["root", "daemon", "nobody", "sshd", "www-data"] {
|
||||
assert!(
|
||||
validate_username(name).is_some(),
|
||||
"{} yasaklı olmalı", name
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn username_start_and_charset_rules() {
|
||||
assert!(validate_username("1user").is_some(), "rakamla başlayamaz");
|
||||
assert!(validate_username("Auser").is_some(), "büyük harfle başlayamaz");
|
||||
assert!(validate_username("user name").is_some(), "boşluk olmaz");
|
||||
assert!(validate_username(&"a".repeat(33)).is_some(), "32'den uzun olmaz");
|
||||
assert!(validate_username(&"a".repeat(32)).is_none(), "32 kabul edilir");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hostname_rules() {
|
||||
assert!(is_valid_hostname("pisi-pc"));
|
||||
assert!(is_valid_hostname("Pisi1"));
|
||||
assert!(!is_valid_hostname(""), "boş olmaz");
|
||||
assert!(!is_valid_hostname("-pisi"), "tire ile başlayamaz");
|
||||
assert!(!is_valid_hostname("pisi-"), "tire ile bitemez");
|
||||
assert!(!is_valid_hostname("pisi pc"), "boşluk olmaz");
|
||||
assert!(!is_valid_hostname("pisi_pc"), "alt çizgi olmaz");
|
||||
assert!(!is_valid_hostname(&"a".repeat(64)), "63'ten uzun olmaz");
|
||||
assert!(is_valid_hostname(&"a".repeat(63)), "63 kabul edilir");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn derive_username_transliterates_turkish() {
|
||||
assert_eq!(derive_username("Şükrü Çelik"), "sukru-celik");
|
||||
assert_eq!(derive_username("Çağrı"), "cagri");
|
||||
assert_eq!(derive_username("İpek"), "ipek");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn derive_username_never_returns_empty() {
|
||||
// Yalnızca geçersiz karakterlerden oluşan isimde boş dönmemeli.
|
||||
assert_eq!(derive_username("!!!"), "user");
|
||||
assert_eq!(derive_username(" "), "user");
|
||||
assert_eq!(derive_username(""), "user");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn derive_username_caps_length() {
|
||||
let long = "a".repeat(50);
|
||||
assert_eq!(derive_username(&long).len(), 32);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user