From 51e9cf5c105c845d43904aea307b22b3968f9154 Mon Sep 17 00:00:00 2001 From: Mikhail Kilin Date: Sun, 17 May 2026 18:55:36 +0300 Subject: [PATCH] Add CI quality gate --- .woodpecker/check.yml | 12 ++++++++++-- AGENT.md | 1 + DEVELOPMENT.md | 14 ++++++++++++++ benches/format_markdown.rs | 12 +++--------- docs/REFACTOR_PLAN.md | 24 ++++++++++++------------ src/accounts/lock.rs | 13 ++++--------- src/input/handlers/chat_list.rs | 1 - src/input/handlers/mod.rs | 5 +---- src/ui/modals/pinned.rs | 7 +------ src/ui/modals/search.rs | 7 +------ src/utils/formatting.rs | 2 +- tests/chat_list.rs | 2 -- tests/helpers/app_builder.rs | 10 ++-------- 13 files changed, 50 insertions(+), 60 deletions(-) diff --git a/.woodpecker/check.yml b/.woodpecker/check.yml index 8b0a786..6fe7818 100644 --- a/.woodpecker/check.yml +++ b/.woodpecker/check.yml @@ -8,6 +8,14 @@ steps: - rustup component add rustfmt - cargo fmt -- --check + - name: check + image: rust:latest + environment: + CARGO_HOME: /tmp/cargo + commands: + - apt-get update -qq && apt-get install -y -qq pkg-config libssl-dev libdbus-1-dev zlib1g-dev > /dev/null 2>&1 + - cargo check --all-targets --all-features + - name: clippy image: rust:latest environment: @@ -15,7 +23,7 @@ steps: commands: - apt-get update -qq && apt-get install -y -qq pkg-config libssl-dev libdbus-1-dev zlib1g-dev > /dev/null 2>&1 - rustup component add clippy - - cargo clippy -- -D warnings + - cargo clippy --all-targets --all-features -- -D warnings - name: test image: rust:latest @@ -23,4 +31,4 @@ steps: CARGO_HOME: /tmp/cargo commands: - apt-get update -qq && apt-get install -y -qq pkg-config libssl-dev libdbus-1-dev zlib1g-dev > /dev/null 2>&1 - - cargo test + - cargo test --all-features diff --git a/AGENT.md b/AGENT.md index 302c087..1e80d71 100644 --- a/AGENT.md +++ b/AGENT.md @@ -13,5 +13,6 @@ - Не запускай `cargo run`, `cargo build`, `cargo test`, `cargo check` без прямой команды пользователя. - Не коммить изменения, пока пользователь не попросит. +- Если пользователь попросил тесты/коммит/план до конца, используй quality gate из [DEVELOPMENT.md](DEVELOPMENT.md). - После функциональной правки дай короткий ручной сценарий проверки. - Обновляй [CONTEXT.md](CONTEXT.md), только если изменились статус, риск, архитектурное решение или следующий шаг. diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 8aa3e35..e4f3158 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -21,6 +21,20 @@ cargo check В финальном ответе после изменения укажи, какие cargo-команды не запускались, и дай минимальную ручную проверку. +## Quality Gate + +Если пользователь прямо попросил проверить, закоммитить или выполнить план с тестами, используй тот же набор проверок, что и CI: + +```bash +cargo fmt -- --check +cargo check --all-targets --all-features +cargo clippy --all-targets --all-features -- -D warnings +cargo test --all-features +git diff --check +``` + +Перед коммитом не оставляй `*.snap.new` файлы. + ## Scope - Делай одну логическую правку за раз. diff --git a/benches/format_markdown.rs b/benches/format_markdown.rs index 15d776f..8f37cc6 100644 --- a/benches/format_markdown.rs +++ b/benches/format_markdown.rs @@ -43,9 +43,7 @@ fn benchmark_format_simple_text(c: &mut Criterion) { let entities = vec![]; c.bench_function("format_simple_text", |b| { - b.iter(|| { - format_text_with_entities(black_box(&text), black_box(&entities), Color::White) - }); + b.iter(|| format_text_with_entities(black_box(&text), black_box(&entities), Color::White)); }); } @@ -53,9 +51,7 @@ fn benchmark_format_markdown_text(c: &mut Criterion) { let (text, entities) = create_text_with_entities(); c.bench_function("format_markdown_text", |b| { - b.iter(|| { - format_text_with_entities(black_box(&text), black_box(&entities), Color::White) - }); + b.iter(|| format_text_with_entities(black_box(&text), black_box(&entities), Color::White)); }); } @@ -79,9 +75,7 @@ fn benchmark_format_long_text(c: &mut Criterion) { } c.bench_function("format_long_text_with_100_entities", |b| { - b.iter(|| { - format_text_with_entities(black_box(&text), black_box(&entities), Color::White) - }); + b.iter(|| format_text_with_entities(black_box(&text), black_box(&entities), Color::White)); }); } diff --git a/docs/REFACTOR_PLAN.md b/docs/REFACTOR_PLAN.md index 51bc4ca..8f768a4 100644 --- a/docs/REFACTOR_PLAN.md +++ b/docs/REFACTOR_PLAN.md @@ -216,11 +216,11 @@ Target files: Steps: -- [ ] Add CI steps for `cargo check --all-targets --all-features`. -- [ ] Add CI steps for `cargo clippy --all-targets --all-features -- -D warnings`. -- [ ] Add CI steps for `cargo test --all-features`. -- [ ] Document the same commands in `DEVELOPMENT.md` or `AGENT.md`. -- [ ] Keep CI commands aligned with the commands used by agents and humans locally. +- [x] Add CI steps for `cargo check --all-targets --all-features`. +- [x] Add CI steps for `cargo clippy --all-targets --all-features -- -D warnings`. +- [x] Add CI steps for `cargo test --all-features`. +- [x] Document the same commands in `DEVELOPMENT.md` or `AGENT.md`. +- [x] Keep CI commands aligned with the commands used by agents and humans locally. Acceptance criteria: @@ -231,13 +231,13 @@ Acceptance criteria: Before considering the refactor layer complete: -- [ ] `cargo check --all-targets --all-features` passes. -- [ ] `cargo clippy --all-targets --all-features -- -D warnings` passes. -- [ ] `cargo test --all-features` passes. -- [ ] `git diff --check` passes. -- [ ] No unexpected `*.snap.new` files remain. -- [ ] `rg -n "current_chat_messages_mut|chats_mut|folders_mut|pending_user_ids_mut|user_cache_mut" src/tdlib` shows only intentionally contained internal access. -- [ ] `rg -n "unwrap\\(|expect\\(|panic!\\(" src` has no risky production UI or TDLib data-path panics left. +- [x] `cargo check --all-targets --all-features` passes. +- [x] `cargo clippy --all-targets --all-features -- -D warnings` passes. +- [x] `cargo test --all-features` passes. +- [x] `git diff --check` passes. +- [x] No unexpected `*.snap.new` files remain. +- [x] `rg -n "current_chat_messages_mut|chats_mut|folders_mut|pending_user_ids_mut|user_cache_mut" src/tdlib` shows only intentionally contained internal access. +- [x] `rg -n "unwrap\\(|expect\\(|panic!\\(" src` has no risky production UI or TDLib data-path panics left. ## Recommended Commit Order diff --git a/src/accounts/lock.rs b/src/accounts/lock.rs index db135b9..ab36166 100644 --- a/src/accounts/lock.rs +++ b/src/accounts/lock.rs @@ -33,17 +33,12 @@ pub fn acquire_lock(account_name: &str) -> Result { // Ensure parent directory exists if let Some(parent) = lock_path.parent() { - fs::create_dir_all(parent).map_err(|e| { - format!( - "Не удалось создать директорию для lock-файла: {}", - e - ) - })?; + fs::create_dir_all(parent) + .map_err(|e| format!("Не удалось создать директорию для lock-файла: {}", e))?; } - let file = File::create(&lock_path).map_err(|e| { - format!("Не удалось создать lock-файл {}: {}", lock_path.display(), e) - })?; + let file = File::create(&lock_path) + .map_err(|e| format!("Не удалось создать lock-файл {}: {}", lock_path.display(), e))?; file.try_lock_exclusive().map_err(|_| { format!( diff --git a/src/input/handlers/chat_list.rs b/src/input/handlers/chat_list.rs index 49b6aba..69c1f97 100644 --- a/src/input/handlers/chat_list.rs +++ b/src/input/handlers/chat_list.rs @@ -74,4 +74,3 @@ pub async fn select_folder(app: &mut App, folder_idx: usize app.chat_list_state.select(Some(0)); } } - diff --git a/src/input/handlers/mod.rs b/src/input/handlers/mod.rs index ae9ee51..11bdd4b 100644 --- a/src/input/handlers/mod.rs +++ b/src/input/handlers/mod.rs @@ -21,10 +21,7 @@ pub mod modal; pub mod profile; pub mod search; -pub use chat_loader::{ - process_chat_init_events, - process_pending_chat_init, -}; +pub use chat_loader::{process_chat_init_events, process_pending_chat_init}; pub use clipboard::*; pub use global::*; pub use profile::get_available_actions_count; diff --git a/src/ui/modals/pinned.rs b/src/ui/modals/pinned.rs index 081bbc1..6caac5e 100644 --- a/src/ui/modals/pinned.rs +++ b/src/ui/modals/pinned.rs @@ -56,12 +56,7 @@ pub fn render(f: &mut Frame, area: Rect, app: &App) { if idx > 0 { lines.push(Line::from("")); } - lines.extend(render_message_item( - msg, - idx == selected_index, - content_width, - 3, - )); + lines.extend(render_message_item(msg, idx == selected_index, content_width, 3)); } if lines.is_empty() { diff --git a/src/ui/modals/search.rs b/src/ui/modals/search.rs index f01cf8f..e82bc4e 100644 --- a/src/ui/modals/search.rs +++ b/src/ui/modals/search.rs @@ -80,12 +80,7 @@ pub fn render(f: &mut Frame, area: Rect, app: &App) { if idx > 0 { lines.push(Line::from("")); } - lines.extend(render_message_item( - msg, - idx == selected_index, - content_width, - 2, - )); + lines.extend(render_message_item(msg, idx == selected_index, content_width, 2)); } } diff --git a/src/utils/formatting.rs b/src/utils/formatting.rs index f2133b4..1cb5088 100644 --- a/src/utils/formatting.rs +++ b/src/utils/formatting.rs @@ -1,6 +1,6 @@ -use chrono::{DateTime, Local, NaiveDate, Utc}; #[cfg(test)] use chrono::FixedOffset; +use chrono::{DateTime, Local, NaiveDate, Utc}; use std::time::{SystemTime, UNIX_EPOCH}; pub trait LocalTimeSource { diff --git a/tests/chat_list.rs b/tests/chat_list.rs index 4f8dec2..6ad6da1 100644 --- a/tests/chat_list.rs +++ b/tests/chat_list.rs @@ -56,7 +56,6 @@ fn snapshot_chat_with_unread_count() { #[test] fn test_incoming_message_shows_unread_badge() { - // Создаём чат БЕЗ непрочитанных сообщений let chat = TestChatBuilder::new("Friend", 999) .unread_count(0) @@ -485,7 +484,6 @@ fn snapshot_chat_search_mode() { #[test] fn snapshot_chat_with_online_status() { - let chat = TestChatBuilder::new("Alice", 123) .last_message("Hey there!") .build(); diff --git a/tests/helpers/app_builder.rs b/tests/helpers/app_builder.rs index f7a2c68..742c328 100644 --- a/tests/helpers/app_builder.rs +++ b/tests/helpers/app_builder.rs @@ -144,19 +144,13 @@ impl TestAppBuilder { /// Добавить сообщение для чата pub fn with_message(mut self, chat_id: i64, message: MessageInfo) -> Self { - self.messages - .entry(chat_id) - .or_default() - .push(message); + self.messages.entry(chat_id).or_default().push(message); self } /// Добавить несколько сообщений для чата pub fn with_messages(mut self, chat_id: i64, messages: Vec) -> Self { - self.messages - .entry(chat_id) - .or_default() - .extend(messages); + self.messages.entry(chat_id).or_default().extend(messages); self }