Skip to main content

jugar_probar/
capabilities.rs

1//! WASM Thread Capabilities Detection (PROBAR-SPEC-010)
2//!
3//! Verify SharedArrayBuffer/COOP-COEP headers and threading availability.
4//!
5//! ## Toyota Way Application:
6//! - **Genchi Genbutsu**: Direct observation of actual browser capabilities
7//! - **Poka-Yoke**: Type-safe capability assertions prevent runtime surprises
8//! - **Andon**: Clear error messages with fix hints
9//!
10//! ## References:
11//! - [7] Herlihy & Shavit (2012) SharedArrayBuffer testing
12//! - [8] Lamport (1978) Worker message ordering
13
14use std::fmt;
15
16/// Required HTTP headers for SharedArrayBuffer support
17#[derive(Debug, Clone, Copy)]
18pub struct RequiredHeaders;
19
20impl RequiredHeaders {
21    /// Cross-Origin-Opener-Policy header value
22    pub const COOP: &'static str = "same-origin";
23    /// Cross-Origin-Embedder-Policy header value
24    pub const COEP: &'static str = "require-corp";
25}
26
27/// WASM threading capabilities detected from browser context
28#[derive(Debug, Clone, Default)]
29pub struct WasmThreadCapabilities {
30    /// Whether `crossOriginIsolated` is true in the browser
31    pub cross_origin_isolated: bool,
32
33    /// Whether `SharedArrayBuffer` is available
34    pub shared_array_buffer: bool,
35
36    /// Whether `Atomics` is available
37    pub atomics: bool,
38
39    /// `navigator.hardwareConcurrency` value
40    pub hardware_concurrency: u32,
41
42    /// COOP header value (if present)
43    pub coop_header: Option<String>,
44
45    /// COEP header value (if present)
46    pub coep_header: Option<String>,
47
48    /// Whether the page is served over HTTPS (required for SAB)
49    pub is_secure_context: bool,
50
51    /// Error messages collected during detection
52    pub errors: Vec<String>,
53}
54
55/// Capability check result
56#[derive(Debug, Clone, PartialEq, Eq)]
57pub enum CapabilityStatus {
58    /// Capability is available
59    Available,
60    /// Capability is unavailable with reason
61    Unavailable(String),
62    /// Capability status is unknown
63    Unknown,
64}
65
66impl WasmThreadCapabilities {
67    /// Create capabilities indicating full threading support
68    #[must_use]
69    pub fn full_support() -> Self {
70        Self {
71            cross_origin_isolated: true,
72            shared_array_buffer: true,
73            atomics: true,
74            hardware_concurrency: 8,
75            coop_header: Some(RequiredHeaders::COOP.to_string()),
76            coep_header: Some(RequiredHeaders::COEP.to_string()),
77            is_secure_context: true,
78            errors: vec![],
79        }
80    }
81
82    /// Create capabilities indicating no threading support
83    #[must_use]
84    pub fn no_support() -> Self {
85        Self {
86            cross_origin_isolated: false,
87            shared_array_buffer: false,
88            atomics: false,
89            hardware_concurrency: 1,
90            coop_header: None,
91            coep_header: None,
92            is_secure_context: false,
93            errors: vec!["SharedArrayBuffer not available".to_string()],
94        }
95    }
96
97    /// Assert all requirements for multi-threaded WASM
98    ///
99    /// # Errors
100    /// Returns error with detailed message if any requirement is not met
101    pub fn assert_threading_ready(&self) -> Result<(), CapabilityError> {
102        let mut errors = Vec::new();
103
104        if !self.cross_origin_isolated {
105            errors.push("crossOriginIsolated is false".to_string());
106        }
107
108        if !self.shared_array_buffer {
109            errors.push("SharedArrayBuffer is not available".to_string());
110        }
111
112        if !self.atomics {
113            errors.push("Atomics is not available".to_string());
114        }
115
116        if !self.is_secure_context {
117            errors.push("Page is not served over HTTPS".to_string());
118        }
119
120        // Check COOP header
121        match &self.coop_header {
122            Some(value) if value == RequiredHeaders::COOP => {}
123            Some(value) => {
124                errors.push(format!(
125                    "COOP header is '{value}', expected '{}'",
126                    RequiredHeaders::COOP
127                ));
128            }
129            None => {
130                errors.push(format!(
131                    "COOP header missing. Add: Cross-Origin-Opener-Policy: {}",
132                    RequiredHeaders::COOP
133                ));
134            }
135        }
136
137        // Check COEP header
138        match &self.coep_header {
139            Some(value) if value == RequiredHeaders::COEP => {}
140            Some(value) => {
141                errors.push(format!(
142                    "COEP header is '{value}', expected '{}'",
143                    RequiredHeaders::COEP
144                ));
145            }
146            None => {
147                errors.push(format!(
148                    "COEP header missing. Add: Cross-Origin-Embedder-Policy: {}",
149                    RequiredHeaders::COEP
150                ));
151            }
152        }
153
154        if errors.is_empty() {
155            Ok(())
156        } else {
157            Err(CapabilityError::ThreadingNotReady(errors))
158        }
159    }
160
161    /// Assert requirements for streaming audio processing
162    ///
163    /// Streaming requires threading for real-time performance
164    ///
165    /// # Errors
166    /// Returns error if streaming requirements are not met
167    pub fn assert_streaming_ready(&self) -> Result<(), CapabilityError> {
168        // Streaming has same requirements as threading
169        self.assert_threading_ready()?;
170
171        // Additional check: need sufficient cores
172        if self.hardware_concurrency < 2 {
173            return Err(CapabilityError::InsufficientResources(
174                "Streaming requires at least 2 CPU cores".to_string(),
175            ));
176        }
177
178        Ok(())
179    }
180
181    /// Get recommended thread count for optimal performance
182    ///
183    /// Per whisper.apr: `N_threads = max(1, min(hardwareConcurrency - 1, 8))`
184    #[must_use]
185    pub fn optimal_threads(&self) -> u32 {
186        self.hardware_concurrency.saturating_sub(1).clamp(1, 8)
187    }
188
189    /// Check if threading is available (non-asserting)
190    #[must_use]
191    pub fn is_threading_available(&self) -> bool {
192        self.cross_origin_isolated
193            && self.shared_array_buffer
194            && self.atomics
195            && self.is_secure_context
196    }
197
198    /// Check SharedArrayBuffer status
199    #[must_use]
200    pub fn shared_array_buffer_status(&self) -> CapabilityStatus {
201        if self.shared_array_buffer {
202            CapabilityStatus::Available
203        } else if !self.is_secure_context {
204            CapabilityStatus::Unavailable("Not in secure context (HTTPS required)".to_string())
205        } else if !self.cross_origin_isolated {
206            CapabilityStatus::Unavailable("crossOriginIsolated is false".to_string())
207        } else {
208            CapabilityStatus::Unavailable("Unknown reason".to_string())
209        }
210    }
211
212    /// Generate JavaScript code to detect capabilities in browser
213    #[must_use]
214    pub fn detection_js() -> &'static str {
215        r#"
216(function() {
217    const caps = {
218        crossOriginIsolated: !!self.crossOriginIsolated,
219        sharedArrayBuffer: typeof SharedArrayBuffer !== 'undefined',
220        atomics: typeof Atomics !== 'undefined',
221        hardwareConcurrency: navigator.hardwareConcurrency || 1,
222        isSecureContext: !!self.isSecureContext,
223        coopHeader: null,
224        coepHeader: null
225    };
226
227    // Try to get response headers via fetch
228    // Note: This only works if the server includes them in Access-Control-Expose-Headers
229    return JSON.stringify(caps);
230})();
231"#
232    }
233
234    /// Parse capabilities from JSON response
235    ///
236    /// # Errors
237    /// Returns error if JSON parsing fails
238    pub fn from_json(json: &str) -> Result<Self, CapabilityError> {
239        let parsed: serde_json::Value =
240            serde_json::from_str(json).map_err(|e| CapabilityError::ParseError(e.to_string()))?;
241
242        Ok(Self {
243            cross_origin_isolated: parsed["crossOriginIsolated"].as_bool().unwrap_or(false),
244            shared_array_buffer: parsed["sharedArrayBuffer"].as_bool().unwrap_or(false),
245            atomics: parsed["atomics"].as_bool().unwrap_or(false),
246            hardware_concurrency: parsed["hardwareConcurrency"].as_u64().unwrap_or(1) as u32,
247            is_secure_context: parsed["isSecureContext"].as_bool().unwrap_or(false),
248            coop_header: parsed["coopHeader"].as_str().map(String::from),
249            coep_header: parsed["coepHeader"].as_str().map(String::from),
250            errors: vec![],
251        })
252    }
253
254    /// Detect capabilities from a CDP page
255    ///
256    /// Executes JavaScript in the browser context to detect threading capabilities.
257    ///
258    /// # Example
259    /// ```ignore
260    /// use jugar_probar::WasmThreadCapabilities;
261    ///
262    /// let caps = WasmThreadCapabilities::detect(&page).await?;
263    /// caps.assert_threading_ready()?;
264    /// ```
265    #[cfg(feature = "browser")]
266    pub async fn detect(page: &chromiumoxide::Page) -> Result<Self, CapabilityError> {
267        let js = Self::detection_js();
268        let result: String = page
269            .evaluate(js)
270            .await
271            .map_err(|e| CapabilityError::ParseError(format!("CDP evaluation failed: {e}")))?
272            .into_value()
273            .map_err(|e| CapabilityError::ParseError(format!("Value extraction failed: {e}")))?;
274
275        Self::from_json(&result)
276    }
277
278    /// Detect capabilities and assert threading is ready
279    ///
280    /// Convenience method that detects and validates in one call.
281    ///
282    /// # Errors
283    /// Returns error if detection fails or threading requirements are not met
284    #[cfg(feature = "browser")]
285    pub async fn detect_and_assert(page: &chromiumoxide::Page) -> Result<Self, CapabilityError> {
286        let caps = Self::detect(page).await?;
287        caps.assert_threading_ready()?;
288        Ok(caps)
289    }
290}
291
292/// Error type for capability checks
293#[derive(Debug, Clone)]
294pub enum CapabilityError {
295    /// Threading requirements not met
296    ThreadingNotReady(Vec<String>),
297    /// Insufficient resources
298    InsufficientResources(String),
299    /// Parse error
300    ParseError(String),
301    /// Worker state mismatch
302    WorkerState {
303        /// Expected state
304        expected: String,
305        /// Actual state
306        actual: String,
307    },
308}
309
310impl fmt::Display for CapabilityError {
311    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
312        match self {
313            Self::ThreadingNotReady(errors) => {
314                writeln!(f, "Threading not ready:")?;
315                for err in errors {
316                    writeln!(f, "  - {err}")?;
317                }
318                Ok(())
319            }
320            Self::InsufficientResources(msg) => write!(f, "Insufficient resources: {msg}"),
321            Self::ParseError(msg) => write!(f, "Parse error: {msg}"),
322            Self::WorkerState { expected, actual } => {
323                write!(
324                    f,
325                    "Worker state mismatch: expected {expected}, got {actual}"
326                )
327            }
328        }
329    }
330}
331
332impl std::error::Error for CapabilityError {}
333
334/// Worker state for state machine testing
335#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
336pub enum WorkerState {
337    /// Worker not yet initialized
338    Uninitialized,
339    /// Worker loading/initializing
340    Loading,
341    /// Worker ready for work
342    Ready,
343    /// Worker processing a task
344    Processing,
345    /// Worker encountered an error
346    Error,
347    /// Worker terminated
348    Terminated,
349}
350
351impl Default for WorkerState {
352    fn default() -> Self {
353        Self::Uninitialized
354    }
355}
356
357impl fmt::Display for WorkerState {
358    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
359        match self {
360            Self::Uninitialized => write!(f, "Uninitialized"),
361            Self::Loading => write!(f, "Loading"),
362            Self::Ready => write!(f, "Ready"),
363            Self::Processing => write!(f, "Processing"),
364            Self::Error => write!(f, "Error"),
365            Self::Terminated => write!(f, "Terminated"),
366        }
367    }
368}
369
370/// Worker message for injection/interception
371#[derive(Debug, Clone)]
372pub struct WorkerMessage {
373    /// Message type identifier
374    pub type_: String,
375    /// Message payload as JSON value
376    pub data: serde_json::Value,
377    /// Timestamp when message was created/received
378    pub timestamp: f64,
379}
380
381impl WorkerMessage {
382    /// Create a new worker message
383    #[must_use]
384    pub fn new(type_: impl Into<String>, data: serde_json::Value) -> Self {
385        Self {
386            type_: type_.into(),
387            data,
388            timestamp: 0.0,
389        }
390    }
391
392    /// Create with timestamp
393    #[must_use]
394    pub fn with_timestamp(mut self, timestamp: f64) -> Self {
395        self.timestamp = timestamp;
396        self
397    }
398}
399
400/// Web Worker emulator for testing message passing and state transitions
401///
402/// Implements Lamport (1978) message ordering guarantees for verification.
403///
404/// # Example
405/// ```
406/// use jugar_probar::capabilities::{WorkerEmulator, WorkerMessage, WorkerState};
407///
408/// let mut emulator = WorkerEmulator::new();
409/// emulator.spawn("audio_processor");
410///
411/// // Simulate worker initialization
412/// emulator.send(WorkerMessage::new("Init", serde_json::json!({"model": "tiny"})));
413/// assert_eq!(emulator.state(), WorkerState::Loading);
414///
415/// emulator.receive_response(WorkerMessage::new("Ready", serde_json::json!({})));
416/// assert_eq!(emulator.state(), WorkerState::Ready);
417/// ```
418#[derive(Debug, Clone)]
419pub struct WorkerEmulator {
420    /// Current worker state
421    state: WorkerState,
422    /// Worker name/identifier
423    name: String,
424    /// Message queue (ordered)
425    message_queue: Vec<WorkerMessage>,
426    /// Response queue
427    response_queue: Vec<WorkerMessage>,
428    /// Lamport timestamp for ordering
429    lamport_clock: u64,
430    /// Whether to simulate delays
431    simulate_delays: bool,
432    /// Message history for verification
433    history: Vec<(u64, String, String)>, // (timestamp, direction, type)
434}
435
436impl Default for WorkerEmulator {
437    fn default() -> Self {
438        Self::new()
439    }
440}
441
442impl WorkerEmulator {
443    /// Create a new worker emulator
444    #[must_use]
445    pub fn new() -> Self {
446        Self {
447            state: WorkerState::Uninitialized,
448            name: String::new(),
449            message_queue: Vec::new(),
450            response_queue: Vec::new(),
451            lamport_clock: 0,
452            simulate_delays: false,
453            history: Vec::new(),
454        }
455    }
456
457    /// Spawn a named worker
458    pub fn spawn(&mut self, name: impl Into<String>) {
459        self.name = name.into();
460        self.state = WorkerState::Loading;
461        self.lamport_clock += 1;
462        self.history
463            .push((self.lamport_clock, "spawn".to_string(), self.name.clone()));
464    }
465
466    /// Get current worker state
467    #[must_use]
468    pub fn state(&self) -> WorkerState {
469        self.state
470    }
471
472    /// Get worker name
473    #[must_use]
474    pub fn name(&self) -> &str {
475        &self.name
476    }
477
478    /// Send a message to the worker
479    pub fn send(&mut self, message: WorkerMessage) {
480        self.lamport_clock += 1;
481        self.history.push((
482            self.lamport_clock,
483            "send".to_string(),
484            message.type_.clone(),
485        ));
486        self.message_queue.push(message);
487
488        // Update state based on message type
489        match self.state {
490            WorkerState::Uninitialized => {
491                self.state = WorkerState::Loading;
492            }
493            WorkerState::Ready => {
494                self.state = WorkerState::Processing;
495            }
496            _ => {}
497        }
498    }
499
500    /// Receive a response from the worker
501    pub fn receive_response(&mut self, response: WorkerMessage) {
502        self.lamport_clock += 1;
503        self.history.push((
504            self.lamport_clock,
505            "receive".to_string(),
506            response.type_.clone(),
507        ));
508
509        // Update state based on response type
510        if response.type_ == "Ready" || response.type_ == "ready" {
511            self.state = WorkerState::Ready;
512        } else if response.type_ == "Error" || response.type_ == "error" {
513            self.state = WorkerState::Error;
514        } else if response.type_ == "Complete" || response.type_ == "complete" {
515            self.state = WorkerState::Ready;
516        }
517
518        self.response_queue.push(response);
519    }
520
521    /// Terminate the worker
522    pub fn terminate(&mut self) {
523        self.lamport_clock += 1;
524        self.history
525            .push((self.lamport_clock, "terminate".to_string(), String::new()));
526        self.state = WorkerState::Terminated;
527    }
528
529    /// Get pending messages
530    #[must_use]
531    pub fn pending_messages(&self) -> &[WorkerMessage] {
532        &self.message_queue
533    }
534
535    /// Get received responses
536    #[must_use]
537    pub fn responses(&self) -> &[WorkerMessage] {
538        &self.response_queue
539    }
540
541    /// Get current Lamport timestamp
542    #[must_use]
543    pub fn lamport_time(&self) -> u64 {
544        self.lamport_clock
545    }
546
547    /// Get message history for verification
548    #[must_use]
549    pub fn history(&self) -> &[(u64, String, String)] {
550        &self.history
551    }
552
553    /// Enable delay simulation
554    pub fn with_delays(mut self, enable: bool) -> Self {
555        self.simulate_delays = enable;
556        self
557    }
558
559    /// Clear all queues
560    pub fn clear(&mut self) {
561        self.message_queue.clear();
562        self.response_queue.clear();
563    }
564
565    /// Verify message ordering (Lamport guarantee)
566    ///
567    /// Returns true if all messages maintain causal ordering.
568    #[must_use]
569    pub fn verify_ordering(&self) -> bool {
570        let mut last_time = 0u64;
571        for (time, _, _) in &self.history {
572            if *time <= last_time {
573                return false;
574            }
575            last_time = *time;
576        }
577        true
578    }
579
580    /// Assert worker is in expected state
581    ///
582    /// # Errors
583    /// Returns error if worker is not in expected state.
584    pub fn assert_state(&self, expected: WorkerState) -> Result<(), CapabilityError> {
585        if self.state == expected {
586            Ok(())
587        } else {
588            Err(CapabilityError::WorkerState {
589                expected: format!("{}", expected),
590                actual: format!("{}", self.state),
591            })
592        }
593    }
594
595    /// Create emulator with simulated worker ready
596    #[must_use]
597    pub fn ready(name: impl Into<String>) -> Self {
598        let mut emulator = Self::new();
599        emulator.spawn(name);
600        emulator.receive_response(WorkerMessage::new("Ready", serde_json::json!({})));
601        emulator
602    }
603
604    /// Generate JavaScript to intercept Worker constructor
605    #[must_use]
606    pub fn interception_js() -> &'static str {
607        r#"
608(function() {
609    const originalWorker = window.Worker;
610    window.__PROBAR_WORKERS__ = [];
611    window.__PROBAR_WORKER_REFS__ = [];
612    window.__PROBAR_WORKER_MESSAGES__ = [];
613
614    window.Worker = function(url, options) {
615        const worker = new originalWorker(url, options);
616        const id = window.__PROBAR_WORKERS__.length;
617
618        window.__PROBAR_WORKERS__.push({
619            id: id,
620            url: url,
621            state: 'loading',
622            messages: []
623        });
624        window.__PROBAR_WORKER_REFS__.push(worker);
625
626        const originalPostMessage = worker.postMessage.bind(worker);
627        worker.postMessage = function(data, transfer) {
628            window.__PROBAR_WORKER_MESSAGES__.push({
629                workerId: id,
630                direction: 'send',
631                data: JSON.stringify(data),
632                timestamp: performance.now()
633            });
634            return originalPostMessage(data, transfer);
635        };
636
637        worker.addEventListener('message', function(e) {
638            window.__PROBAR_WORKER_MESSAGES__.push({
639                workerId: id,
640                direction: 'receive',
641                data: JSON.stringify(e.data),
642                timestamp: performance.now()
643            });
644            if (e.data && (e.data.type === 'ready' || e.data.type === 'Ready')) {
645                window.__PROBAR_WORKERS__[id].state = 'ready';
646            }
647        });
648
649        worker.addEventListener('error', function(e) {
650            window.__PROBAR_WORKERS__[id].state = 'error';
651            window.__PROBAR_WORKER_MESSAGES__.push({
652                workerId: id,
653                direction: 'error',
654                data: e.message,
655                timestamp: performance.now()
656            });
657        });
658
659        return worker;
660    };
661})();
662"#
663    }
664
665    /// Attach worker interception to a CDP page
666    ///
667    /// Injects Worker constructor interception to track all worker messages.
668    ///
669    /// # Example
670    /// ```ignore
671    /// use jugar_probar::WorkerEmulator;
672    ///
673    /// WorkerEmulator::attach_cdp(&page).await?;
674    /// // ... run test that creates workers ...
675    /// let workers = WorkerEmulator::get_workers_cdp(&page).await?;
676    /// ```
677    #[cfg(feature = "browser")]
678    pub async fn attach_cdp(page: &chromiumoxide::Page) -> Result<(), CapabilityError> {
679        let js = Self::interception_js();
680        page.evaluate(js)
681            .await
682            .map_err(|e| CapabilityError::ParseError(format!("CDP attach failed: {e}")))?;
683
684        Ok(())
685    }
686
687    /// Get list of workers created on the CDP page
688    #[cfg(feature = "browser")]
689    pub async fn get_workers_cdp(
690        page: &chromiumoxide::Page,
691    ) -> Result<Vec<serde_json::Value>, CapabilityError> {
692        let json: String = page
693            .evaluate("JSON.stringify(window.__PROBAR_WORKERS__ || [])")
694            .await
695            .map_err(|e| CapabilityError::ParseError(format!("CDP query failed: {e}")))?
696            .into_value()
697            .unwrap_or_else(|_| "[]".to_string());
698
699        serde_json::from_str(&json).map_err(|e| CapabilityError::ParseError(e.to_string()))
700    }
701
702    /// Get worker message history from CDP page
703    #[cfg(feature = "browser")]
704    pub async fn get_messages_cdp(
705        page: &chromiumoxide::Page,
706    ) -> Result<Vec<serde_json::Value>, CapabilityError> {
707        let json: String = page
708            .evaluate("JSON.stringify(window.__PROBAR_WORKER_MESSAGES__ || [])")
709            .await
710            .map_err(|e| CapabilityError::ParseError(format!("CDP query failed: {e}")))?
711            .into_value()
712            .unwrap_or_else(|_| "[]".to_string());
713
714        serde_json::from_str(&json).map_err(|e| CapabilityError::ParseError(e.to_string()))
715    }
716
717    /// Verify worker message ordering on CDP page (Lamport guarantee)
718    ///
719    /// Returns true if all messages maintain causal ordering by timestamp.
720    #[cfg(feature = "browser")]
721    pub async fn verify_ordering_cdp(page: &chromiumoxide::Page) -> Result<bool, CapabilityError> {
722        let messages = Self::get_messages_cdp(page).await?;
723        let mut last_time = 0.0_f64;
724
725        for msg in messages {
726            let time = msg["timestamp"].as_f64().unwrap_or(0.0);
727            if time < last_time {
728                return Ok(false);
729            }
730            last_time = time;
731        }
732
733        Ok(true)
734    }
735
736    /// Post a message to a worker via CDP
737    ///
738    /// # Arguments
739    /// * `worker_id` - Index of the worker in the workers array
740    /// * `data` - Message data to send
741    ///
742    /// # Errors
743    /// Returns error if CDP call fails or worker not found.
744    #[cfg(feature = "browser")]
745    pub async fn post_message_cdp(
746        page: &chromiumoxide::Page,
747        worker_id: usize,
748        data: &serde_json::Value,
749    ) -> Result<(), CapabilityError> {
750        let data_json = serde_json::to_string(data)
751            .map_err(|e| CapabilityError::ParseError(format!("JSON serialize failed: {e}")))?;
752
753        let js = format!(
754            r#"
755            (function() {{
756                const workers = window.__PROBAR_WORKERS__ || [];
757                if ({worker_id} >= workers.length) {{
758                    return {{ error: 'Worker not found: {worker_id}' }};
759                }}
760                // Get the actual worker reference
761                const workerRefs = window.__PROBAR_WORKER_REFS__ || [];
762                if (workerRefs[{worker_id}]) {{
763                    workerRefs[{worker_id}].postMessage({data_json});
764                    return {{ success: true }};
765                }}
766                return {{ error: 'Worker reference not available' }};
767            }})()
768            "#
769        );
770
771        let result: serde_json::Value = page
772            .evaluate(js)
773            .await
774            .map_err(|e| CapabilityError::ParseError(format!("CDP call failed: {e}")))?
775            .into_value()
776            .unwrap_or_else(|_| serde_json::json!({"error": "Unknown error"}));
777
778        if result.get("error").is_some() {
779            return Err(CapabilityError::ParseError(
780                result["error"].as_str().unwrap_or("Unknown").to_string(),
781            ));
782        }
783
784        Ok(())
785    }
786
787    /// Wait for a specific message type from a worker via CDP
788    ///
789    /// Polls the message log until a message with the specified type appears
790    /// or the timeout is reached.
791    ///
792    /// # Arguments
793    /// * `message_type` - The type of message to wait for
794    /// * `timeout` - Maximum time to wait
795    ///
796    /// # Errors
797    /// Returns error if timeout reached or CDP call fails.
798    #[cfg(feature = "browser")]
799    pub async fn wait_for_message_cdp(
800        page: &chromiumoxide::Page,
801        message_type: &str,
802        timeout: std::time::Duration,
803    ) -> Result<WorkerMessage, CapabilityError> {
804        let start = std::time::Instant::now();
805        let poll_interval = std::time::Duration::from_millis(50);
806
807        while start.elapsed() < timeout {
808            let messages = Self::get_messages_cdp(page).await?;
809
810            for msg in messages {
811                let data_str = msg["data"].as_str().unwrap_or("{}");
812                if let Ok(data) = serde_json::from_str::<serde_json::Value>(data_str) {
813                    let type_ = data["type"].as_str().unwrap_or("");
814                    if type_ == message_type || type_.eq_ignore_ascii_case(message_type) {
815                        return Ok(WorkerMessage {
816                            type_: type_.to_string(),
817                            data,
818                            timestamp: msg["timestamp"].as_f64().unwrap_or(0.0),
819                        });
820                    }
821                }
822            }
823
824            tokio::time::sleep(poll_interval).await;
825        }
826
827        Err(CapabilityError::ParseError(format!(
828            "Timeout waiting for message type: {message_type}"
829        )))
830    }
831
832    /// Terminate a worker via CDP
833    ///
834    /// # Arguments
835    /// * `worker_id` - Index of the worker to terminate
836    ///
837    /// # Errors
838    /// Returns error if CDP call fails or worker not found.
839    #[cfg(feature = "browser")]
840    pub async fn terminate_cdp(
841        page: &chromiumoxide::Page,
842        worker_id: usize,
843    ) -> Result<(), CapabilityError> {
844        let js = format!(
845            r#"
846            (function() {{
847                const workerRefs = window.__PROBAR_WORKER_REFS__ || [];
848                if (workerRefs[{worker_id}]) {{
849                    workerRefs[{worker_id}].terminate();
850                    if (window.__PROBAR_WORKERS__ && window.__PROBAR_WORKERS__[{worker_id}]) {{
851                        window.__PROBAR_WORKERS__[{worker_id}].state = 'terminated';
852                    }}
853                    return {{ success: true }};
854                }}
855                return {{ error: 'Worker not found: {worker_id}' }};
856            }})()
857            "#
858        );
859
860        let result: serde_json::Value = page
861            .evaluate(js)
862            .await
863            .map_err(|e| CapabilityError::ParseError(format!("CDP call failed: {e}")))?
864            .into_value()
865            .unwrap_or_else(|_| serde_json::json!({"error": "Unknown error"}));
866
867        if result.get("error").is_some() {
868            return Err(CapabilityError::ParseError(
869                result["error"].as_str().unwrap_or("Unknown").to_string(),
870            ));
871        }
872
873        Ok(())
874    }
875
876    /// Assert message sequence occurred in order via CDP
877    ///
878    /// # Arguments
879    /// * `expected` - Expected message types in order
880    ///
881    /// # Errors
882    /// Returns error if sequence was not observed.
883    #[cfg(feature = "browser")]
884    pub async fn assert_message_order_cdp(
885        page: &chromiumoxide::Page,
886        expected: &[&str],
887    ) -> Result<(), CapabilityError> {
888        let messages = Self::get_messages_cdp(page).await?;
889
890        let mut expected_iter = expected.iter();
891        let mut current_expected = expected_iter.next();
892
893        for msg in &messages {
894            let data_str = msg["data"].as_str().unwrap_or("{}");
895            if let Ok(data) = serde_json::from_str::<serde_json::Value>(data_str) {
896                let type_ = data["type"].as_str().unwrap_or("");
897                if let Some(exp) = current_expected {
898                    if type_.eq_ignore_ascii_case(exp) {
899                        current_expected = expected_iter.next();
900                    }
901                }
902            }
903        }
904
905        if current_expected.is_some() {
906            return Err(CapabilityError::ParseError(format!(
907                "Message sequence not complete: still waiting for {:?}",
908                current_expected
909            )));
910        }
911
912        Ok(())
913    }
914}
915
916#[cfg(test)]
917#[allow(clippy::unwrap_used, clippy::expect_used)]
918mod tests {
919    use super::*;
920
921    // ========================================================================
922    // H1: Threading detection is reliable - Falsification tests
923    // ========================================================================
924
925    #[test]
926    fn f001_cross_origin_isolated_false() {
927        // Falsification: crossOriginIsolated=false should fail threading check
928        let caps = WasmThreadCapabilities {
929            cross_origin_isolated: false,
930            shared_array_buffer: true,
931            atomics: true,
932            is_secure_context: true,
933            coop_header: Some("same-origin".to_string()),
934            coep_header: Some("require-corp".to_string()),
935            ..Default::default()
936        };
937        let result = caps.assert_threading_ready();
938        assert!(result.is_err());
939        let err = result.unwrap_err();
940        assert!(err.to_string().contains("crossOriginIsolated"));
941    }
942
943    #[test]
944    fn f002_shared_array_buffer_undefined() {
945        // Falsification: SharedArrayBuffer undefined should fail
946        let caps = WasmThreadCapabilities {
947            cross_origin_isolated: true,
948            shared_array_buffer: false,
949            atomics: true,
950            is_secure_context: true,
951            coop_header: Some("same-origin".to_string()),
952            coep_header: Some("require-corp".to_string()),
953            ..Default::default()
954        };
955        let result = caps.assert_threading_ready();
956        assert!(result.is_err());
957        assert!(result
958            .unwrap_err()
959            .to_string()
960            .contains("SharedArrayBuffer"));
961    }
962
963    #[test]
964    fn f003_coop_header_missing() {
965        // Falsification: Missing COOP header should provide fix hint
966        let caps = WasmThreadCapabilities {
967            cross_origin_isolated: true,
968            shared_array_buffer: true,
969            atomics: true,
970            is_secure_context: true,
971            coop_header: None,
972            coep_header: Some("require-corp".to_string()),
973            ..Default::default()
974        };
975        let result = caps.assert_threading_ready();
976        assert!(result.is_err());
977        let err = result.unwrap_err().to_string();
978        assert!(err.contains("COOP"));
979        assert!(err.contains("Cross-Origin-Opener-Policy")); // Fix hint
980    }
981
982    #[test]
983    fn f004_coep_header_wrong() {
984        // Falsification: Wrong COEP value should fail
985        let caps = WasmThreadCapabilities {
986            cross_origin_isolated: true,
987            shared_array_buffer: true,
988            atomics: true,
989            is_secure_context: true,
990            coop_header: Some("same-origin".to_string()),
991            coep_header: Some("wrong-value".to_string()),
992            ..Default::default()
993        };
994        let result = caps.assert_threading_ready();
995        assert!(result.is_err());
996        let err = result.unwrap_err().to_string();
997        assert!(err.contains("COEP"));
998        assert!(err.contains("wrong-value"));
999    }
1000
1001    #[test]
1002    fn f005_atomics_blocked() {
1003        // Falsification: Atomics blocked should fail
1004        let caps = WasmThreadCapabilities {
1005            cross_origin_isolated: true,
1006            shared_array_buffer: true,
1007            atomics: false,
1008            is_secure_context: true,
1009            coop_header: Some("same-origin".to_string()),
1010            coep_header: Some("require-corp".to_string()),
1011            ..Default::default()
1012        };
1013        let result = caps.assert_threading_ready();
1014        assert!(result.is_err());
1015        assert!(result.unwrap_err().to_string().contains("Atomics"));
1016    }
1017
1018    // ========================================================================
1019    // H2: Thread pool initialization is safe - Falsification tests
1020    // ========================================================================
1021
1022    #[test]
1023    fn f006_zero_hardware_concurrency() {
1024        // Falsification: Zero cores should return 1 optimal thread
1025        let caps = WasmThreadCapabilities {
1026            hardware_concurrency: 0,
1027            ..Default::default()
1028        };
1029        assert_eq!(caps.optimal_threads(), 1);
1030    }
1031
1032    #[test]
1033    fn f007_many_cores() {
1034        // Falsification: 256 cores should be capped at 8
1035        let caps = WasmThreadCapabilities {
1036            hardware_concurrency: 256,
1037            ..Default::default()
1038        };
1039        assert_eq!(caps.optimal_threads(), 8);
1040    }
1041
1042    #[test]
1043    fn f008_single_core_streaming() {
1044        // Falsification: Single core should fail streaming check
1045        let mut caps = WasmThreadCapabilities::full_support();
1046        caps.hardware_concurrency = 1;
1047        let result = caps.assert_streaming_ready();
1048        assert!(result.is_err());
1049        assert!(result.unwrap_err().to_string().contains("2 CPU cores"));
1050    }
1051
1052    // ========================================================================
1053    // H3: Worker message protocol is robust - Falsification tests
1054    // ========================================================================
1055
1056    #[test]
1057    fn f011_worker_message_creation() {
1058        // Verify worker message creation
1059        let msg = WorkerMessage::new("Init", serde_json::json!({"model": "tiny"}));
1060        assert_eq!(msg.type_, "Init");
1061        assert!(msg.timestamp.abs() < f64::EPSILON);
1062    }
1063
1064    #[test]
1065    fn f012_worker_message_timestamp() {
1066        // Verify timestamp handling
1067        let msg =
1068            WorkerMessage::new("Transcribe", serde_json::json!({})).with_timestamp(1234567.89);
1069        assert!((msg.timestamp - 1234567.89).abs() < f64::EPSILON);
1070    }
1071
1072    // ========================================================================
1073    // Unit tests for core functionality
1074    // ========================================================================
1075
1076    #[test]
1077    fn test_full_support() {
1078        let caps = WasmThreadCapabilities::full_support();
1079        assert!(caps.is_threading_available());
1080        assert!(caps.assert_threading_ready().is_ok());
1081        assert!(caps.assert_streaming_ready().is_ok());
1082    }
1083
1084    #[test]
1085    fn test_no_support() {
1086        let caps = WasmThreadCapabilities::no_support();
1087        assert!(!caps.is_threading_available());
1088        assert!(caps.assert_threading_ready().is_err());
1089    }
1090
1091    #[test]
1092    fn test_optimal_threads_calculation() {
1093        // 4 cores -> 3 threads
1094        let caps = WasmThreadCapabilities {
1095            hardware_concurrency: 4,
1096            ..Default::default()
1097        };
1098        assert_eq!(caps.optimal_threads(), 3);
1099
1100        // 8 cores -> 7 threads
1101        let caps = WasmThreadCapabilities {
1102            hardware_concurrency: 8,
1103            ..Default::default()
1104        };
1105        assert_eq!(caps.optimal_threads(), 7);
1106
1107        // 16 cores -> 8 threads (capped)
1108        let caps = WasmThreadCapabilities {
1109            hardware_concurrency: 16,
1110            ..Default::default()
1111        };
1112        assert_eq!(caps.optimal_threads(), 8);
1113    }
1114
1115    #[test]
1116    fn test_capability_status() {
1117        let caps = WasmThreadCapabilities::full_support();
1118        assert_eq!(
1119            caps.shared_array_buffer_status(),
1120            CapabilityStatus::Available
1121        );
1122
1123        let caps = WasmThreadCapabilities::no_support();
1124        matches!(
1125            caps.shared_array_buffer_status(),
1126            CapabilityStatus::Unavailable(_)
1127        );
1128    }
1129
1130    #[test]
1131    fn test_from_json() {
1132        let json = r#"{
1133            "crossOriginIsolated": true,
1134            "sharedArrayBuffer": true,
1135            "atomics": true,
1136            "hardwareConcurrency": 8,
1137            "isSecureContext": true
1138        }"#;
1139
1140        let caps = WasmThreadCapabilities::from_json(json).unwrap();
1141        assert!(caps.cross_origin_isolated);
1142        assert!(caps.shared_array_buffer);
1143        assert!(caps.atomics);
1144        assert_eq!(caps.hardware_concurrency, 8);
1145        assert!(caps.is_secure_context);
1146    }
1147
1148    #[test]
1149    fn test_from_json_invalid() {
1150        let result = WasmThreadCapabilities::from_json("not json");
1151        assert!(result.is_err());
1152    }
1153
1154    #[test]
1155    fn test_worker_state_display() {
1156        assert_eq!(format!("{}", WorkerState::Uninitialized), "Uninitialized");
1157        assert_eq!(format!("{}", WorkerState::Ready), "Ready");
1158        assert_eq!(format!("{}", WorkerState::Processing), "Processing");
1159    }
1160
1161    #[test]
1162    fn test_detection_js() {
1163        let js = WasmThreadCapabilities::detection_js();
1164        assert!(js.contains("crossOriginIsolated"));
1165        assert!(js.contains("SharedArrayBuffer"));
1166        assert!(js.contains("hardwareConcurrency"));
1167    }
1168
1169    #[test]
1170    fn test_required_headers() {
1171        assert_eq!(RequiredHeaders::COOP, "same-origin");
1172        assert_eq!(RequiredHeaders::COEP, "require-corp");
1173    }
1174
1175    // ========================================================================
1176    // WorkerEmulator tests (H3: Worker message protocol)
1177    // ========================================================================
1178
1179    #[test]
1180    fn f009_worker_spawn_state() {
1181        // Falsification: spawn should transition to Loading state
1182        let mut emulator = WorkerEmulator::new();
1183        assert_eq!(emulator.state(), WorkerState::Uninitialized);
1184
1185        emulator.spawn("test_worker");
1186        assert_eq!(emulator.state(), WorkerState::Loading);
1187        assert_eq!(emulator.name(), "test_worker");
1188    }
1189
1190    #[test]
1191    fn f010_worker_ready_transition() {
1192        // Falsification: Ready message should transition to Ready state
1193        let mut emulator = WorkerEmulator::new();
1194        emulator.spawn("audio_worker");
1195        emulator.receive_response(WorkerMessage::new("Ready", serde_json::json!({})));
1196        assert_eq!(emulator.state(), WorkerState::Ready);
1197    }
1198
1199    #[test]
1200    fn f013_worker_message_ordering() {
1201        // Falsification: Messages must maintain Lamport ordering
1202        let mut emulator = WorkerEmulator::new();
1203        emulator.spawn("worker");
1204        emulator.send(WorkerMessage::new("Init", serde_json::json!({})));
1205        emulator.receive_response(WorkerMessage::new("Ready", serde_json::json!({})));
1206        emulator.send(WorkerMessage::new("Transcribe", serde_json::json!({})));
1207        emulator.terminate();
1208
1209        assert!(emulator.verify_ordering());
1210        assert_eq!(emulator.lamport_time(), 5);
1211    }
1212
1213    #[test]
1214    fn f014_worker_error_state() {
1215        // Falsification: Error response should transition to Error state
1216        let mut emulator = WorkerEmulator::new();
1217        emulator.spawn("worker");
1218        emulator.receive_response(WorkerMessage::new(
1219            "Error",
1220            serde_json::json!({"msg": "OOM"}),
1221        ));
1222        assert_eq!(emulator.state(), WorkerState::Error);
1223    }
1224
1225    #[test]
1226    fn f015_worker_terminate_state() {
1227        // Falsification: Terminate should transition to Terminated state
1228        let emulator = WorkerEmulator::ready("worker");
1229        assert_eq!(emulator.state(), WorkerState::Ready);
1230
1231        let mut emulator = emulator;
1232        emulator.terminate();
1233        assert_eq!(emulator.state(), WorkerState::Terminated);
1234    }
1235
1236    #[test]
1237    fn test_worker_assert_state() {
1238        let emulator = WorkerEmulator::ready("test");
1239        assert!(emulator.assert_state(WorkerState::Ready).is_ok());
1240        assert!(emulator.assert_state(WorkerState::Processing).is_err());
1241    }
1242
1243    #[test]
1244    fn test_worker_pending_messages() {
1245        let mut emulator = WorkerEmulator::ready("test");
1246        emulator.send(WorkerMessage::new(
1247            "Process",
1248            serde_json::json!({"data": [1,2,3]}),
1249        ));
1250        assert_eq!(emulator.pending_messages().len(), 1);
1251        assert_eq!(emulator.pending_messages()[0].type_, "Process");
1252    }
1253
1254    #[test]
1255    fn test_worker_clear() {
1256        let mut emulator = WorkerEmulator::ready("test");
1257        emulator.send(WorkerMessage::new("Process", serde_json::json!({})));
1258        emulator.clear();
1259        assert!(emulator.pending_messages().is_empty());
1260    }
1261
1262    // ========================================================================
1263    // Additional coverage tests for CapabilityError Display
1264    // ========================================================================
1265
1266    #[test]
1267    fn test_capability_error_display_threading_not_ready() {
1268        let err =
1269            CapabilityError::ThreadingNotReady(vec!["Error 1".to_string(), "Error 2".to_string()]);
1270        let display = format!("{}", err);
1271        assert!(display.contains("Threading not ready"));
1272        assert!(display.contains("Error 1"));
1273        assert!(display.contains("Error 2"));
1274    }
1275
1276    #[test]
1277    fn test_capability_error_display_insufficient_resources() {
1278        let err = CapabilityError::InsufficientResources("Not enough memory".to_string());
1279        let display = format!("{}", err);
1280        assert!(display.contains("Insufficient resources"));
1281        assert!(display.contains("Not enough memory"));
1282    }
1283
1284    #[test]
1285    fn test_capability_error_display_parse_error() {
1286        let err = CapabilityError::ParseError("Invalid JSON".to_string());
1287        let display = format!("{}", err);
1288        assert!(display.contains("Parse error"));
1289        assert!(display.contains("Invalid JSON"));
1290    }
1291
1292    #[test]
1293    fn test_capability_error_display_worker_state() {
1294        let err = CapabilityError::WorkerState {
1295            expected: "Ready".to_string(),
1296            actual: "Loading".to_string(),
1297        };
1298        let display = format!("{}", err);
1299        assert!(display.contains("Worker state mismatch"));
1300        assert!(display.contains("Ready"));
1301        assert!(display.contains("Loading"));
1302    }
1303
1304    // ========================================================================
1305    // Additional coverage for WorkerState Display
1306    // ========================================================================
1307
1308    #[test]
1309    fn test_worker_state_display_all() {
1310        assert_eq!(format!("{}", WorkerState::Loading), "Loading");
1311        assert_eq!(format!("{}", WorkerState::Error), "Error");
1312        assert_eq!(format!("{}", WorkerState::Terminated), "Terminated");
1313    }
1314
1315    #[test]
1316    fn test_worker_state_default() {
1317        let state = WorkerState::default();
1318        assert_eq!(state, WorkerState::Uninitialized);
1319    }
1320
1321    // ========================================================================
1322    // Additional coverage for shared_array_buffer_status
1323    // ========================================================================
1324
1325    #[test]
1326    fn test_sab_status_not_secure_context() {
1327        let caps = WasmThreadCapabilities {
1328            shared_array_buffer: false,
1329            is_secure_context: false,
1330            cross_origin_isolated: true,
1331            ..Default::default()
1332        };
1333        let status = caps.shared_array_buffer_status();
1334        assert!(
1335            matches!(status, CapabilityStatus::Unavailable(msg) if msg.contains("secure context") || msg.contains("HTTPS"))
1336        );
1337    }
1338
1339    #[test]
1340    fn test_sab_status_not_cross_origin_isolated() {
1341        let caps = WasmThreadCapabilities {
1342            shared_array_buffer: false,
1343            is_secure_context: true,
1344            cross_origin_isolated: false,
1345            ..Default::default()
1346        };
1347        let status = caps.shared_array_buffer_status();
1348        assert!(
1349            matches!(status, CapabilityStatus::Unavailable(msg) if msg.contains("crossOriginIsolated"))
1350        );
1351    }
1352
1353    #[test]
1354    fn test_sab_status_unknown_reason() {
1355        let caps = WasmThreadCapabilities {
1356            shared_array_buffer: false,
1357            is_secure_context: true,
1358            cross_origin_isolated: true,
1359            ..Default::default()
1360        };
1361        let status = caps.shared_array_buffer_status();
1362        assert!(matches!(status, CapabilityStatus::Unavailable(msg) if msg.contains("Unknown")));
1363    }
1364
1365    // ========================================================================
1366    // Additional coverage for WorkerEmulator
1367    // ========================================================================
1368
1369    #[test]
1370    fn test_worker_with_delays() {
1371        let emulator = WorkerEmulator::new().with_delays(true);
1372        // Just verify it doesn't panic and creates the emulator
1373        assert_eq!(emulator.state(), WorkerState::Uninitialized);
1374    }
1375
1376    #[test]
1377    fn test_worker_responses() {
1378        let emulator = WorkerEmulator::ready("test");
1379        // The ready() method adds a Ready response
1380        assert!(!emulator.responses().is_empty());
1381        assert_eq!(emulator.responses()[0].type_, "Ready");
1382    }
1383
1384    #[test]
1385    fn test_worker_history() {
1386        let mut emulator = WorkerEmulator::new();
1387        emulator.spawn("worker");
1388        emulator.send(WorkerMessage::new("Test", serde_json::json!({})));
1389        let history = emulator.history();
1390        assert!(!history.is_empty());
1391        // First entry should be spawn
1392        assert_eq!(history[0].1, "spawn");
1393    }
1394
1395    #[test]
1396    fn test_worker_send_from_uninitialized() {
1397        let mut emulator = WorkerEmulator::new();
1398        emulator.send(WorkerMessage::new("Init", serde_json::json!({})));
1399        // Sending from Uninitialized should transition to Loading
1400        assert_eq!(emulator.state(), WorkerState::Loading);
1401    }
1402
1403    #[test]
1404    fn test_worker_send_from_ready() {
1405        let mut emulator = WorkerEmulator::ready("test");
1406        emulator.send(WorkerMessage::new("Process", serde_json::json!({})));
1407        // Sending from Ready should transition to Processing
1408        assert_eq!(emulator.state(), WorkerState::Processing);
1409    }
1410
1411    #[test]
1412    fn test_worker_receive_complete() {
1413        let mut emulator = WorkerEmulator::ready("test");
1414        emulator.send(WorkerMessage::new("Process", serde_json::json!({})));
1415        assert_eq!(emulator.state(), WorkerState::Processing);
1416        emulator.receive_response(WorkerMessage::new("Complete", serde_json::json!({})));
1417        // Complete should transition back to Ready
1418        assert_eq!(emulator.state(), WorkerState::Ready);
1419    }
1420
1421    #[test]
1422    fn test_worker_receive_lowercase() {
1423        let mut emulator = WorkerEmulator::new();
1424        emulator.spawn("test");
1425        // Test lowercase "ready"
1426        emulator.receive_response(WorkerMessage::new("ready", serde_json::json!({})));
1427        assert_eq!(emulator.state(), WorkerState::Ready);
1428    }
1429
1430    #[test]
1431    fn test_worker_receive_lowercase_error() {
1432        let mut emulator = WorkerEmulator::new();
1433        emulator.spawn("test");
1434        // Test lowercase "error"
1435        emulator.receive_response(WorkerMessage::new("error", serde_json::json!({})));
1436        assert_eq!(emulator.state(), WorkerState::Error);
1437    }
1438
1439    #[test]
1440    fn test_worker_receive_lowercase_complete() {
1441        let mut emulator = WorkerEmulator::ready("test");
1442        emulator.send(WorkerMessage::new("Process", serde_json::json!({})));
1443        // Test lowercase "complete"
1444        emulator.receive_response(WorkerMessage::new("complete", serde_json::json!({})));
1445        assert_eq!(emulator.state(), WorkerState::Ready);
1446    }
1447
1448    #[test]
1449    fn test_interception_js() {
1450        let js = WorkerEmulator::interception_js();
1451        assert!(js.contains("originalWorker"));
1452        assert!(js.contains("__PROBAR_WORKERS__"));
1453        assert!(js.contains("postMessage"));
1454    }
1455
1456    #[test]
1457    fn test_from_json_with_headers() {
1458        let json = r#"{
1459            "crossOriginIsolated": true,
1460            "sharedArrayBuffer": true,
1461            "atomics": true,
1462            "hardwareConcurrency": 4,
1463            "isSecureContext": true,
1464            "coopHeader": "same-origin",
1465            "coepHeader": "require-corp"
1466        }"#;
1467        let caps = WasmThreadCapabilities::from_json(json).unwrap();
1468        assert_eq!(caps.coop_header, Some("same-origin".to_string()));
1469        assert_eq!(caps.coep_header, Some("require-corp".to_string()));
1470    }
1471
1472    #[test]
1473    fn test_from_json_defaults() {
1474        // Test with minimal JSON - should use defaults for missing fields
1475        let json = r#"{}"#;
1476        let caps = WasmThreadCapabilities::from_json(json).unwrap();
1477        assert!(!caps.cross_origin_isolated);
1478        assert!(!caps.shared_array_buffer);
1479        assert!(!caps.atomics);
1480        assert_eq!(caps.hardware_concurrency, 1);
1481        assert!(!caps.is_secure_context);
1482    }
1483
1484    #[test]
1485    fn test_capability_status_eq() {
1486        assert_eq!(CapabilityStatus::Available, CapabilityStatus::Available);
1487        assert_eq!(CapabilityStatus::Unknown, CapabilityStatus::Unknown);
1488        assert_eq!(
1489            CapabilityStatus::Unavailable("test".to_string()),
1490            CapabilityStatus::Unavailable("test".to_string())
1491        );
1492        assert_ne!(CapabilityStatus::Available, CapabilityStatus::Unknown);
1493    }
1494
1495    #[test]
1496    fn test_assert_threading_not_secure() {
1497        // Test that non-secure context fails threading check
1498        let caps = WasmThreadCapabilities {
1499            cross_origin_isolated: true,
1500            shared_array_buffer: true,
1501            atomics: true,
1502            is_secure_context: false,
1503            coop_header: Some("same-origin".to_string()),
1504            coep_header: Some("require-corp".to_string()),
1505            ..Default::default()
1506        };
1507        let result = caps.assert_threading_ready();
1508        assert!(result.is_err());
1509        assert!(result.unwrap_err().to_string().contains("HTTPS"));
1510    }
1511
1512    #[test]
1513    fn test_assert_threading_wrong_coop() {
1514        let caps = WasmThreadCapabilities {
1515            cross_origin_isolated: true,
1516            shared_array_buffer: true,
1517            atomics: true,
1518            is_secure_context: true,
1519            coop_header: Some("wrong-value".to_string()),
1520            coep_header: Some("require-corp".to_string()),
1521            ..Default::default()
1522        };
1523        let result = caps.assert_threading_ready();
1524        assert!(result.is_err());
1525        let err = result.unwrap_err().to_string();
1526        assert!(err.contains("COOP"));
1527        assert!(err.contains("wrong-value"));
1528    }
1529
1530    #[test]
1531    fn test_assert_threading_missing_coep() {
1532        let caps = WasmThreadCapabilities {
1533            cross_origin_isolated: true,
1534            shared_array_buffer: true,
1535            atomics: true,
1536            is_secure_context: true,
1537            coop_header: Some("same-origin".to_string()),
1538            coep_header: None,
1539            ..Default::default()
1540        };
1541        let result = caps.assert_threading_ready();
1542        assert!(result.is_err());
1543        let err = result.unwrap_err().to_string();
1544        assert!(err.contains("COEP"));
1545        assert!(err.contains("Cross-Origin-Embedder-Policy"));
1546    }
1547
1548    // ========================================================================
1549    // Additional coverage tests for WorkerEmulator
1550    // ========================================================================
1551
1552    #[test]
1553    fn test_worker_emulator_default() {
1554        let emulator = WorkerEmulator::default();
1555        assert_eq!(emulator.state(), WorkerState::Uninitialized);
1556        assert!(emulator.name().is_empty());
1557        assert!(emulator.pending_messages().is_empty());
1558        assert!(emulator.responses().is_empty());
1559        assert_eq!(emulator.lamport_time(), 0);
1560    }
1561
1562    #[test]
1563    fn test_worker_emulator_debug() {
1564        let emulator = WorkerEmulator::new();
1565        let debug_str = format!("{:?}", emulator);
1566        assert!(debug_str.contains("WorkerEmulator"));
1567    }
1568
1569    #[test]
1570    fn test_worker_emulator_clone() {
1571        let mut emulator = WorkerEmulator::new();
1572        emulator.spawn("test-worker");
1573        let cloned = emulator.clone();
1574        assert_eq!(emulator.name(), cloned.name());
1575        assert_eq!(emulator.state(), cloned.state());
1576    }
1577
1578    #[test]
1579    fn test_worker_send_from_processing_state() {
1580        let mut emulator = WorkerEmulator::ready("test");
1581        emulator.send(WorkerMessage::new("Task1", serde_json::json!({})));
1582        assert_eq!(emulator.state(), WorkerState::Processing);
1583        // Send another message while processing - state should remain Processing
1584        emulator.send(WorkerMessage::new("Task2", serde_json::json!({})));
1585        assert_eq!(emulator.state(), WorkerState::Processing);
1586    }
1587
1588    #[test]
1589    fn test_worker_send_from_error_state() {
1590        let mut emulator = WorkerEmulator::new();
1591        emulator.spawn("test");
1592        emulator.receive_response(WorkerMessage::new("Error", serde_json::json!({})));
1593        assert_eq!(emulator.state(), WorkerState::Error);
1594        // Send while in error state - should stay in Error
1595        emulator.send(WorkerMessage::new("Retry", serde_json::json!({})));
1596        assert_eq!(emulator.state(), WorkerState::Error);
1597    }
1598
1599    #[test]
1600    fn test_worker_send_from_terminated_state() {
1601        let mut emulator = WorkerEmulator::ready("test");
1602        emulator.terminate();
1603        assert_eq!(emulator.state(), WorkerState::Terminated);
1604        // Send while terminated - should stay Terminated
1605        emulator.send(WorkerMessage::new("Test", serde_json::json!({})));
1606        assert_eq!(emulator.state(), WorkerState::Terminated);
1607    }
1608
1609    #[test]
1610    fn test_worker_receive_unknown_type() {
1611        let mut emulator = WorkerEmulator::new();
1612        emulator.spawn("test");
1613        // Receive a message type that doesn't affect state
1614        emulator.receive_response(WorkerMessage::new("CustomType", serde_json::json!({})));
1615        // State should remain Loading since the message type is not recognized
1616        assert_eq!(emulator.state(), WorkerState::Loading);
1617    }
1618
1619    #[test]
1620    fn test_worker_verify_ordering_empty() {
1621        let emulator = WorkerEmulator::new();
1622        assert!(emulator.verify_ordering());
1623    }
1624
1625    #[test]
1626    fn test_worker_verify_ordering_single() {
1627        let mut emulator = WorkerEmulator::new();
1628        emulator.spawn("test");
1629        assert!(emulator.verify_ordering());
1630    }
1631
1632    #[test]
1633    fn test_worker_verify_ordering_fails_with_duplicate_timestamps() {
1634        // We can't easily create a scenario with duplicate timestamps
1635        // since the emulator auto-increments, but we can test the logic
1636        // by manually constructing an emulator with modified history
1637        let mut emulator = WorkerEmulator::new();
1638        // Add entries to history that would fail ordering check
1639        // This is testing the internal logic directly
1640        emulator.spawn("test");
1641        emulator.send(WorkerMessage::new("A", serde_json::json!({})));
1642        // All normal operations maintain ordering
1643        assert!(emulator.verify_ordering());
1644    }
1645
1646    // ========================================================================
1647    // Additional coverage tests for WasmThreadCapabilities
1648    // ========================================================================
1649
1650    #[test]
1651    fn test_wasm_thread_capabilities_default() {
1652        let caps = WasmThreadCapabilities::default();
1653        assert!(!caps.cross_origin_isolated);
1654        assert!(!caps.shared_array_buffer);
1655        assert!(!caps.atomics);
1656        assert_eq!(caps.hardware_concurrency, 0);
1657        assert!(caps.coop_header.is_none());
1658        assert!(caps.coep_header.is_none());
1659        assert!(!caps.is_secure_context);
1660        assert!(caps.errors.is_empty());
1661    }
1662
1663    #[test]
1664    fn test_wasm_thread_capabilities_debug() {
1665        let caps = WasmThreadCapabilities::full_support();
1666        let debug_str = format!("{:?}", caps);
1667        assert!(debug_str.contains("WasmThreadCapabilities"));
1668    }
1669
1670    #[test]
1671    fn test_wasm_thread_capabilities_clone() {
1672        let caps = WasmThreadCapabilities::full_support();
1673        let cloned = caps.clone();
1674        assert_eq!(caps.cross_origin_isolated, cloned.cross_origin_isolated);
1675        assert_eq!(caps.hardware_concurrency, cloned.hardware_concurrency);
1676    }
1677
1678    #[test]
1679    fn test_no_support_has_error() {
1680        let caps = WasmThreadCapabilities::no_support();
1681        assert!(!caps.errors.is_empty());
1682        assert!(caps.errors[0].contains("SharedArrayBuffer"));
1683    }
1684
1685    #[test]
1686    fn test_optimal_threads_one_core() {
1687        let caps = WasmThreadCapabilities {
1688            hardware_concurrency: 1,
1689            ..Default::default()
1690        };
1691        // 1 - 1 = 0, but clamped to minimum 1
1692        assert_eq!(caps.optimal_threads(), 1);
1693    }
1694
1695    #[test]
1696    fn test_optimal_threads_two_cores() {
1697        let caps = WasmThreadCapabilities {
1698            hardware_concurrency: 2,
1699            ..Default::default()
1700        };
1701        assert_eq!(caps.optimal_threads(), 1);
1702    }
1703
1704    #[test]
1705    fn test_assert_streaming_ready_success() {
1706        let caps = WasmThreadCapabilities::full_support();
1707        assert!(caps.assert_streaming_ready().is_ok());
1708    }
1709
1710    #[test]
1711    fn test_assert_streaming_ready_threading_fails() {
1712        let caps = WasmThreadCapabilities::no_support();
1713        let result = caps.assert_streaming_ready();
1714        assert!(result.is_err());
1715    }
1716
1717    // ========================================================================
1718    // Additional coverage tests for CapabilityStatus
1719    // ========================================================================
1720
1721    #[test]
1722    fn test_capability_status_debug() {
1723        let status = CapabilityStatus::Available;
1724        let debug_str = format!("{:?}", status);
1725        assert!(debug_str.contains("Available"));
1726
1727        let status = CapabilityStatus::Unknown;
1728        let debug_str = format!("{:?}", status);
1729        assert!(debug_str.contains("Unknown"));
1730
1731        let status = CapabilityStatus::Unavailable("test".to_string());
1732        let debug_str = format!("{:?}", status);
1733        assert!(debug_str.contains("Unavailable"));
1734    }
1735
1736    #[test]
1737    fn test_capability_status_clone() {
1738        let status = CapabilityStatus::Unavailable("reason".to_string());
1739        let cloned = status.clone();
1740        assert_eq!(status, cloned);
1741    }
1742
1743    // ========================================================================
1744    // Additional coverage tests for WorkerState
1745    // ========================================================================
1746
1747    #[test]
1748    fn test_worker_state_copy() {
1749        let state = WorkerState::Ready;
1750        let copied = state;
1751        assert_eq!(state, copied);
1752    }
1753
1754    #[test]
1755    fn test_worker_state_hash() {
1756        use std::collections::HashSet;
1757        let mut set = HashSet::new();
1758        set.insert(WorkerState::Ready);
1759        set.insert(WorkerState::Processing);
1760        assert!(set.contains(&WorkerState::Ready));
1761        assert!(set.contains(&WorkerState::Processing));
1762        assert!(!set.contains(&WorkerState::Error));
1763    }
1764
1765    // ========================================================================
1766    // Additional coverage tests for WorkerMessage
1767    // ========================================================================
1768
1769    #[test]
1770    fn test_worker_message_debug() {
1771        let msg = WorkerMessage::new("Test", serde_json::json!({}));
1772        let debug_str = format!("{:?}", msg);
1773        assert!(debug_str.contains("WorkerMessage"));
1774        assert!(debug_str.contains("Test"));
1775    }
1776
1777    #[test]
1778    fn test_worker_message_clone() {
1779        let msg =
1780            WorkerMessage::new("Test", serde_json::json!({"key": "value"})).with_timestamp(123.456);
1781        let cloned = msg.clone();
1782        assert_eq!(msg.type_, cloned.type_);
1783        assert_eq!(msg.data, cloned.data);
1784        assert!((msg.timestamp - cloned.timestamp).abs() < f64::EPSILON);
1785    }
1786
1787    // ========================================================================
1788    // Additional coverage tests for RequiredHeaders
1789    // ========================================================================
1790
1791    #[test]
1792    fn test_required_headers_debug() {
1793        let headers = RequiredHeaders;
1794        let debug_str = format!("{:?}", headers);
1795        assert!(debug_str.contains("RequiredHeaders"));
1796    }
1797
1798    #[test]
1799    fn test_required_headers_clone() {
1800        let headers = RequiredHeaders;
1801        let _ = headers;
1802        // Copy trait test
1803        let cloned = headers;
1804        let _ = cloned;
1805    }
1806
1807    // ========================================================================
1808    // Additional coverage tests for CapabilityError
1809    // ========================================================================
1810
1811    #[test]
1812    fn test_capability_error_debug() {
1813        let err = CapabilityError::ParseError("test".to_string());
1814        let debug_str = format!("{:?}", err);
1815        assert!(debug_str.contains("ParseError"));
1816    }
1817
1818    #[test]
1819    fn test_capability_error_clone() {
1820        let err = CapabilityError::InsufficientResources("memory".to_string());
1821        let cloned = err.clone();
1822        assert_eq!(err.to_string(), cloned.to_string());
1823    }
1824
1825    #[test]
1826    fn test_capability_error_is_error_trait() {
1827        let err: Box<dyn std::error::Error> =
1828            Box::new(CapabilityError::ParseError("test".to_string()));
1829        assert!(err.to_string().contains("Parse error"));
1830    }
1831
1832    #[test]
1833    fn test_capability_error_source() {
1834        use std::error::Error;
1835        let err = CapabilityError::ParseError("test".to_string());
1836        // source() should return None for this error type
1837        assert!(err.source().is_none());
1838    }
1839
1840    // ========================================================================
1841    // Edge case tests for from_json
1842    // ========================================================================
1843
1844    #[test]
1845    fn test_from_json_partial_fields() {
1846        let json = r#"{
1847            "crossOriginIsolated": true,
1848            "atomics": false
1849        }"#;
1850        let caps = WasmThreadCapabilities::from_json(json).unwrap();
1851        assert!(caps.cross_origin_isolated);
1852        assert!(!caps.atomics);
1853        // Other fields should default
1854        assert!(!caps.shared_array_buffer);
1855        assert_eq!(caps.hardware_concurrency, 1);
1856    }
1857
1858    #[test]
1859    fn test_from_json_null_values() {
1860        let json = r#"{
1861            "crossOriginIsolated": null,
1862            "sharedArrayBuffer": null,
1863            "hardwareConcurrency": null
1864        }"#;
1865        let caps = WasmThreadCapabilities::from_json(json).unwrap();
1866        // null should be treated as false/1
1867        assert!(!caps.cross_origin_isolated);
1868        assert!(!caps.shared_array_buffer);
1869        assert_eq!(caps.hardware_concurrency, 1);
1870    }
1871
1872    // ========================================================================
1873    // Edge case tests for assert_threading_ready
1874    // ========================================================================
1875
1876    #[test]
1877    fn test_assert_threading_multiple_failures() {
1878        let caps = WasmThreadCapabilities {
1879            cross_origin_isolated: false,
1880            shared_array_buffer: false,
1881            atomics: false,
1882            is_secure_context: false,
1883            coop_header: None,
1884            coep_header: None,
1885            ..Default::default()
1886        };
1887        let result = caps.assert_threading_ready();
1888        assert!(result.is_err());
1889        let err = result.unwrap_err().to_string();
1890        // Should contain multiple error messages
1891        assert!(err.contains("crossOriginIsolated"));
1892        assert!(err.contains("SharedArrayBuffer"));
1893        assert!(err.contains("Atomics"));
1894        assert!(err.contains("HTTPS"));
1895        assert!(err.contains("COOP"));
1896        assert!(err.contains("COEP"));
1897    }
1898
1899    // ========================================================================
1900    // Additional tests for complete coverage
1901    // ========================================================================
1902
1903    #[test]
1904    fn test_is_threading_available_partial() {
1905        // Test with only some flags true
1906        let caps = WasmThreadCapabilities {
1907            cross_origin_isolated: true,
1908            shared_array_buffer: true,
1909            atomics: false,
1910            is_secure_context: true,
1911            ..Default::default()
1912        };
1913        assert!(!caps.is_threading_available());
1914    }
1915
1916    #[test]
1917    fn test_assert_state_error_message() {
1918        let emulator = WorkerEmulator::ready("test");
1919        let result = emulator.assert_state(WorkerState::Processing);
1920        assert!(result.is_err());
1921        let err = result.unwrap_err();
1922        match err {
1923            CapabilityError::WorkerState { expected, actual } => {
1924                assert_eq!(expected, "Processing");
1925                assert_eq!(actual, "Ready");
1926            }
1927            _ => panic!("Expected WorkerState error"),
1928        }
1929    }
1930
1931    #[test]
1932    fn test_worker_send_from_loading() {
1933        let mut emulator = WorkerEmulator::new();
1934        emulator.spawn("test");
1935        assert_eq!(emulator.state(), WorkerState::Loading);
1936        // Send while loading - should stay in Loading (not Ready or Processing)
1937        emulator.send(WorkerMessage::new("Init", serde_json::json!({})));
1938        assert_eq!(emulator.state(), WorkerState::Loading);
1939    }
1940
1941    #[test]
1942    fn test_worker_multiple_responses() {
1943        let mut emulator = WorkerEmulator::new();
1944        emulator.spawn("test");
1945        emulator.receive_response(WorkerMessage::new("Progress", serde_json::json!({})));
1946        emulator.receive_response(WorkerMessage::new("Progress", serde_json::json!({})));
1947        emulator.receive_response(WorkerMessage::new("Ready", serde_json::json!({})));
1948        assert_eq!(emulator.responses().len(), 3);
1949        assert_eq!(emulator.state(), WorkerState::Ready);
1950    }
1951
1952    #[test]
1953    fn test_worker_lamport_increments() {
1954        let mut emulator = WorkerEmulator::new();
1955        assert_eq!(emulator.lamport_time(), 0);
1956        emulator.spawn("test");
1957        assert_eq!(emulator.lamport_time(), 1);
1958        emulator.send(WorkerMessage::new("A", serde_json::json!({})));
1959        assert_eq!(emulator.lamport_time(), 2);
1960        emulator.receive_response(WorkerMessage::new("B", serde_json::json!({})));
1961        assert_eq!(emulator.lamport_time(), 3);
1962        emulator.terminate();
1963        assert_eq!(emulator.lamport_time(), 4);
1964    }
1965
1966    #[test]
1967    fn test_worker_history_entries() {
1968        let mut emulator = WorkerEmulator::new();
1969        emulator.spawn("my-worker");
1970        emulator.send(WorkerMessage::new("Init", serde_json::json!({})));
1971        emulator.receive_response(WorkerMessage::new("Ready", serde_json::json!({})));
1972        emulator.terminate();
1973
1974        let history = emulator.history();
1975        assert_eq!(history.len(), 4);
1976
1977        assert_eq!(history[0].1, "spawn");
1978        assert_eq!(history[0].2, "my-worker");
1979
1980        assert_eq!(history[1].1, "send");
1981        assert_eq!(history[1].2, "Init");
1982
1983        assert_eq!(history[2].1, "receive");
1984        assert_eq!(history[2].2, "Ready");
1985
1986        assert_eq!(history[3].1, "terminate");
1987    }
1988
1989    #[test]
1990    fn test_worker_clear_preserves_state() {
1991        let mut emulator = WorkerEmulator::ready("test");
1992        emulator.send(WorkerMessage::new("Task", serde_json::json!({})));
1993        emulator.receive_response(WorkerMessage::new("Done", serde_json::json!({})));
1994
1995        let state_before = emulator.state();
1996        emulator.clear();
1997
1998        assert!(emulator.pending_messages().is_empty());
1999        assert!(emulator.responses().is_empty());
2000        // State should be preserved after clear
2001        assert_eq!(emulator.state(), state_before);
2002    }
2003
2004    // ========================================================================
2005    // Additional coverage tests
2006    // ========================================================================
2007
2008    #[test]
2009    fn test_shared_array_buffer_status_available() {
2010        let caps = WasmThreadCapabilities::full_support();
2011        assert_eq!(
2012            caps.shared_array_buffer_status(),
2013            CapabilityStatus::Available
2014        );
2015    }
2016
2017    #[test]
2018    fn test_shared_array_buffer_status_not_secure() {
2019        let caps = WasmThreadCapabilities {
2020            shared_array_buffer: false,
2021            is_secure_context: false,
2022            cross_origin_isolated: true,
2023            ..Default::default()
2024        };
2025        match caps.shared_array_buffer_status() {
2026            CapabilityStatus::Unavailable(reason) => {
2027                assert!(reason.contains("HTTPS"));
2028            }
2029            _ => panic!("Expected Unavailable"),
2030        }
2031    }
2032
2033    #[test]
2034    fn test_shared_array_buffer_status_not_cross_origin() {
2035        let caps = WasmThreadCapabilities {
2036            shared_array_buffer: false,
2037            is_secure_context: true,
2038            cross_origin_isolated: false,
2039            ..Default::default()
2040        };
2041        match caps.shared_array_buffer_status() {
2042            CapabilityStatus::Unavailable(reason) => {
2043                assert!(reason.contains("crossOriginIsolated"));
2044            }
2045            _ => panic!("Expected Unavailable"),
2046        }
2047    }
2048
2049    #[test]
2050    fn test_shared_array_buffer_status_unknown() {
2051        let caps = WasmThreadCapabilities {
2052            shared_array_buffer: false,
2053            is_secure_context: true,
2054            cross_origin_isolated: true,
2055            ..Default::default()
2056        };
2057        match caps.shared_array_buffer_status() {
2058            CapabilityStatus::Unavailable(reason) => {
2059                assert!(reason.contains("Unknown"));
2060            }
2061            _ => panic!("Expected Unavailable"),
2062        }
2063    }
2064
2065    #[test]
2066    fn test_from_json_valid_with_headers() {
2067        let json = r#"{
2068            "crossOriginIsolated": true,
2069            "sharedArrayBuffer": true,
2070            "atomics": true,
2071            "hardwareConcurrency": 8,
2072            "isSecureContext": true,
2073            "coopHeader": "same-origin",
2074            "coepHeader": "require-corp"
2075        }"#;
2076        let caps = WasmThreadCapabilities::from_json(json).unwrap();
2077        assert!(caps.cross_origin_isolated);
2078        assert!(caps.shared_array_buffer);
2079        assert!(caps.atomics);
2080        assert_eq!(caps.hardware_concurrency, 8);
2081        assert!(caps.is_secure_context);
2082        assert_eq!(caps.coop_header, Some("same-origin".to_string()));
2083        assert_eq!(caps.coep_header, Some("require-corp".to_string()));
2084    }
2085
2086    #[test]
2087    fn test_from_json_minimal_defaults() {
2088        let json = r#"{}"#;
2089        let caps = WasmThreadCapabilities::from_json(json).unwrap();
2090        assert!(!caps.cross_origin_isolated);
2091        assert!(!caps.shared_array_buffer);
2092        assert_eq!(caps.hardware_concurrency, 1);
2093    }
2094
2095    #[test]
2096    fn test_capability_status_unknown_match() {
2097        let status = CapabilityStatus::Unknown;
2098        assert!(matches!(status, CapabilityStatus::Unknown));
2099    }
2100
2101    #[test]
2102    fn test_required_headers_values() {
2103        assert_eq!(RequiredHeaders::COOP, "same-origin");
2104        assert_eq!(RequiredHeaders::COEP, "require-corp");
2105    }
2106}