Description: Autogenerated patch header for a single-debian-patch file.
 The delta against upstream is either kept as a single patch, or maintained
 in some VCS, and exported as a single patch instead of more manageable
 atomic patches.
Forwarded: not-needed

---
--- pixie-1.1.0.orig/.github/workflows/rust.yml
+++ pixie-1.1.0/.github/workflows/rust.yml
@@ -113,11 +113,49 @@ jobs:
         working-directory: pixie
         run: cargo test --all-features --locked --verbose
 
+  coverage:
+    name: Coverage (min 70%)
+    runs-on: ubuntu-latest
+    needs:
+      - test
+
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v4
+
+      - name: Install Rust toolchain
+        run: |
+          rustup toolchain install stable --profile minimal
+          rustup default stable
+          rustup component add llvm-tools-preview
+
+      - name: Install cargo-llvm-cov
+        run: cargo install cargo-llvm-cov --locked
+
+      - name: Cache cargo
+        uses: actions/cache@v4
+        with:
+          path: |
+            ~/.cargo/registry
+            ~/.cargo/git
+            pixie/target
+          key: ${{ runner.os }}-cargo-coverage-${{ hashFiles('**/Cargo.lock') }}
+          restore-keys: |
+            ${{ runner.os }}-cargo-coverage-
+            ${{ runner.os }}-cargo-
+
+      - name: Check coverage threshold
+        working-directory: pixie
+        run: >
+          cargo llvm-cov --all-features --locked --lib --bins
+          --test config_tests --test logger_tests --test router_tests --test threadpool_tests
+          --summary-only --fail-under-lines 70
+
   build:
     name: Build release
     runs-on: ubuntu-latest
     needs:
-      - test
+      - coverage
 
     steps:
       - name: Checkout
--- pixie-1.1.0.orig/.pre-commit-config.yaml
+++ pixie-1.1.0/.pre-commit-config.yaml
@@ -16,4 +16,3 @@ repos:
         pass_filenames: false
         files: ^pixie/(src/|tests/|Cargo\.toml|Cargo\.lock)
         stages: [pre-commit]
-
--- pixie-1.1.0.orig/CONTRIBUTING.md
+++ pixie-1.1.0/CONTRIBUTING.md
@@ -92,6 +92,7 @@ Avant de soumettre:
 
 - [ ] La branche respecte le format de nommage.
 - [ ] `cargo fmt`, `cargo clippy` et `cargo test` passent en local.
+- [ ] Le seuil de couverture CI reste >= 70%.
 - [ ] Les changements sont documentes si necessaire (`README.md`).
 - [ ] Aucun secret/cle privee/fichier sensible n est commit.
 - [ ] La CI GitHub est verte.
--- pixie-1.1.0.orig/Dockerfile
+++ pixie-1.1.0/Dockerfile
@@ -27,4 +27,3 @@ EXPOSE 8080
 USER pixie
 
 ENTRYPOINT ["/usr/local/bin/pixie"]
-CMD ["serve"]
--- pixie-1.1.0.orig/pixie/src/config.rs
+++ pixie-1.1.0/pixie/src/config.rs
@@ -57,10 +57,10 @@ pub fn runtime_config() -> io::Result<Ru
 
     let addr = file_addr(file_config.as_ref()).unwrap_or_else(default_addr);
 
-    let workers = file_config
-        .and_then(|cfg| cfg.workers)
-        .filter(|value| *value > 0)
-        .unwrap_or(DEFAULT_THREADS);
+    let workers = match file_config.and_then(|cfg| cfg.workers) {
+        Some(value) if value > 0 => value,
+        _ => DEFAULT_THREADS,
+    };
 
     Ok(RuntimeConfig { addr, workers })
 }
@@ -71,11 +71,13 @@ fn load_file_config() -> io::Result<Opti
         return read_config_if_exists(&path);
     }
 
-    if let Some(cfg) = read_config_if_exists(LOCAL_CONFIG_PATH)? {
-        return Ok(Some(cfg));
+    for path in [LOCAL_CONFIG_PATH, SYSTEM_CONFIG_PATH] {
+        if let Some(config) = read_config_if_exists(path)? {
+            return Ok(Some(config));
+        }
     }
 
-    read_config_if_exists(SYSTEM_CONFIG_PATH)
+    Ok(None)
 }
 
 /// Lit et parse un fichier YAML uniquement s'il existe et s'il s'agit d'un fichier.
@@ -107,11 +109,15 @@ fn read_config_if_exists(path: &str) ->
 fn file_addr(config: Option<&FileConfig>) -> Option<String> {
     let config = config?;
 
-    config.addr.clone().or_else(|| {
-        (config.host.is_some() || config.port.is_some()).then(|| {
-            let host = config.host.as_deref().unwrap_or(DEFAULT_HOST);
-            let port = config.port.unwrap_or(DEFAULT_PORT);
-            format!("{host}:{port}")
-        })
-    })
+    if let Some(addr) = config.addr.clone() {
+        return Some(addr);
+    }
+
+    if config.host.is_none() && config.port.is_none() {
+        return None;
+    }
+
+    let host = config.host.as_deref().unwrap_or(DEFAULT_HOST);
+    let port = config.port.unwrap_or(DEFAULT_PORT);
+    Some(format!("{host}:{port}"))
 }
--- pixie-1.1.0.orig/pixie/src/router.rs
+++ pixie-1.1.0/pixie/src/router.rs
@@ -23,19 +23,20 @@ const NOT_FOUND_PAGE: &str = "404.html";
 /// 2. `../web` depuis le manifeste Cargo (mode dev)
 /// 3. `/usr/share/pixie/web` (install système)
 pub fn resolve_web_root() -> PathBuf {
-    if let Ok(path) = env::var("PIXIE_WEB_ROOT") {
-        if !path.is_empty() {
-            let candidate = PathBuf::from(path);
+    if let Some(path) = env::var("PIXIE_WEB_ROOT")
+        .ok()
+        .filter(|path| !path.is_empty())
+    {
+        let candidate = PathBuf::from(path);
 
-            if candidate.is_dir() {
-                return candidate;
-            }
-
-            log_warn(format_args!(
-                "PIXIE_WEB_ROOT='{}' is not a directory, using fallback",
-                candidate.display()
-            ));
+        if candidate.is_dir() {
+            return candidate;
         }
+
+        log_warn(format_args!(
+            "PIXIE_WEB_ROOT='{}' is not a directory, using fallback",
+            candidate.display()
+        ));
     }
 
     let dev_root = Path::new(env!("CARGO_MANIFEST_DIR")).join("../web");
--- pixie-1.1.0.orig/pixie/src/server.rs
+++ pixie-1.1.0/pixie/src/server.rs
@@ -5,9 +5,9 @@
 
 use std::{
     fs,
-    io::{self, BufReader, prelude::*},
+    io::{self, BufRead, BufReader, Write},
     net::{TcpListener, TcpStream},
-    path::{Path, PathBuf},
+    path::Path,
     sync::Arc,
 };
 
@@ -30,34 +30,35 @@ pub fn run_server(addr: &str, pool_size:
         web_root.display()
     ));
 
-    for stream in listener.incoming().flatten() {
-        dispatch_stream(stream, &pool, web_root.as_path());
+    for stream in listener.incoming() {
+        match stream {
+            Ok(stream) => {
+                let web_root = Arc::clone(&web_root);
+                pool.execute(move || {
+                    if let Err(err) = handle_connection(stream, web_root.as_path()) {
+                        log_error(format_args!("failed to handle connection: {err}"));
+                    }
+                });
+            }
+            Err(err) => log_error(format_args!("failed to accept connection: {err}")),
+        }
     }
 
     Ok(())
 }
 
-/// Délègue une connexion acceptée au thread-pool.
-fn dispatch_stream(stream: TcpStream, pool: &ThreadPool, web_root: &Path) {
-    let web_root = PathBuf::from(web_root);
-
-    pool.execute(move || {
-        if let Err(err) = handle_connection(stream, web_root.as_path()) {
-            log_error(format_args!("failed to handle connection: {err}"));
-        }
-    });
-}
-
 /// Lit la request-line HTTP, résout la page cible puis écrit la réponse.
 fn handle_connection(mut stream: TcpStream, web_root: &Path) -> io::Result<()> {
-    let buf_reader = BufReader::new(&stream);
-    let request_line = match buf_reader.lines().next().transpose()? {
-        Some(line) => line,
-        None => return Ok(()),
-    };
+    let mut request_line = String::new();
+    
+    if BufReader::new(&stream).read_line(&mut request_line)? == 0 {
+        return Ok(());
+    }
+
+    let request_line = request_line.trim_end_matches(['\r', '\n']);
 
-    let (status_line, filename) = resolve_route(&request_line, web_root);
-    let contents = fs::read_to_string(filename)?;
+    let (status_line, filename) = resolve_route(request_line, web_root);
+    let contents = fs::read_to_string(&filename)?;
     let length = contents.len();
 
     let response = format!(
--- pixie-1.1.0.orig/pixie/src/threadpool.rs
+++ pixie-1.1.0/pixie/src/threadpool.rs
@@ -1,7 +1,9 @@
 //! Thread-pool minimal utilisé par le serveur pour traiter les connexions en parallèle.
 
-mod job;
 mod pool;
 mod worker;
 
+/// Type d'un travail asynchrone exécuté par un worker.
+pub(crate) type Job = Box<dyn FnOnce() + Send + 'static>;
+
 pub use pool::ThreadPool;
--- pixie-1.1.0.orig/pixie/src/threadpool/job.rs
+++ /dev/null
@@ -1,2 +0,0 @@
-/// Type d'un travail asynchrone exécuté par un worker.
-pub(crate) type Job = Box<dyn FnOnce() + Send + 'static>;
--- pixie-1.1.0.orig/pixie/src/threadpool/pool.rs
+++ pixie-1.1.0/pixie/src/threadpool/pool.rs
@@ -1,6 +1,6 @@
 use std::sync::{Arc, Mutex, mpsc};
 
-use super::{job::Job, worker::Worker};
+use super::{Job, worker::Worker};
 
 /// Pool de threads fixe utilisé pour exécuter des jobs concurrents.
 pub struct ThreadPool {
@@ -34,10 +34,13 @@ impl ThreadPool {
     where
         F: FnOnce() + Send + 'static,
     {
-        let sender = self.sender.as_ref().expect("thread pool sender is missing");
+        let Some(sender) = self.sender.as_ref() else {
+            crate::logger::log_warn(format_args!("thread pool is shutting down; dropping job"));
+            return;
+        };
 
         if let Err(err) = sender.send(Box::new(f)) {
-            eprintln!("[pixie][error] failed to send job to worker: {err}");
+            crate::logger::log_error(format_args!("failed to send job to worker: {err}"));
         }
     }
 }
--- pixie-1.1.0.orig/pixie/src/threadpool/worker.rs
+++ pixie-1.1.0/pixie/src/threadpool/worker.rs
@@ -3,7 +3,7 @@ use std::{
     thread,
 };
 
-use super::job::Job;
+use super::Job;
 
 /// Worker interne du thread-pool.
 pub(super) struct Worker {
@@ -20,14 +20,14 @@ impl Worker {
                     .lock()
                     .expect("worker receiver lock poisoned")
                     .recv();
+                let Ok(job) = message else {
+                    crate::logger::log_info(format_args!(
+                        "worker {id} disconnected; shutting down"
+                    ));
+                    break;
+                };
 
-                match message {
-                    Ok(job) => job(),
-                    Err(_) => {
-                        eprintln!("[pixie][info] worker {id} disconnected; shutting down");
-                        break;
-                    }
-                }
+                job();
             }
         });
 
@@ -40,8 +40,8 @@ impl Worker {
     /// Attend la fin du thread worker.
     pub(super) fn join(&mut self) {
         if let Some(thread) = self.thread.take() {
-            if let Err(err) = thread.join() {
-                eprintln!("[pixie][error] worker {} panicked: {:?}", self.id, err);
+            if thread.join().is_err() {
+                crate::logger::log_error(format_args!("worker {} panicked", self.id));
             }
         }
     }
--- pixie-1.1.0.orig/pixie/tests/common/mod.rs
+++ pixie-1.1.0/pixie/tests/common/mod.rs
@@ -4,7 +4,7 @@ use std::{
     env, fs,
     io::{self, Read, Write},
     net::{Shutdown, TcpListener, TcpStream},
-    path::{Path, PathBuf},
+    path::PathBuf,
     sync::Mutex,
     thread,
     time::{Duration, SystemTime, UNIX_EPOCH},
@@ -153,8 +153,3 @@ pub fn send_http_request(addr: &str, req
     String::from_utf8(buf)
         .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err.to_string()))
 }
-
-/// Retourne vrai si le chemin pointe vers un dossier existant.
-pub fn is_dir(path: &Path) -> bool {
-    path.is_dir()
-}
--- pixie-1.1.0.orig/wikidoc/code_fonctionnement.md
+++ pixie-1.1.0/wikidoc/code_fonctionnement.md
@@ -15,9 +15,8 @@ Fichier: `/pixie/src/main.rs`
 
 Flux:
 
-1. lit les arguments CLI (`pixie serve`)
-2. charge la config runtime via `runtime_config()`
-3. lance le serveur via `run_server(addr, workers)`
+1. charge la config runtime via `runtime_config()`
+2. lance le serveur via `run_server(addr, workers)`
 
 ## Modules Principaux
 
@@ -95,7 +94,6 @@ Suites presentes:
 - `server_tests.rs`
 - `threadpool_tests.rs`
 - `logger_tests.rs`
-- `cli_tests.rs`
 
 ## Couverture
 
@@ -113,7 +111,6 @@ TOOLBIN="$(rustc --print sysroot)/lib/ru
   --object target/debug/deps/router_tests-* \
   --object target/debug/deps/server_tests-* \
   --object target/debug/deps/logger_tests-* \
-  --object target/debug/deps/cli_tests-* \
   --instr-profile pixie.profdata \
   --ignore-filename-regex '/\\.cargo/registry|/tests/'
 ```
--- pixie-1.1.0.orig/wikidoc/install_debian.md
+++ pixie-1.1.0/wikidoc/install_debian.md
@@ -23,10 +23,10 @@ Depuis mentors.debian.net:
 ```bash
 sudo apt install -y devscripts
 cd /tmp
-dget -x https://mentors.debian.net/debian/pool/main/p/pixie/pixie_1.1.0-2.dsc
+dget -x https://mentors.debian.net/debian/pool/main/p/pixie/pixie_1.1.0-3.dsc
 cd pixie-1.1.0
 dpkg-buildpackage -us -uc -b
-sudo apt install ../pixie_1.1.0-2_amd64.deb
+sudo apt install ../pixie_1.1.0-3_amd64.deb
 ```
 
 ## Configuration
--- pixie-1.1.0.orig/wikidoc/pixie_publish.md
+++ pixie-1.1.0/wikidoc/pixie_publish.md
@@ -65,7 +65,7 @@ Puis suivre le bug RFS (ex: `#1133771`)
 - ITP: `#1133770`
 - RFS: `#1133771`
 - Upload mentors:
-  `https://mentors.debian.net/debian/pool/main/p/pixie/pixie_1.1.0-2.dsc`
+  `https://mentors.debian.net/debian/pool/main/p/pixie/pixie_1.1.0-3.dsc`
 
 ## Notes importantes
 
