Skip to main content

cloud_sdk_testkit/
adversarial.rs

1//! Reusable adversarial response corpus.
2
3use crate::{FixtureBody, FixtureBodyError};
4
5/// Common response limit used by the initial provider response boundary.
6pub const DEFAULT_RESPONSE_LIMIT: usize = 8_388_608;
7
8/// Adversarial response category.
9#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
10pub enum AdversarialKind {
11    /// Syntactically malformed JSON.
12    MalformedJson,
13    /// Valid JSON containing an additive unknown field.
14    UnknownFields,
15    /// Valid JSON missing required fields.
16    MissingRequiredFields,
17    /// Response one byte larger than the common admitted ceiling.
18    OversizedResponse,
19    /// Structurally invalid pagination values.
20    InvalidPagination,
21    /// Unknown action lifecycle state.
22    InvalidActionState,
23}
24
25/// Named adversarial response body.
26#[derive(Clone, Copy, Debug, Eq, PartialEq)]
27pub struct AdversarialFixture<'a> {
28    kind: AdversarialKind,
29    body: FixtureBody<'a>,
30}
31
32impl<'a> AdversarialFixture<'a> {
33    /// Creates a named adversarial fixture.
34    #[must_use]
35    pub const fn new(kind: AdversarialKind, body: FixtureBody<'a>) -> Self {
36        Self { kind, body }
37    }
38
39    /// Returns the adversarial category.
40    #[must_use]
41    pub const fn kind(self) -> AdversarialKind {
42        self.kind
43    }
44
45    /// Returns the compact fixture body.
46    #[must_use]
47    pub const fn body(self) -> FixtureBody<'a> {
48        self.body
49    }
50}
51
52/// Creates the fixed six-case adversarial response corpus.
53pub fn adversarial_corpus() -> Result<[AdversarialFixture<'static>; 6], FixtureBodyError> {
54    let oversized_len = DEFAULT_RESPONSE_LIMIT
55        .checked_add(1)
56        .ok_or(FixtureBodyError::TooLarge)?;
57    Ok([
58        AdversarialFixture::new(
59            AdversarialKind::MalformedJson,
60            FixtureBody::new(br#"{"error":"#)?,
61        ),
62        AdversarialFixture::new(
63            AdversarialKind::UnknownFields,
64            FixtureBody::new(
65                br#"{"action":{"id":1,"command":"test","status":"running","progress":1,"started":"2026-07-12T12:00:00Z","finished":null,"resources":[],"error":null,"unknown":true},"unknown":true}"#,
66            )?,
67        ),
68        AdversarialFixture::new(
69            AdversarialKind::MissingRequiredFields,
70            FixtureBody::new(br#"{}"#)?,
71        ),
72        AdversarialFixture::new(
73            AdversarialKind::OversizedResponse,
74            FixtureBody::repeated(b' ', oversized_len)?,
75        ),
76        AdversarialFixture::new(
77            AdversarialKind::InvalidPagination,
78            FixtureBody::new(br#"{"meta":{"pagination":{"page":0,"per_page":0,"last_page":0}}}"#)?,
79        ),
80        AdversarialFixture::new(
81            AdversarialKind::InvalidActionState,
82            FixtureBody::new(br#"{"action":{"id":1,"status":"not-a-state","progress":101}}"#)?,
83        ),
84    ])
85}