smart-house-web: в работе
This commit is contained in:
@@ -16,10 +16,10 @@
|
|||||||
|
|
||||||
API backend сервиса предоставляет доступ ко всему базовому функционалу библиотеки умного дома:
|
API backend сервиса предоставляет доступ ко всему базовому функционалу библиотеки умного дома:
|
||||||
|
|
||||||
- [ ] Добавление/удаление/перечисление комнат в доме и получение информации о конкретной комнате.
|
- [x] Добавление/удаление/перечисление комнат в доме и получение информации о конкретной комнате.
|
||||||
- [ ] Добавление/удаление/перечисление устройств в комнате и получение информации о конкретном устройстве.
|
- [x] Добавление/удаление/перечисление устройств в комнате и получение информации о конкретном устройстве.
|
||||||
- [ ] Получение отчёта о доме.
|
- [x] Получение отчёта о доме.
|
||||||
- [ ] Присутствуют функциональные тесты, которые общаются с backend-ом и проверяют его ответы.
|
- [x] Присутствуют функциональные тесты, которые общаются с backend-ом и проверяют его ответы.
|
||||||
|
|
||||||
Frontend приложение:
|
Frontend приложение:
|
||||||
|
|
||||||
|
|||||||
@@ -12,4 +12,4 @@ serde = { version = "1.0", features = ["derive"] }
|
|||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
hyper = "1.9.0"
|
reqwest = { version = "0.13.3", features = ["json"] }
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
use std::process::exit;
|
||||||
|
use tracing::{Level, error, trace};
|
||||||
|
|
||||||
/// Ошибка инициализации логгера
|
/// Ошибка инициализации логгера
|
||||||
const CODE_LOGGER_INITIALIZATION_ERROR: i32 = 1;
|
const CODE_LOGGER_INITIALIZATION_ERROR: i32 = 1;
|
||||||
|
|
||||||
@@ -15,8 +18,6 @@ const CODE_CTRL_C_SIGNAL_INSTALL_ERROR: i32 = 5;
|
|||||||
|
|
||||||
/// Инициализация логирования
|
/// Инициализация логирования
|
||||||
pub fn init_logger() {
|
pub fn init_logger() {
|
||||||
use std::process::exit;
|
|
||||||
use tracing::{Level, trace};
|
|
||||||
use tracing_subscriber::{
|
use tracing_subscriber::{
|
||||||
Layer, filter::Targets, fmt::layer, layer::SubscriberExt, registry, util::SubscriberInitExt,
|
Layer, filter::Targets, fmt::layer, layer::SubscriberExt, registry, util::SubscriberInitExt,
|
||||||
};
|
};
|
||||||
@@ -29,6 +30,8 @@ pub fn init_logger() {
|
|||||||
.with_filter(
|
.with_filter(
|
||||||
Targets::new()
|
Targets::new()
|
||||||
.with_target("axum::serve", Level::INFO)
|
.with_target("axum::serve", Level::INFO)
|
||||||
|
.with_target("hyper_util::client::legacy", Level::INFO)
|
||||||
|
.with_target("reqwest::retry", Level::INFO)
|
||||||
.with_default(Level::TRACE),
|
.with_default(Level::TRACE),
|
||||||
)
|
)
|
||||||
.boxed();
|
.boxed();
|
||||||
@@ -40,8 +43,31 @@ pub fn init_logger() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn build_runtime() -> tokio::runtime::Runtime {
|
||||||
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||||
|
|
||||||
|
match tokio::runtime::Builder::new_multi_thread()
|
||||||
|
.name("tokio")
|
||||||
|
.thread_name_fn(|| {
|
||||||
|
static LAST_ID: AtomicUsize = AtomicUsize::new(0);
|
||||||
|
let id = LAST_ID.fetch_add(1, Ordering::SeqCst);
|
||||||
|
format!("tkwr-{id}")
|
||||||
|
})
|
||||||
|
.worker_threads(2)
|
||||||
|
.thread_stack_size(256 * 1024)
|
||||||
|
.enable_all()
|
||||||
|
.build()
|
||||||
|
{
|
||||||
|
Ok(runtime) => runtime,
|
||||||
|
Err(e) => {
|
||||||
|
error!("Failed to create Tokio runtime: {:?}", e);
|
||||||
|
exit(crate::CODE_TOKIO_RUNTIME_CREATION_ERROR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
mod server;
|
mod server;
|
||||||
pub use server::run_server;
|
pub use server::server_main;
|
||||||
|
|
||||||
mod house;
|
mod house;
|
||||||
pub use house::{Device, House, PowerSocket, Room, Thermometer};
|
pub use house::{Device, House, PowerSocket, Room, Thermometer};
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use backend::{init_logger, run_server};
|
use backend::{build_runtime, init_logger, server_main};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
init_logger();
|
init_logger();
|
||||||
run_server();
|
let runtime = build_runtime();
|
||||||
|
runtime.block_on(server_main());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,44 +1,15 @@
|
|||||||
use axum::routing::{delete, get, post, put};
|
use axum::routing::{delete, get, post, put};
|
||||||
use std::{
|
use std::{process::exit, sync::Arc};
|
||||||
process::exit,
|
|
||||||
sync::{
|
|
||||||
Arc,
|
|
||||||
atomic::{AtomicUsize, Ordering},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
use tokio::sync::RwLock;
|
use tokio::sync::RwLock;
|
||||||
use tracing::{error, info};
|
use tracing::{error, info};
|
||||||
|
|
||||||
use crate::House;
|
use crate::House;
|
||||||
|
|
||||||
/// Запуск сервера
|
|
||||||
pub fn run_server() {
|
|
||||||
let runtime = match tokio::runtime::Builder::new_multi_thread()
|
|
||||||
.name("tokio")
|
|
||||||
.thread_name_fn(|| {
|
|
||||||
static LAST_ID: AtomicUsize = AtomicUsize::new(0);
|
|
||||||
let id = LAST_ID.fetch_add(1, Ordering::SeqCst);
|
|
||||||
format!("tkwr-{id}")
|
|
||||||
})
|
|
||||||
.worker_threads(2)
|
|
||||||
.thread_stack_size(256 * 1024)
|
|
||||||
.enable_all()
|
|
||||||
.build()
|
|
||||||
{
|
|
||||||
Ok(runtime) => runtime,
|
|
||||||
Err(e) => {
|
|
||||||
error!("Failed to create Tokio runtime: {:?}", e);
|
|
||||||
exit(crate::CODE_TOKIO_RUNTIME_CREATION_ERROR);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
runtime.block_on(server_main());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Тип состояния
|
/// Тип состояния
|
||||||
type ServerState = Arc<RwLock<House>>;
|
type ServerState = Arc<RwLock<House>>;
|
||||||
|
|
||||||
/// Основная функция сервера
|
/// Основная функция сервера
|
||||||
async fn server_main() {
|
pub async fn server_main() {
|
||||||
let state: ServerState = Arc::new(RwLock::new(House::default()));
|
let state: ServerState = Arc::new(RwLock::new(House::default()));
|
||||||
|
|
||||||
let app = axum::Router::new()
|
let app = axum::Router::new()
|
||||||
|
|||||||
238
smart-house-web/backend/tests/api_smoke_test.rs
Normal file
238
smart-house-web/backend/tests/api_smoke_test.rs
Normal file
@@ -0,0 +1,238 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use backend::{Device, Room, init_logger, server_main};
|
||||||
|
use reqwest::{Client, Response, StatusCode};
|
||||||
|
use serde_json::json;
|
||||||
|
use tokio::spawn;
|
||||||
|
use tracing::info;
|
||||||
|
|
||||||
|
type RqResult<T> = Result<T, reqwest::Error>;
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "current_thread")]
|
||||||
|
async fn smoke() -> RqResult<()> {
|
||||||
|
init_logger();
|
||||||
|
spawn(server_main());
|
||||||
|
|
||||||
|
let client = Client::new();
|
||||||
|
|
||||||
|
verify_rooms_state0(&client).await?;
|
||||||
|
init_rooms(&client).await?;
|
||||||
|
verify_rooms_state1(&client).await?;
|
||||||
|
delete_room0(&client).await?;
|
||||||
|
verify_rooms_state2(&client).await?;
|
||||||
|
put_room(&client).await?;
|
||||||
|
verify_rooms_state3(&client).await?;
|
||||||
|
verify_room1_state0(&client).await?;
|
||||||
|
verify_room1_devices_state0(&client).await?;
|
||||||
|
verify_room1_device_psock_state0(&client).await?;
|
||||||
|
delete_psock(&client).await?;
|
||||||
|
verify_room1_device_psock_state1(&client).await?;
|
||||||
|
put_device(&client).await?;
|
||||||
|
verify_new_device_in_place(&client).await?;
|
||||||
|
print_house_report(&client).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn print_resp(resp: Response) -> RqResult<()> {
|
||||||
|
info!(
|
||||||
|
"\n<<< StatusCode: {}\n<<< BODY: {}",
|
||||||
|
resp.status(),
|
||||||
|
resp.text().await?
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn verify_rooms_state0(client: &Client) -> RqResult<()> {
|
||||||
|
let resp = client.get("http://localhost:8080/rooms").send().await?;
|
||||||
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
|
|
||||||
|
let rooms = resp.json::<HashMap<String, Room>>().await?;
|
||||||
|
assert!(rooms.is_empty());
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn init_rooms(client: &Client) -> RqResult<()> {
|
||||||
|
let resp = client
|
||||||
|
.post("http://localhost:8080/rooms")
|
||||||
|
.json(&json!({
|
||||||
|
"ROOM0": { "devices": {} },
|
||||||
|
"ROOM1": { "devices": {
|
||||||
|
"therm": { "type": "Thermometer", "temperature": 22 },
|
||||||
|
"psock": { "type": "PowerSocket", "power_rate": 11, "on": false }
|
||||||
|
} }
|
||||||
|
}))
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
assert_eq!(resp.status(), StatusCode::CREATED);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn verify_rooms_state1(client: &Client) -> RqResult<()> {
|
||||||
|
let resp = client.get("http://localhost:8080/rooms").send().await?;
|
||||||
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
|
|
||||||
|
let rooms = resp.json::<HashMap<String, Room>>().await?;
|
||||||
|
assert_eq!(rooms.len(), 2);
|
||||||
|
assert_eq!(rooms.get("ROOM1").unwrap().get_devices().len(), 2);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn delete_room0(client: &Client) -> RqResult<()> {
|
||||||
|
let resp = client
|
||||||
|
.delete("http://localhost:8080/room/ROOM0")
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
assert_eq!(resp.status(), StatusCode::ACCEPTED);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn verify_rooms_state2(client: &Client) -> RqResult<()> {
|
||||||
|
let resp = client.get("http://localhost:8080/rooms").send().await?;
|
||||||
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
|
|
||||||
|
let rooms = resp.json::<HashMap<String, Room>>().await?;
|
||||||
|
assert_eq!(rooms.len(), 1);
|
||||||
|
assert_eq!(rooms.get("ROOM1").unwrap().get_devices().len(), 2);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn put_room(client: &Client) -> RqResult<()> {
|
||||||
|
let resp = client
|
||||||
|
.put("http://localhost:8080/room/ROOM")
|
||||||
|
.json(&json!({ "devices": {
|
||||||
|
"therm": { "type": "Thermometer", "temperature": 20 },
|
||||||
|
"psock": { "type": "PowerSocket", "power_rate": 10, "on": true }
|
||||||
|
} }))
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
assert_eq!(resp.status(), StatusCode::CREATED);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn verify_rooms_state3(client: &Client) -> RqResult<()> {
|
||||||
|
let resp = client.get("http://localhost:8080/rooms").send().await?;
|
||||||
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
|
|
||||||
|
let rooms = resp.json::<HashMap<String, Room>>().await?;
|
||||||
|
assert_eq!(rooms.len(), 2);
|
||||||
|
assert_eq!(rooms.get("ROOM").unwrap().get_devices().len(), 2);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn verify_room1_state0(client: &Client) -> RqResult<()> {
|
||||||
|
let resp = client
|
||||||
|
.get("http://localhost:8080/room/ROOM1")
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
|
|
||||||
|
let room = resp.json::<Room>().await?;
|
||||||
|
assert_eq!(room.get_devices().len(), 2);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn verify_room1_devices_state0(client: &Client) -> RqResult<()> {
|
||||||
|
let resp = client
|
||||||
|
.get("http://localhost:8080/room/ROOM1/devices")
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
|
|
||||||
|
let device_map = resp.json::<HashMap<String, Device>>().await?;
|
||||||
|
assert_eq!(device_map.len(), 2);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn verify_room1_device_psock_state0(client: &Client) -> RqResult<()> {
|
||||||
|
let resp = client
|
||||||
|
.get("http://localhost:8080/room/ROOM1/device/psock")
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
|
|
||||||
|
let device = resp.json::<Device>().await?;
|
||||||
|
|
||||||
|
let Device::PowerSocket(power_socket) = device else {
|
||||||
|
panic!("PowerSocket expected");
|
||||||
|
};
|
||||||
|
assert_eq!(power_socket.get_power(), 0.0);
|
||||||
|
assert_eq!(power_socket.is_on(), false);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn delete_psock(client: &Client) -> RqResult<()> {
|
||||||
|
let resp = client
|
||||||
|
.delete("http://localhost:8080/room/ROOM1/device/psock")
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
assert_eq!(resp.status(), StatusCode::ACCEPTED);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn verify_room1_device_psock_state1(client: &Client) -> RqResult<()> {
|
||||||
|
let resp = client
|
||||||
|
.get("http://localhost:8080/room/ROOM1/device/psock")
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn put_device(client: &Client) -> RqResult<()> {
|
||||||
|
let resp = client
|
||||||
|
.put("http://localhost:8080/room/ROOM1/device/TEST")
|
||||||
|
.json(&json!({
|
||||||
|
"type": "PowerSocket",
|
||||||
|
"power_rate": 5,
|
||||||
|
"on": true
|
||||||
|
}))
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
assert_eq!(resp.status(), StatusCode::CREATED);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn verify_new_device_in_place(client: &Client) -> RqResult<()> {
|
||||||
|
let resp = client.get("http://localhost:8080/rooms").send().await?;
|
||||||
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
|
|
||||||
|
let rooms = resp.json::<HashMap<String, Room>>().await?;
|
||||||
|
assert_eq!(rooms.len(), 2);
|
||||||
|
assert_eq!(rooms.get("ROOM1").unwrap().get_devices().len(), 2);
|
||||||
|
let device = rooms
|
||||||
|
.get("ROOM1")
|
||||||
|
.unwrap()
|
||||||
|
.get_devices()
|
||||||
|
.get("TEST")
|
||||||
|
.unwrap();
|
||||||
|
let Device::PowerSocket(power_socket) = device else {
|
||||||
|
panic!("TEST device must be a PowerSocket")
|
||||||
|
};
|
||||||
|
assert!(power_socket.is_on());
|
||||||
|
assert_eq!(power_socket.get_power(), 5.0);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn print_house_report(client: &Client) -> RqResult<()> {
|
||||||
|
let resp = client.get("http://localhost:8080/rooms").send().await?;
|
||||||
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
|
|
||||||
|
print_resp(resp).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
#[tokio::test(flavor = "current_thread")]
|
|
||||||
async fn smoke() {
|
|
||||||
println!("Hello test!")
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user