Compare commits

...

2 Commits

3 changed files with 22 additions and 375 deletions

View File

@ -92,7 +92,7 @@ impl DBOperator {
} }
} }
pub async fn update_equipment(&self, equipment: Equipment) -> Result<bool, sqlx::Error> { pub async fn update_equipment(&self, equipment: Equipment) -> Result<bool, sqlx::Error> {
match sqlx::query("UPDATE Brewery.equipment SET inv_number=?, maintenance_date=?, worker_id=?, name=?, is_written_off=? WHERE id=?;") match sqlx::query("UPDATE `equipment` SET inv_number=?, maintenance_date=?, worker_id=?, name=?, is_written_off=? WHERE id=?;")
.bind(equipment.inv_number) .bind(equipment.inv_number)
.bind(equipment.maintenance_date) .bind(equipment.maintenance_date)
.bind(equipment.worker.id) .bind(equipment.worker.id)
@ -149,7 +149,7 @@ impl DBOperator {
} }
pub async fn add_worker(&self, worker: WorkerRow) -> Result<bool, sqlx::Error> { pub async fn add_worker(&self, worker: WorkerRow) -> Result<bool, sqlx::Error> {
// sqlx::query!("INSERT INTO Brewery.worker (id, position_id, hire_date, is_fired, full_name) VALUES(0, ?, ?, ?, ?);",worker.position.id, worker.hire_date ,worker.is_fired,worker.full_name) // sqlx::query!("INSERT INTO Brewery.worker (id, position_id, hire_date, is_fired, full_name) VALUES(0, ?, ?, ?, ?);",worker.position.id, worker.hire_date ,worker.is_fired,worker.full_name)
match sqlx::query("INSERT INTO Brewery.worker (id, position_id, hire_date, is_fired, full_name) VALUES(0, ?, ?, ?, ?);") match sqlx::query("INSERT INTO `worker` (id, position_id, hire_date, is_fired, full_name) VALUES(0, ?, ?, ?, ?);")
.bind(worker.position_id) .bind(worker.position_id)
.bind(worker.hire_date) .bind(worker.hire_date)
.bind(worker.is_fired) .bind(worker.is_fired)
@ -165,7 +165,7 @@ impl DBOperator {
} }
} }
pub async fn add_position(&self, position: Position) -> Result<bool, sqlx::Error> { pub async fn add_position(&self, position: Position) -> Result<bool, sqlx::Error> {
match sqlx::query("INSERT INTO Brewery.`position` (id, name, wage) VALUES(0, ?, ?);") match sqlx::query("INSERT INTO `position` (id, name, wage) VALUES(0, ?, ?);")
.bind(position.name) .bind(position.name)
.bind(position.wage) .bind(position.wage)
.execute(&self.pool) .execute(&self.pool)
@ -190,7 +190,7 @@ impl DBOperator {
} }
pub async fn add_material(&self, mat: MaterialRow) -> Result<bool, sqlx::Error> { pub async fn add_material(&self, mat: MaterialRow) -> Result<bool, sqlx::Error> {
match sqlx::query( match sqlx::query(
"INSERT INTO Brewery.material (id, name, quantity, category_id) VALUES(0, ?, ?, ?);", "INSERT INTO `material` (id, name, quantity, category_id) VALUES(0, ?, ?, ?);",
) )
.bind(mat.name) .bind(mat.name)
.bind(mat.quantity) .bind(mat.quantity)
@ -203,7 +203,7 @@ impl DBOperator {
} }
} }
pub async fn delete_position(&self, position: Position) -> Result<bool, sqlx::Error> { pub async fn delete_position(&self, position: Position) -> Result<bool, sqlx::Error> {
match sqlx::query("DELETE FROM Brewery.`position` WHERE id = ?;") match sqlx::query("DELETE FROM `position` WHERE id = ?;")
.bind(position.id) .bind(position.id)
.execute(&self.pool) .execute(&self.pool)
.await .await
@ -216,7 +216,7 @@ impl DBOperator {
&self, &self,
mat_cat: MaterialCategory, mat_cat: MaterialCategory,
) -> Result<bool, sqlx::Error> { ) -> Result<bool, sqlx::Error> {
match sqlx::query("DELETE FROM Brewery.`material_category` WHERE id = ?;") match sqlx::query("DELETE FROM `material_category` WHERE id = ?;")
.bind(mat_cat.id) .bind(mat_cat.id)
.execute(&self.pool) .execute(&self.pool)
.await .await
@ -226,7 +226,7 @@ impl DBOperator {
} }
} }
pub async fn delete_material(&self, mat: MaterialRow) -> Result<bool, sqlx::Error> { pub async fn delete_material(&self, mat: MaterialRow) -> Result<bool, sqlx::Error> {
match sqlx::query("DELETE FROM Brewery.`material` WHERE id = ?;") match sqlx::query("DELETE FROM `material` WHERE id = ?;")
.bind(mat.id) .bind(mat.id)
.execute(&self.pool) .execute(&self.pool)
.await .await
@ -237,7 +237,7 @@ impl DBOperator {
} }
pub async fn update_worker(&self, worker: WorkerRow) -> Result<bool, sqlx::Error> { pub async fn update_worker(&self, worker: WorkerRow) -> Result<bool, sqlx::Error> {
match sqlx::query("UPDATE Brewery.worker SET position_id=?, hire_date=?, is_fired=?, full_name=? WHERE id=?;") match sqlx::query("UPDATE `worker` SET position_id=?, hire_date=?, is_fired=?, full_name=? WHERE id=?;")
.bind(worker.position_id) .bind(worker.position_id)
.bind(worker.hire_date) .bind(worker.hire_date)
.bind(worker.is_fired) .bind(worker.is_fired)
@ -257,7 +257,7 @@ impl DBOperator {
&self, &self,
mat_cat: MaterialCategory, mat_cat: MaterialCategory,
) -> Result<bool, sqlx::Error> { ) -> Result<bool, sqlx::Error> {
match sqlx::query("UPDATE Brewery.`material_category` SET name=? WHERE id=?;") match sqlx::query("UPDATE `material_category` SET name=? WHERE id=?;")
.bind(mat_cat.name) .bind(mat_cat.name)
.bind(mat_cat.id) .bind(mat_cat.id)
.execute(&self.pool) .execute(&self.pool)
@ -269,7 +269,7 @@ impl DBOperator {
} }
pub async fn update_material(&self, mat: MaterialRow) -> Result<bool, sqlx::Error> { pub async fn update_material(&self, mat: MaterialRow) -> Result<bool, sqlx::Error> {
match sqlx::query( match sqlx::query(
"UPDATE Brewery.material SET name=?, quantity=?, category_id=? WHERE id=?;", "UPDATE `material` SET name=?, quantity=?, category_id=? WHERE id=?;",
) )
.bind(mat.name) .bind(mat.name)
.bind(mat.quantity) .bind(mat.quantity)
@ -303,7 +303,7 @@ impl DBOperator {
} }
pub async fn update_position(&self, pos: Position) -> Result<bool, sqlx::Error> { pub async fn update_position(&self, pos: Position) -> Result<bool, sqlx::Error> {
match sqlx::query("UPDATE Brewery.position SET name=?, wage=? WHERE id=?") match sqlx::query("UPDATE `position`SET name=?, wage=? WHERE id=?")
.bind(pos.name) .bind(pos.name)
.bind(pos.wage) .bind(pos.wage)
.bind(pos.id) .bind(pos.id)
@ -319,7 +319,7 @@ impl DBOperator {
username: String, username: String,
password_hash: String, password_hash: String,
) -> Result<bool, sqlx::Error> { ) -> Result<bool, sqlx::Error> {
let ans = sqlx::query("SELECT * FROM Brewery.profile WHERE login=? AND password=?") let ans = sqlx::query("SELECT * FROM `profile` WHERE login=? AND password=?")
.bind(username) .bind(username)
.bind(password_hash) .bind(password_hash)
.fetch_all(&self.pool) .fetch_all(&self.pool)
@ -344,11 +344,11 @@ impl DBOperator {
profile.login, profile.login,
position.id position.id
FROM FROM
Brewery.profile `profile`
JOIN JOIN
Brewery.worker `worker`
JOIN JOIN
Brewery.position `position`
ON profile.worker_id = worker.id ON profile.worker_id = worker.id
AND profile.login=? AND profile.login=?
AND worker.position_id =position.id"#, AND worker.position_id =position.id"#,
@ -397,7 +397,7 @@ impl DBOperator {
salary_size: BigDecimal, salary_size: BigDecimal,
comment: Option<String>, comment: Option<String>,
) -> Result<bool, sqlx::Error> { ) -> Result<bool, sqlx::Error> {
match sqlx::query("INSERT INTO Brewery.salary (id, worker_id, salary_size, salary_date, comment) VALUES(0, ?, ?, ?, ?);") match sqlx::query("INSERT INTO `salary` (id, worker_id, salary_size, salary_date, comment) VALUES(0, ?, ?, ?, ?);")
.bind(worker_id) .bind(worker_id)
.bind(salary_size) .bind(salary_size)
.bind(salary_date) .bind(salary_date)
@ -409,7 +409,7 @@ impl DBOperator {
} }
} }
pub async fn update_salary(&self, salary: Salary) -> Result<bool, sqlx::Error> { pub async fn update_salary(&self, salary: Salary) -> Result<bool, sqlx::Error> {
match sqlx::query("UPDATE Brewery.salary SET worker_id=?, salary_size=?, salary_date=?, comment=? WHERE id=?;") match sqlx::query("UPDATE `salary` SET worker_id=?, salary_size=?, salary_date=?, comment=? WHERE id=?;")
.bind(salary.worker.id) .bind(salary.worker.id)
.bind(salary.salary_size) .bind(salary.salary_size)
.bind(salary.salary_date) .bind(salary.salary_date)
@ -422,7 +422,7 @@ impl DBOperator {
} }
} }
pub async fn remove_salary(&self, id: i32) -> Result<bool, sqlx::Error> { pub async fn remove_salary(&self, id: i32) -> Result<bool, sqlx::Error> {
match sqlx::query("DELETE FROM Brewery.salary WHERE id=?;") match sqlx::query("DELETE FROM `salary` WHERE id=?;")
.bind(id) .bind(id)
.execute(&self.pool) .execute(&self.pool)
.await .await
@ -432,7 +432,7 @@ impl DBOperator {
} }
} }
pub async fn get_recipes(&self) -> Result<Vec<Recipe>, sqlx::Error> { pub async fn get_recipes(&self) -> Result<Vec<Recipe>, sqlx::Error> {
let pre_ret = sqlx::query_as::<_, RecipeRow>("SELECT * FROM recipe") let pre_ret = sqlx::query_as::<_, RecipeRow>("SELECT * FROM `recipe`")
.fetch_all(&self.pool) .fetch_all(&self.pool)
.await .await
.unwrap_or(Vec::new()); .unwrap_or(Vec::new());
@ -480,7 +480,7 @@ impl DBOperator {
} }
} }
pub async fn update_recipe(&self, recipe: Recipe) -> Result<bool, sqlx::Error> { pub async fn update_recipe(&self, recipe: Recipe) -> Result<bool, sqlx::Error> {
match sqlx::query("UPDATE Brewery.recipe SET name=? WHERE id=?;") match sqlx::query("UPDATE `recipe` SET name=? WHERE id=?;")
.bind(recipe.name) .bind(recipe.name)
.bind(recipe.id) .bind(recipe.id)
.execute(&self.pool) .execute(&self.pool)
@ -491,7 +491,7 @@ impl DBOperator {
} }
} }
pub async fn get_recipe_elements(&self) -> Result<Vec<RecipeElement>, sqlx::Error> { pub async fn get_recipe_elements(&self) -> Result<Vec<RecipeElement>, sqlx::Error> {
let pre_ret = sqlx::query_as::<_, RecipeElementRow>("SELECT id, recipe_id, material_id, equipment_id, quantity FROM Brewery.recipe_element;").fetch_all(&self.pool).await; let pre_ret = sqlx::query_as::<_, RecipeElementRow>("SELECT id, recipe_id, material_id, equipment_id, quantity FROM `recipe_element`;").fetch_all(&self.pool).await;
let mats = self.get_materials().await.unwrap(); let mats = self.get_materials().await.unwrap();
let eqs = self.get_equipment().await.unwrap(); let eqs = self.get_equipment().await.unwrap();

View File

@ -1,353 +0,0 @@
#[cfg(test)]
mod tests {
use super::database::DBOperator;
use crate::models::*;
use bigdecimal::BigDecimal;
use chrono::Local;
use std::collections::HashSet;
// ============================================================
// UNIT TESTS (no database required)
// ============================================================
/// Test 1: Position default values and construction
#[test]
fn test_position_default_and_construction() {
let default_pos = Position::default();
assert_eq!(default_pos.id, 1);
assert_eq!(default_pos.name, "");
assert_eq!(default_pos.wage, BigDecimal::from(0));
let custom_pos = Position {
id: 42,
name: "Пивовар".to_string(),
wage: BigDecimal::from(65000),
};
assert_eq!(custom_pos.id, 42);
assert_eq!(custom_pos.name, "Пивовар");
assert_eq!(custom_pos.wage, BigDecimal::from(65000));
}
/// Test 2: Worker default values and construction with nested Position
#[test]
fn test_worker_default_and_construction() {
let default_worker = Worker::default();
assert_eq!(default_worker.id, 0);
assert_eq!(default_worker.full_name, "");
assert!(!default_worker.is_fired);
let pos = Position {
id: 3,
name: "Пивовар".to_string(),
wage: BigDecimal::from(65000),
};
let worker = Worker {
id: 1,
full_name: "Иванов Иван".to_string(),
hire_date: Local::now(),
position: pos.clone(),
is_fired: false,
};
assert_eq!(worker.full_name, "Иванов Иван");
assert_eq!(worker.position.name, "Пивовар");
assert_eq!(worker.position.wage, BigDecimal::from(65000));
assert!(!worker.is_fired);
}
/// Test 3: MaterialCategory default and Material construction with category
#[test]
fn test_material_and_category_construction() {
let default_cat = MaterialCategory::default();
assert_eq!(default_cat.id, 0);
assert_eq!(default_cat.name, "");
let cat = MaterialCategory {
id: 2,
name: "Солод".to_string(),
};
let mat = Material {
id: 1,
name: "Курский тёмный".to_string(),
quantity: 20,
category: cat.clone(),
};
assert_eq!(mat.name, "Курский тёмный");
assert_eq!(mat.quantity, 20);
assert_eq!(mat.category.name, "Солод");
}
/// Test 4: ModalStatus default and equality (using matches! to avoid Debug)
#[test]
fn test_modal_status_default_and_equality() {
let default_status = ModalStatus::default();
assert!(matches!(default_status, ModalStatus::Add));
assert!(!matches!(default_status, ModalStatus::Edit));
assert!(!matches!(default_status, ModalStatus::Remove));
let edit_status = ModalStatus::Edit;
assert!(matches!(edit_status, ModalStatus::Edit));
assert!(!matches!(edit_status, ModalStatus::Add));
}
/// Test 5: Equipment default and construction with nested Worker
#[test]
fn test_equipment_default_and_construction() {
let default_eq = Equipment::default();
assert_eq!(default_eq.id, 0);
assert_eq!(default_eq.name, "");
assert_eq!(default_eq.inv_number, "");
assert!(!default_eq.is_written_off);
let pos = Position {
id: 1,
name: "Уборщик".to_string(),
wage: BigDecimal::from(20000),
};
let worker = Worker {
id: 1,
full_name: "Иван Петров".to_string(),
hire_date: Local::now(),
position: pos,
is_fired: false,
};
let equipment = Equipment {
id: 1,
name: "Метла деревяная".to_string(),
inv_number: "М-1".to_string(),
maintenance_date: Local::now(),
worker: worker.clone(),
is_written_off: false,
};
assert_eq!(equipment.name, "Метла деревяная");
assert_eq!(equipment.inv_number, "М-1");
assert_eq!(equipment.worker.full_name, "Иван Петров");
assert!(!equipment.is_written_off);
}
// ============================================================
// INTEGRATION TESTS (require database connection)
// ============================================================
/// Test 6: Integration — get_position returns positions from DB
#[tokio::test]
async fn test_integration_get_position() {
let db = DBOperator::new().await;
let positions = db.get_position().await.expect("get_position failed");
// The seed data has 4 positions: Уборщик, Кладовщик, Пивовар, Менеджер
assert!(
positions.len() >= 4,
"Expected at least 4 positions, got {}",
positions.len()
);
let names: Vec<&str> = positions.iter().map(|p| p.name.as_str()).collect();
assert!(names.contains(&"Уборщик"), "Should contain Уборщик");
assert!(names.contains(&"Пивовар"), "Should contain Пивовар");
}
/// Test 7: Integration — add_worker and get_workers round-trip
#[tokio::test]
async fn test_integration_add_and_get_worker() {
let db = DBOperator::new().await;
// Add a new worker
let new_worker = WorkerRow {
id: 0, // auto-increment
full_name: "Тестовый Работник Для Теста".to_string(),
hire_date: Local::now(),
position_id: 1, // Уборщик
is_fired: false,
};
let add_result = db.add_worker(new_worker).await.expect("add_worker failed");
assert!(add_result, "add_worker should return true");
// Verify the worker appears in get_workers
let workers = db.get_workers().await.expect("get_workers failed");
let found = workers
.iter()
.any(|w| w.full_name == "Тестовый Работник Для Теста");
assert!(found, "Newly added worker should appear in get_workers");
// Cleanup: find and mark the test worker as fired
let test_worker = workers
.iter()
.find(|w| w.full_name == "Тестовый Работник Для Теста")
.expect("Test worker not found for cleanup");
let fired_row = WorkerRow {
id: test_worker.id,
full_name: test_worker.full_name.clone(),
hire_date: test_worker.hire_date,
position_id: test_worker.position.id,
is_fired: true,
};
db.update_worker(fired_row)
.await
.expect("update_worker (fire) failed");
}
/// Test 8: Integration — material_category full CRUD round-trip
#[tokio::test]
async fn test_integration_material_category_crud() {
let db = DBOperator::new().await;
// Add a new category
let new_cat = MaterialCategory {
id: 0,
name: "Тестовая Категория Интеграция".to_string(),
};
let add_result = db
.add_material_category(new_cat)
.await
.expect("add_material_category failed");
assert!(add_result, "add_material_category should return true");
// Verify it appears in get_mcat
let cats = db.get_mcat().await.expect("get_mcat failed");
let found_cat = cats
.iter()
.find(|c| c.name == "Тестовая Категория Интеграция")
.expect("New category not found in get_mcat");
// Update the category
let mut updated_cat = found_cat.clone();
updated_cat.name = "Обновлённая Тестовая Категория".to_string();
let update_result = db
.update_material_category(updated_cat.clone())
.await
.expect("update_material_category failed");
assert!(update_result, "update_material_category should return true");
// Verify update
let cats_after = db.get_mcat().await.expect("get_mcat after update failed");
let updated = cats_after
.iter()
.find(|c| c.id == found_cat.id)
.expect("Category not found after update");
assert_eq!(updated.name, "Обновлённая Тестовая Категория");
// Delete the category
let delete_result = db
.delete_material_category(updated_cat)
.await
.expect("delete_material_category failed");
assert!(delete_result, "delete_material_category should return true");
// Verify deletion
let cats_final = db.get_mcat().await.expect("get_mcat after delete failed");
let not_found = cats_final.iter().find(|c| c.id == found_cat.id).is_none();
assert!(not_found, "Deleted category should not appear in get_mcat");
}
/// Test 9: Integration — salary full CRUD round-trip
#[tokio::test]
async fn test_integration_salary_crud() {
let db = DBOperator::new().await;
// Get existing workers to use a valid worker_id
let workers = db.get_workers().await.expect("get_workers failed");
let worker = workers.first().expect("No workers in DB for salary test");
let salary_date = Local::now();
let salary_size = BigDecimal::from(99999);
let comment = Some("Интеграционный тест зарплаты".to_string());
// Add salary
let add_result = db
.add_salary(worker.id, salary_date, salary_size.clone(), comment.clone())
.await
.expect("add_salary failed");
assert!(add_result, "add_salary should return true");
// Verify it appears in get_salaries
let salaries = db.get_salaries().await.expect("get_salaries failed");
let found_sal = salaries
.iter()
.find(|s| {
s.worker.id == worker.id
&& s.salary_size == salary_size
&& s.comment == "Интеграционный тест зарплаты"
})
.expect("Newly added salary not found in get_salaries");
// Remove the salary
let remove_result = db
.remove_salary(found_sal.id)
.await
.expect("remove_salary failed");
assert!(remove_result, "remove_salary should return true");
// Verify removal
let salaries_after = db
.get_salaries()
.await
.expect("get_salaries after remove failed");
let not_found = salaries_after
.iter()
.find(|s| s.id == found_sal.id)
.is_none();
assert!(
not_found,
"Removed salary should not appear in get_salaries"
);
}
/// Test 10: Integration — recipe full CRUD round-trip
#[tokio::test]
async fn test_integration_recipe_crud() {
let db = DBOperator::new().await;
// Add a new recipe
let new_recipe = Recipe {
id: 0,
name: "Тестовый Рецепт Интеграция".to_string(),
elements: HashSet::new(),
};
let recipe_id = db.add_recipe(new_recipe).await.expect("add_recipe failed");
assert!(recipe_id > 0, "add_recipe should return a valid ID");
// Verify it appears in get_recipes
let recipes = db.get_recipes().await.expect("get_recipes failed");
let found_recipe = recipes
.iter()
.find(|r| r.name == "Тестовый Рецепт Интеграция")
.expect("Newly added recipe not found in get_recipes");
assert_eq!(found_recipe.id as u64, recipe_id);
// Update the recipe
let mut updated_recipe = found_recipe.clone();
updated_recipe.name = "Обновлённый Тестовый Рецепт".to_string();
let update_result = db
.update_recipe(updated_recipe.clone())
.await
.expect("update_recipe failed");
assert!(update_result, "update_recipe should return true");
// Verify update
let recipes_after = db
.get_recipes()
.await
.expect("get_recipes after update failed");
let updated = recipes_after
.iter()
.find(|r| r.id == found_recipe.id)
.expect("Recipe not found after update");
assert_eq!(updated.name, "Обновлённый Тестовый Рецепт");
// Remove the recipe
let remove_result = db
.remove_recipe(found_recipe.id)
.await
.expect("remove_recipe failed");
assert!(remove_result, "remove_recipe should return true");
// Verify removal
let recipes_final = db
.get_recipes()
.await
.expect("get_recipes after remove failed");
let not_found = recipes_final
.iter()
.find(|r| r.id == found_recipe.id)
.is_none();
assert!(not_found, "Removed recipe should not appear in get_recipes");
}
}

View File

@ -11,7 +11,7 @@ mod reports;
mod tab; mod tab;
mod worker_database; mod worker_database;
mod worker_tab; mod worker_tab;
mod lib;
fn main() { fn main() {
dotenvy::dotenv().ok(); dotenvy::dotenv().ok();