use escpos_vfd::codec::EpsonCodec; use escpos_vfd::{ ConfigError, DisplaySettings, MAX_MARQUEE_CHARS, MAX_MARQUEE_CPS, MAX_QUEUED_RAW_BYTES, TextEncoding, Vfd, VfdError, VfdWorker, }; use std::time::Duration; #[test] fn public_text_api_neutralizes_controls_and_legacy_fallback_is_one_byte() { let display = DisplaySettings::new(8, 1, TextEncoding::Ascii); let mut vfd = Vfd::from_transport(Vec::::new(), display).unwrap(); vfd.print_line(1, "A\u{1b}@B\u{0c}C").unwrap(); assert_eq!(&vfd.into_inner()[6..], b"A @B C "); for encoding in [TextEncoding::Cp866, TextEncoding::Windows1251] { let codec = EpsonCodec::new(DisplaySettings::new(1, 1, encoding)).unwrap(); assert_eq!(codec.encode_text("😀"), vec![b'?']); } } #[test] fn public_codec_constructor_returns_geometry_errors() { assert!(matches!( EpsonCodec::new(DisplaySettings::new(usize::MAX, 1, TextEncoding::Ascii)), Err(ConfigError::InvalidColumns(usize::MAX)) )); } #[test] fn sync_worker_enforces_payload_and_rate_boundaries() { let display = DisplaySettings::new(20, 2, TextEncoding::Ascii); let worker = VfdWorker::from_transport(Vec::::new(), display, 2).unwrap(); let handle = worker.handle(); handle.print_line(1, "x".repeat(1_000_000)).unwrap(); handle .set_marquee_text("m".repeat(MAX_MARQUEE_CHARS)) .unwrap(); assert!(matches!( handle.set_marquee_text("m".repeat(MAX_MARQUEE_CHARS + 1)), Err(VfdError::TextTooLong { .. }) )); handle.write_raw(vec![0; MAX_QUEUED_RAW_BYTES]).unwrap(); assert!(matches!( handle.write_raw(vec![0; MAX_QUEUED_RAW_BYTES + 1]), Err(VfdError::RawPayloadTooLarge { .. }) )); handle .start_marquee(2, MAX_MARQUEE_CPS, Duration::ZERO) .unwrap(); handle.stop_marquee().unwrap(); assert!(matches!( handle.start_marquee(2, MAX_MARQUEE_CPS + 1, Duration::ZERO), Err(VfdError::InvalidMarqueeSpeed { .. }) )); let bytes = worker.shutdown().unwrap().into_inner(); assert!(bytes.windows(20).any(|window| window == [b'x'; 20])); } #[cfg(feature = "tokio")] #[tokio::test] async fn tokio_public_api_matches_security_boundaries() { use escpos_vfd::tokio::{AsyncVfd, AsyncVfdWorker}; let display = DisplaySettings::new(8, 1, TextEncoding::Ascii); let mut vfd = AsyncVfd::from_transport(Vec::::new(), display) .await .unwrap(); vfd.print_line(1, "A\u{1b}@B\u{0c}C").await.unwrap(); assert_eq!(&vfd.into_inner()[6..], b"A @B C "); let display = DisplaySettings::new(20, 2, TextEncoding::Ascii); let worker = AsyncVfdWorker::from_transport(Vec::::new(), display, 2) .await .unwrap(); let handle = worker.handle(); assert!(matches!( handle .set_marquee_text("m".repeat(MAX_MARQUEE_CHARS + 1)) .await, Err(VfdError::TextTooLong { .. }) )); assert!(matches!( handle.write_raw(vec![0; MAX_QUEUED_RAW_BYTES + 1]).await, Err(VfdError::RawPayloadTooLarge { .. }) )); assert!(matches!( handle .start_marquee(1, MAX_MARQUEE_CPS + 1, Duration::ZERO) .await, Err(VfdError::InvalidMarqueeSpeed { .. }) )); worker.shutdown().await.unwrap(); }