Skip to main content

asupersync_conformance/
h2_data_end_stream_conformance.rs

1//! HTTP/2 DATA frame END_STREAM conformance testing.
2//!
3//! This harness exercises the asupersync HTTP/2 connection's DATA frame
4//! END_STREAM handling against RFC-backed expected states. The h2 reference
5//! side is not wired yet, so matching the local expected state is reported as
6//! XFAIL instead of a vendor-parity pass.
7
8use asupersync::bytes::{Bytes, BytesMut};
9use asupersync::http::h2::{
10    Connection, Header, HpackEncoder, Settings,
11    connection::ReceivedFrame,
12    frame::{DataFrame, Frame, HeadersFrame, SettingsFrame},
13};
14use serde::{Deserialize, Serialize};
15use std::fmt;
16
17const H2_REFERENCE_UNAVAILABLE: &str =
18    "h2 reference comparison unavailable in standalone frame harness";
19
20/// Test verdict for individual conformance cases.
21#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
22pub enum DataEndStreamTestVerdict {
23    Pass,
24    Fail,
25    ExpectedFailure, // Known divergence
26    Skipped,
27}
28
29impl fmt::Display for DataEndStreamTestVerdict {
30    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
31        match self {
32            Self::Pass => write!(f, "PASS"),
33            Self::Fail => write!(f, "FAIL"),
34            Self::ExpectedFailure => write!(f, "XFAIL"),
35            Self::Skipped => write!(f, "SKIP"),
36        }
37    }
38}
39
40/// Requirement level for conformance testing.
41#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
42pub enum RequirementLevel {
43    Must,   // RFC MUST
44    Should, // RFC SHOULD
45    May,    // RFC MAY
46}
47
48/// Stream state after DATA END_STREAM processing.
49#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
50pub struct StreamEndStreamState {
51    pub stream_id: u32,
52    /// Stream state: Open, HalfClosedLocal, HalfClosedRemote, Closed
53    pub state: String,
54    /// Whether the stream can receive more data
55    pub can_recv: bool,
56    /// Whether the stream can send more data
57    pub can_send: bool,
58    /// Error code if stream was reset
59    pub error_code: Option<String>,
60}
61
62/// Connection state after DATA END_STREAM processing for comparison.
63#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
64pub struct DataEndStreamConnectionState {
65    /// Connection state (should remain stable)
66    pub connection_state: String,
67    /// Stream states indexed by stream ID
68    pub stream_states: Vec<StreamEndStreamState>,
69    /// Whether any errors occurred during processing
70    pub has_errors: bool,
71    /// List of error messages for failed operations
72    pub error_messages: Vec<String>,
73}
74
75/// Serializable DATA frame for test cases.
76#[derive(Debug, Clone, Serialize, Deserialize)]
77pub struct SerializableDataFrame {
78    pub stream_id: u32,
79    pub data: Vec<u8>,
80    pub end_stream: bool,
81}
82
83impl From<DataFrame> for SerializableDataFrame {
84    fn from(frame: DataFrame) -> Self {
85        Self {
86            stream_id: frame.stream_id,
87            data: frame.data.to_vec(),
88            end_stream: frame.end_stream,
89        }
90    }
91}
92
93impl From<SerializableDataFrame> for DataFrame {
94    fn from(frame: SerializableDataFrame) -> Self {
95        Self::new(frame.stream_id, Bytes::from(frame.data), frame.end_stream)
96    }
97}
98
99/// Single conformance test case for DATA frame END_STREAM handling.
100#[derive(Debug, Clone, Serialize, Deserialize)]
101pub struct DataEndStreamConformanceCase {
102    pub id: String,
103    pub description: String,
104    pub requirement_level: RequirementLevel,
105    /// Initial stream setup (HEADERS frames to establish streams)
106    pub initial_streams: Vec<u32>,
107    /// Sequence of DATA frames to apply
108    pub data_sequence: Vec<SerializableDataFrame>,
109    /// Expected final connection state
110    pub expected_connection_state: DataEndStreamConnectionState,
111}
112
113/// Result of a single conformance test.
114#[derive(Debug, Clone, Serialize, Deserialize)]
115pub struct DataEndStreamConformanceResult {
116    pub case_id: String,
117    pub verdict: DataEndStreamTestVerdict,
118    pub error: Option<String>,
119    /// Asupersync's final connection state
120    pub asupersync_state: Option<DataEndStreamConnectionState>,
121    /// H2 reference's final connection state
122    pub h2_state: Option<DataEndStreamConnectionState>,
123    /// Differences detected between implementations
124    pub differences: Vec<String>,
125}
126
127/// Summary statistics for the conformance run.
128#[derive(Debug, Clone, Serialize, Deserialize)]
129pub struct DataEndStreamComplianceSummary {
130    pub total_cases: usize,
131    pub passed: usize,
132    pub failed: usize,
133    pub expected_failures: usize,
134    pub skipped: usize,
135    pub compliance_score: f64, // passed / total
136}
137
138/// Complete conformance test report.
139#[derive(Debug, Clone, Serialize, Deserialize)]
140pub struct DataEndStreamComplianceReport {
141    pub test_run_id: String,
142    pub timestamp: chrono::DateTime<chrono::Utc>,
143    pub total_cases: usize,
144    pub results: Vec<DataEndStreamConformanceResult>,
145    pub summary: DataEndStreamComplianceSummary,
146}
147
148impl DataEndStreamComplianceReport {
149    /// Create a new report with generated ID and timestamp.
150    fn new(results: Vec<DataEndStreamConformanceResult>) -> Self {
151        let total_cases = results.len();
152        let passed = results
153            .iter()
154            .filter(|r| r.verdict == DataEndStreamTestVerdict::Pass)
155            .count();
156        let failed = results
157            .iter()
158            .filter(|r| r.verdict == DataEndStreamTestVerdict::Fail)
159            .count();
160        let expected_failures = results
161            .iter()
162            .filter(|r| r.verdict == DataEndStreamTestVerdict::ExpectedFailure)
163            .count();
164        let skipped = results
165            .iter()
166            .filter(|r| r.verdict == DataEndStreamTestVerdict::Skipped)
167            .count();
168
169        let compliance_score = if total_cases > 0 {
170            passed as f64 / total_cases as f64
171        } else {
172            1.0
173        };
174
175        let summary = DataEndStreamComplianceSummary {
176            total_cases,
177            passed,
178            failed,
179            expected_failures,
180            skipped,
181            compliance_score,
182        };
183
184        Self {
185            test_run_id: uuid::Uuid::new_v4().to_string(),
186            timestamp: chrono::Utc::now(),
187            total_cases,
188            results,
189            summary,
190        }
191    }
192}
193
194/// Main conformance tester for HTTP/2 DATA frame END_STREAM handling.
195#[derive(Debug)]
196pub struct DataEndStreamConformanceTester {
197    pub test_cases: Vec<DataEndStreamConformanceCase>,
198}
199
200impl DataEndStreamConformanceTester {
201    /// Create a new tester with predefined conformance cases.
202    pub fn new() -> Self {
203        Self {
204            test_cases: create_data_end_stream_test_cases(),
205        }
206    }
207
208    /// Run all conformance tests and return a report.
209    pub async fn run_all_tests(&self) -> DataEndStreamComplianceReport {
210        let mut results = Vec::new();
211
212        for case in &self.test_cases {
213            let result = self.run_single_test(case).await;
214            results.push(result);
215        }
216
217        DataEndStreamComplianceReport::new(results)
218    }
219
220    /// Run a single conformance test case.
221    async fn run_single_test(
222        &self,
223        case: &DataEndStreamConformanceCase,
224    ) -> DataEndStreamConformanceResult {
225        // Test asupersync implementation
226        let asupersync_result = self.test_asupersync_data_end_stream(case).await;
227
228        // Test h2 reference implementation. If the external reference is not
229        // wired, keep this as a live conformance check against the RFC-backed
230        // expected state instead of reporting an invented comparison.
231        let h2_result = self.test_h2_data_end_stream(case).await;
232
233        // Compare results
234        let (verdict, error, differences) = match (&asupersync_result, &h2_result) {
235            (Ok(asupersync_state), Err(h2_err)) if h2_err == H2_REFERENCE_UNAVAILABLE => {
236                let differences = self
237                    .compare_connection_states(asupersync_state, &case.expected_connection_state);
238                if differences.is_empty() {
239                    (
240                        DataEndStreamTestVerdict::ExpectedFailure,
241                        Some(format!(
242                            "{h2_err}; live asupersync matched the RFC-expected state but vendor parity remains unexercised"
243                        )),
244                        differences,
245                    )
246                } else {
247                    (
248                        DataEndStreamTestVerdict::Fail,
249                        Some(format!(
250                            "Live asupersync DATA END_STREAM state differed from expected RFC behavior while {h2_err}"
251                        )),
252                        differences,
253                    )
254                }
255            }
256            (Err(asupersync_err), Err(h2_err)) if h2_err == H2_REFERENCE_UNAVAILABLE => (
257                DataEndStreamTestVerdict::Fail,
258                Some(format!(
259                    "Live asupersync DATA END_STREAM processing failed while {h2_err}: {asupersync_err}"
260                )),
261                vec![format!("asupersync_error: {asupersync_err}")],
262            ),
263            (Ok(asupersync_state), Ok(h2_state)) => {
264                let differences = self.compare_connection_states(asupersync_state, h2_state);
265                if differences.is_empty() {
266                    (DataEndStreamTestVerdict::Pass, None, differences)
267                } else {
268                    (
269                        DataEndStreamTestVerdict::Fail,
270                        Some(format!(
271                            "Connection state differences: {}",
272                            differences.join(", ")
273                        )),
274                        differences,
275                    )
276                }
277            }
278            (_, Err(h2_err)) if h2_err == H2_REFERENCE_UNAVAILABLE => (
279                DataEndStreamTestVerdict::Skipped,
280                Some(h2_err.clone()),
281                Vec::new(),
282            ),
283            (Err(asupersync_err), Err(h2_err)) => {
284                // Both failed - check if they failed the same way
285                if asupersync_err == h2_err {
286                    (DataEndStreamTestVerdict::Pass, None, Vec::new())
287                } else {
288                    (
289                        DataEndStreamTestVerdict::Fail,
290                        Some(format!(
291                            "Different error behaviors: asupersync={}, h2={}",
292                            asupersync_err, h2_err
293                        )),
294                        vec![format!(
295                            "Error divergence: {} vs {}",
296                            asupersync_err, h2_err
297                        )],
298                    )
299                }
300            }
301            (Ok(_), Err(h2_err)) => (
302                DataEndStreamTestVerdict::Fail,
303                Some(format!("asupersync succeeded, h2 failed: {}", h2_err)),
304                vec!["Implementation success divergence".to_string()],
305            ),
306            (Err(asupersync_err), Ok(_)) => (
307                DataEndStreamTestVerdict::Fail,
308                Some(format!(
309                    "asupersync failed, h2 succeeded: {}",
310                    asupersync_err
311                )),
312                vec!["Implementation success divergence".to_string()],
313            ),
314        };
315
316        DataEndStreamConformanceResult {
317            case_id: case.id.clone(),
318            verdict,
319            error,
320            asupersync_state: asupersync_result.as_ref().ok().cloned(),
321            h2_state: h2_result.as_ref().ok().cloned(),
322            differences,
323        }
324    }
325
326    /// Test asupersync DATA END_STREAM handling.
327    async fn test_asupersync_data_end_stream(
328        &self,
329        case: &DataEndStreamConformanceCase,
330    ) -> Result<DataEndStreamConnectionState, String> {
331        let settings = Settings::default();
332        let mut connection = Connection::server(settings);
333        let mut error_messages = Vec::new();
334        accept_peer_settings(&mut connection)?;
335
336        // Initialize streams
337        for &stream_id in &case.initial_streams {
338            if let Err(e) = initialize_remote_stream(&mut connection, stream_id) {
339                return Err(format!("Failed to initialize stream {}: {}", stream_id, e));
340            }
341        }
342
343        // Apply DATA sequence
344        for serializable_frame in &case.data_sequence {
345            let data_frame: DataFrame = serializable_frame.clone().into();
346            match process_live_data_frame(&mut connection, &data_frame) {
347                Ok(_) => {}
348                Err(e) => {
349                    error_messages.push(format!(
350                        "DATA frame error on stream {}: {}",
351                        data_frame.stream_id, e
352                    ));
353                }
354            }
355        }
356
357        // Extract connection state
358        let connection_state = extract_asupersync_data_end_stream_state(
359            &connection,
360            &case.initial_streams,
361            error_messages,
362        );
363        Ok(connection_state)
364    }
365
366    /// Test h2 reference DATA END_STREAM handling.
367    async fn test_h2_data_end_stream(
368        &self,
369        _case: &DataEndStreamConformanceCase,
370    ) -> Result<DataEndStreamConnectionState, String> {
371        Err(H2_REFERENCE_UNAVAILABLE.to_string())
372    }
373
374    /// Compare connection states between implementations.
375    fn compare_connection_states(
376        &self,
377        asupersync: &DataEndStreamConnectionState,
378        h2: &DataEndStreamConnectionState,
379    ) -> Vec<String> {
380        let mut differences = Vec::new();
381
382        if asupersync.connection_state != h2.connection_state {
383            differences.push(format!(
384                "connection_state differs: asupersync={}, h2={}",
385                asupersync.connection_state, h2.connection_state
386            ));
387        }
388
389        if asupersync.has_errors != h2.has_errors {
390            differences.push(format!(
391                "has_errors differs: asupersync={}, h2={}",
392                asupersync.has_errors, h2.has_errors
393            ));
394        }
395
396        // Compare stream states
397        if asupersync.stream_states.len() != h2.stream_states.len() {
398            differences.push(format!(
399                "stream_states count differs: asupersync={}, h2={}",
400                asupersync.stream_states.len(),
401                h2.stream_states.len()
402            ));
403        } else {
404            for (asupersync_stream, h2_stream) in
405                asupersync.stream_states.iter().zip(&h2.stream_states)
406            {
407                if asupersync_stream.stream_id != h2_stream.stream_id {
408                    differences.push(format!(
409                        "stream_id mismatch: asupersync={}, h2={}",
410                        asupersync_stream.stream_id, h2_stream.stream_id
411                    ));
412                }
413                if asupersync_stream.state != h2_stream.state {
414                    differences.push(format!(
415                        "stream {} state differs: asupersync={}, h2={}",
416                        asupersync_stream.stream_id, asupersync_stream.state, h2_stream.state
417                    ));
418                }
419                if asupersync_stream.can_recv != h2_stream.can_recv {
420                    differences.push(format!(
421                        "stream {} can_recv differs: asupersync={}, h2={}",
422                        asupersync_stream.stream_id, asupersync_stream.can_recv, h2_stream.can_recv
423                    ));
424                }
425                if asupersync_stream.can_send != h2_stream.can_send {
426                    differences.push(format!(
427                        "stream {} can_send differs: asupersync={}, h2={}",
428                        asupersync_stream.stream_id, asupersync_stream.can_send, h2_stream.can_send
429                    ));
430                }
431            }
432        }
433
434        // Compare error messages (order-independent)
435        if asupersync.error_messages.len() != h2.error_messages.len() {
436            differences.push(format!(
437                "error_messages count differs: asupersync={}, h2={}",
438                asupersync.error_messages.len(),
439                h2.error_messages.len()
440            ));
441        } else {
442            let mut asupersync_errors = asupersync.error_messages.clone();
443            let mut h2_errors = h2.error_messages.clone();
444            asupersync_errors.sort();
445            h2_errors.sort();
446
447            if asupersync_errors != h2_errors {
448                differences.push("error_messages content differs".to_string());
449            }
450        }
451
452        differences
453    }
454
455    /// Generate a markdown report.
456    pub fn generate_markdown_report(&self, report: &DataEndStreamComplianceReport) -> String {
457        let mut output = String::new();
458        output.push_str("# HTTP/2 DATA Frame END_STREAM Conformance Report\n\n");
459
460        output.push_str(&format!("**Test Run ID:** {}\n", report.test_run_id));
461        output.push_str(&format!("**Timestamp:** {}\n", report.timestamp));
462        output.push_str(&format!("**Total Test Cases:** {}\n\n", report.total_cases));
463
464        output.push_str("## Summary\n\n");
465        output.push_str(&format!("- **Passed:** {}\n", report.summary.passed));
466        output.push_str(&format!("- **Failed:** {}\n", report.summary.failed));
467        output.push_str(&format!(
468            "- **Expected Failures:** {}\n",
469            report.summary.expected_failures
470        ));
471        output.push_str(&format!("- **Skipped:** {}\n", report.summary.skipped));
472        output.push_str(&format!(
473            "- **Compliance Score:** {:.1}%\n\n",
474            report.summary.compliance_score * 100.0
475        ));
476
477        if report.summary.failed > 0 {
478            output.push_str("## Failures\n\n");
479            for result in &report.results {
480                if result.verdict == DataEndStreamTestVerdict::Fail {
481                    output.push_str(&format!("### {}\n", result.case_id));
482                    if let Some(error) = &result.error {
483                        output.push_str(&format!("**Error:** {}\n", error));
484                    }
485                    if !result.differences.is_empty() {
486                        output.push_str("**Differences:**\n");
487                        for diff in &result.differences {
488                            output.push_str(&format!("- {}\n", diff));
489                        }
490                    }
491                    output.push('\n');
492                }
493            }
494        }
495
496        output.push_str("## All Results\n\n");
497        output.push_str("| Case ID | Verdict | Description |\n");
498        output.push_str("|---------|---------|-------------|\n");
499        for result in &report.results {
500            output.push_str(&format!(
501                "| {} | {} | Case {} |\n",
502                result.case_id, result.verdict, result.case_id
503            ));
504        }
505
506        output
507    }
508}
509
510impl Default for DataEndStreamConformanceTester {
511    fn default() -> Self {
512        Self::new()
513    }
514}
515
516fn accept_peer_settings(connection: &mut Connection) -> Result<(), String> {
517    let received = connection
518        .process_frame(Frame::Settings(SettingsFrame::new(vec![])))
519        .map_err(|err| err.to_string())?;
520    if received.is_some() {
521        return Err("SETTINGS handshake produced an application frame".to_string());
522    }
523
524    match connection.next_frame() {
525        Some(Frame::Settings(settings)) if settings.ack => Ok(()),
526        other => Err(format!(
527            "SETTINGS handshake should queue exactly one ACK, got {other:?}"
528        )),
529    }
530}
531
532fn request_header_block(stream_id: u32) -> Bytes {
533    let headers = [
534        Header::new(":method", "GET"),
535        Header::new(":scheme", "https"),
536        Header::new(":authority", "example.test"),
537        Header::new(":path", format!("/stream/{stream_id}")),
538    ];
539    let mut encoder = HpackEncoder::new();
540    let mut block = BytesMut::new();
541    encoder.encode(&headers, &mut block);
542    block.freeze()
543}
544
545/// Open a peer-initiated stream through the production HEADERS path.
546fn initialize_remote_stream(connection: &mut Connection, stream_id: u32) -> Result<(), String> {
547    let headers = HeadersFrame::new(stream_id, request_header_block(stream_id), false, true);
548    match connection
549        .process_frame(Frame::Headers(headers))
550        .map_err(|err| err.to_string())?
551    {
552        Some(ReceivedFrame::Headers {
553            stream_id: received_stream_id,
554            end_stream,
555            ..
556        }) if received_stream_id == stream_id && !end_stream => Ok(()),
557        other => Err(format!(
558            "HEADERS stream initialization produced unexpected frame: {other:?}"
559        )),
560    }
561}
562
563/// Process a DATA frame through the production connection state machine.
564fn process_live_data_frame(
565    connection: &mut Connection,
566    data_frame: &DataFrame,
567) -> Result<(), String> {
568    match connection.process_frame(Frame::Data(data_frame.clone())) {
569        Ok(Some(ReceivedFrame::Data {
570            stream_id,
571            end_stream,
572            ..
573        })) if stream_id == data_frame.stream_id && end_stream == data_frame.end_stream => Ok(()),
574        Ok(None) => Ok(()),
575        Ok(other) => Err(format!("unexpected DATA result frame: {other:?}")),
576        Err(err) => Err(format!("{:?}", err.code)),
577    }
578}
579
580/// Extract DATA END_STREAM-related connection state from asupersync connection.
581fn extract_asupersync_data_end_stream_state(
582    connection: &Connection,
583    stream_ids: &[u32],
584    error_messages: Vec<String>,
585) -> DataEndStreamConnectionState {
586    let stream_states = stream_ids
587        .iter()
588        .filter_map(|&stream_id| {
589            let stream = connection.stream(stream_id)?;
590            let state = stream.state();
591            Some(StreamEndStreamState {
592                stream_id,
593                state: format!("{state:?}"),
594                can_recv: state.can_recv(),
595                can_send: state.can_send(),
596                error_code: stream.error_code().map(|code| format!("{code:?}")),
597            })
598        })
599        .collect();
600
601    DataEndStreamConnectionState {
602        connection_state: format!("{:?}", connection.state()),
603        stream_states,
604        has_errors: !error_messages.is_empty(),
605        error_messages,
606    }
607}
608
609/// Create predefined test cases for DATA frame END_STREAM conformance.
610fn create_data_end_stream_test_cases() -> Vec<DataEndStreamConformanceCase> {
611    vec![
612        // Test Case 1: Basic DATA with END_STREAM
613        DataEndStreamConformanceCase {
614            id: "data-end-stream-001".to_string(),
615            description: "Basic DATA frame with END_STREAM closes stream correctly".to_string(),
616            requirement_level: RequirementLevel::Must,
617            initial_streams: vec![1],
618            data_sequence: vec![SerializableDataFrame {
619                stream_id: 1,
620                data: b"Hello, World!".to_vec(),
621                end_stream: true,
622            }],
623            expected_connection_state: DataEndStreamConnectionState {
624                connection_state: "Open".to_string(),
625                stream_states: vec![StreamEndStreamState {
626                    stream_id: 1,
627                    state: "HalfClosedRemote".to_string(),
628                    can_recv: false,
629                    can_send: true,
630                    error_code: None,
631                }],
632                has_errors: false,
633                error_messages: Vec::new(),
634            },
635        },
636        // Test Case 2: DATA after END_STREAM should be rejected
637        DataEndStreamConformanceCase {
638            id: "data-end-stream-002".to_string(),
639            description: "DATA frame after END_STREAM is rejected with StreamClosed error"
640                .to_string(),
641            requirement_level: RequirementLevel::Must,
642            initial_streams: vec![1],
643            data_sequence: vec![
644                SerializableDataFrame {
645                    stream_id: 1,
646                    data: b"First message".to_vec(),
647                    end_stream: true,
648                },
649                SerializableDataFrame {
650                    stream_id: 1,
651                    data: b"Should be rejected".to_vec(),
652                    end_stream: false,
653                },
654            ],
655            expected_connection_state: DataEndStreamConnectionState {
656                connection_state: "Open".to_string(),
657                stream_states: vec![StreamEndStreamState {
658                    stream_id: 1,
659                    state: "HalfClosedRemote".to_string(),
660                    can_recv: false,
661                    can_send: true,
662                    error_code: None,
663                }],
664                has_errors: true,
665                error_messages: vec!["DATA frame error on stream 1: StreamClosed".to_string()],
666            },
667        },
668        // Test Case 3: Multiple DATA frames, last with END_STREAM
669        DataEndStreamConformanceCase {
670            id: "data-end-stream-003".to_string(),
671            description: "Multiple DATA frames, only last has END_STREAM".to_string(),
672            requirement_level: RequirementLevel::Must,
673            initial_streams: vec![1],
674            data_sequence: vec![
675                SerializableDataFrame {
676                    stream_id: 1,
677                    data: b"Chunk 1".to_vec(),
678                    end_stream: false,
679                },
680                SerializableDataFrame {
681                    stream_id: 1,
682                    data: b"Chunk 2".to_vec(),
683                    end_stream: false,
684                },
685                SerializableDataFrame {
686                    stream_id: 1,
687                    data: b"Final chunk".to_vec(),
688                    end_stream: true,
689                },
690            ],
691            expected_connection_state: DataEndStreamConnectionState {
692                connection_state: "Open".to_string(),
693                stream_states: vec![StreamEndStreamState {
694                    stream_id: 1,
695                    state: "HalfClosedRemote".to_string(),
696                    can_recv: false,
697                    can_send: true,
698                    error_code: None,
699                }],
700                has_errors: false,
701                error_messages: Vec::new(),
702            },
703        },
704        // Test Case 4: Empty DATA with END_STREAM
705        DataEndStreamConformanceCase {
706            id: "data-end-stream-004".to_string(),
707            description: "Empty DATA frame with END_STREAM closes stream".to_string(),
708            requirement_level: RequirementLevel::Must,
709            initial_streams: vec![1],
710            data_sequence: vec![SerializableDataFrame {
711                stream_id: 1,
712                data: Vec::new(), // Empty payload
713                end_stream: true,
714            }],
715            expected_connection_state: DataEndStreamConnectionState {
716                connection_state: "Open".to_string(),
717                stream_states: vec![StreamEndStreamState {
718                    stream_id: 1,
719                    state: "HalfClosedRemote".to_string(),
720                    can_recv: false,
721                    can_send: true,
722                    error_code: None,
723                }],
724                has_errors: false,
725                error_messages: Vec::new(),
726            },
727        },
728        // Test Case 5: Multiple streams with END_STREAM
729        DataEndStreamConformanceCase {
730            id: "data-end-stream-005".to_string(),
731            description: "Multiple streams each closed with END_STREAM".to_string(),
732            requirement_level: RequirementLevel::Should,
733            initial_streams: vec![1, 3, 5],
734            data_sequence: vec![
735                SerializableDataFrame {
736                    stream_id: 1,
737                    data: b"Stream 1 data".to_vec(),
738                    end_stream: true,
739                },
740                SerializableDataFrame {
741                    stream_id: 3,
742                    data: b"Stream 3 data".to_vec(),
743                    end_stream: true,
744                },
745                SerializableDataFrame {
746                    stream_id: 5,
747                    data: b"Stream 5 data".to_vec(),
748                    end_stream: true,
749                },
750            ],
751            expected_connection_state: DataEndStreamConnectionState {
752                connection_state: "Open".to_string(),
753                stream_states: vec![
754                    StreamEndStreamState {
755                        stream_id: 1,
756                        state: "HalfClosedRemote".to_string(),
757                        can_recv: false,
758                        can_send: true,
759                        error_code: None,
760                    },
761                    StreamEndStreamState {
762                        stream_id: 3,
763                        state: "HalfClosedRemote".to_string(),
764                        can_recv: false,
765                        can_send: true,
766                        error_code: None,
767                    },
768                    StreamEndStreamState {
769                        stream_id: 5,
770                        state: "HalfClosedRemote".to_string(),
771                        can_recv: false,
772                        can_send: true,
773                        error_code: None,
774                    },
775                ],
776                has_errors: false,
777                error_messages: Vec::new(),
778            },
779        },
780        // Test Case 6: Large DATA with END_STREAM
781        DataEndStreamConformanceCase {
782            id: "data-end-stream-006".to_string(),
783            description: "Large DATA frame with END_STREAM handles correctly".to_string(),
784            requirement_level: RequirementLevel::Should,
785            initial_streams: vec![1],
786            data_sequence: vec![SerializableDataFrame {
787                stream_id: 1,
788                data: vec![0u8; 8192], // 8KB payload
789                end_stream: true,
790            }],
791            expected_connection_state: DataEndStreamConnectionState {
792                connection_state: "Open".to_string(),
793                stream_states: vec![StreamEndStreamState {
794                    stream_id: 1,
795                    state: "HalfClosedRemote".to_string(),
796                    can_recv: false,
797                    can_send: true,
798                    error_code: None,
799                }],
800                has_errors: false,
801                error_messages: Vec::new(),
802            },
803        },
804        // Test Case 7: Attempt to send multiple END_STREAM frames
805        DataEndStreamConformanceCase {
806            id: "data-end-stream-007".to_string(),
807            description: "Multiple END_STREAM frames on same stream should be rejected".to_string(),
808            requirement_level: RequirementLevel::Must,
809            initial_streams: vec![1],
810            data_sequence: vec![
811                SerializableDataFrame {
812                    stream_id: 1,
813                    data: b"First end".to_vec(),
814                    end_stream: true,
815                },
816                SerializableDataFrame {
817                    stream_id: 1,
818                    data: b"Second end".to_vec(),
819                    end_stream: true, // Second END_STREAM should be rejected
820                },
821            ],
822            expected_connection_state: DataEndStreamConnectionState {
823                connection_state: "Open".to_string(),
824                stream_states: vec![StreamEndStreamState {
825                    stream_id: 1,
826                    state: "HalfClosedRemote".to_string(),
827                    can_recv: false,
828                    can_send: true,
829                    error_code: None,
830                }],
831                has_errors: true,
832                error_messages: vec!["DATA frame error on stream 1: StreamClosed".to_string()],
833            },
834        },
835    ]
836}
837
838#[cfg(test)]
839mod tests {
840    use super::*;
841    use asupersync::http::h2::ErrorCode;
842
843    #[tokio::test]
844    async fn h2_reference_unavailable_still_runs_live_data_assertions() {
845        let tester = DataEndStreamConformanceTester::new();
846        let report = tester.run_all_tests().await;
847
848        assert_eq!(report.total_cases, 7);
849        assert_eq!(report.summary.passed, 0);
850        assert_eq!(report.summary.failed, 0);
851        assert_eq!(report.summary.expected_failures, 7);
852        assert_eq!(report.summary.skipped, 0);
853        assert_eq!(report.summary.compliance_score, 0.0);
854        assert!(
855            report
856                .results
857                .iter()
858                .all(|result| result.h2_state.is_none()),
859            "h2 reference is intentionally not wired for this harness"
860        );
861        assert!(
862            report
863                .results
864                .iter()
865                .all(|result| result.asupersync_state.is_some()),
866            "every case must exercise the live asupersync connection"
867        );
868    }
869
870    #[tokio::test]
871    async fn h2_reference_gap_is_reported_as_expected_failure_not_pass() {
872        let tester = DataEndStreamConformanceTester::new();
873        let report = tester.run_all_tests().await;
874
875        assert!(
876            report
877                .results
878                .iter()
879                .all(|result| result.verdict == DataEndStreamTestVerdict::ExpectedFailure),
880            "unwired h2 vendor parity must not be reported as full pass: {:?}",
881            report.results
882        );
883        assert!(
884            report.results.iter().all(|result| result
885                .error
886                .as_deref()
887                .is_some_and(|error| error.contains(H2_REFERENCE_UNAVAILABLE)
888                    && error.contains("vendor parity remains unexercised"))),
889            "each expected failure should explain the missing h2 reference parity"
890        );
891    }
892
893    #[test]
894    fn data_end_stream_moves_stream_half_closed_remote() {
895        let mut connection = Connection::server(Settings::default());
896        accept_peer_settings(&mut connection).expect("SETTINGS handshake");
897        initialize_remote_stream(&mut connection, 1).expect("open stream");
898
899        let data = DataFrame::new(1, Bytes::from_static(b"done"), true);
900        process_live_data_frame(&mut connection, &data).expect("DATA should process");
901
902        let stream = connection.stream(1).expect("stream exists");
903        assert_eq!(format!("{:?}", stream.state()), "HalfClosedRemote");
904        assert!(!stream.state().can_recv());
905        assert!(stream.state().can_send());
906    }
907
908    #[test]
909    fn data_after_end_stream_reports_stream_closed() {
910        let mut connection = Connection::server(Settings::default());
911        accept_peer_settings(&mut connection).expect("SETTINGS handshake");
912        initialize_remote_stream(&mut connection, 1).expect("open stream");
913
914        let first = DataFrame::new(1, Bytes::from_static(b"done"), true);
915        process_live_data_frame(&mut connection, &first).expect("first DATA should process");
916
917        let second = DataFrame::new(1, Bytes::from_static(b"again"), false);
918        let error = process_live_data_frame(&mut connection, &second)
919            .expect_err("DATA after END_STREAM must fail");
920        assert_eq!(error, "StreamClosed");
921    }
922
923    #[test]
924    fn data_frame_updates_connection_and_stream_receive_windows() {
925        let mut connection = Connection::server(Settings::default());
926        accept_peer_settings(&mut connection).expect("SETTINGS handshake");
927        initialize_remote_stream(&mut connection, 1).expect("open stream");
928
929        let connection_window_before = connection.recv_window();
930        let stream_window_before = connection.stream(1).unwrap().recv_window();
931        let data = DataFrame::new(1, Bytes::from_static(b"windowed"), false);
932        process_live_data_frame(&mut connection, &data).expect("DATA should process");
933
934        assert_eq!(connection.recv_window(), connection_window_before - 8);
935        assert_eq!(
936            connection.stream(1).unwrap().recv_window(),
937            stream_window_before - 8
938        );
939        assert_eq!(
940            format!("{:?}", connection.stream(1).unwrap().state()),
941            "Open"
942        );
943    }
944
945    #[test]
946    fn headers_after_data_end_stream_reports_stream_closed() {
947        let mut connection = Connection::server(Settings::default());
948        accept_peer_settings(&mut connection).expect("SETTINGS handshake");
949        initialize_remote_stream(&mut connection, 1).expect("open stream");
950
951        let data = DataFrame::new(1, Bytes::from_static(b"done"), true);
952        process_live_data_frame(&mut connection, &data).expect("DATA should close remote side");
953
954        let trailers = HeadersFrame::new(1, Bytes::new(), true, true);
955        let error = connection
956            .process_frame(Frame::Headers(trailers))
957            .expect_err("HEADERS after END_STREAM must fail");
958        assert_eq!(error.code, ErrorCode::StreamClosed);
959        assert_eq!(error.stream_id, Some(1));
960    }
961}