Skip to main content

toasty_driver_integration_suite/tests/
dynamodb_limit_boundary.rs

1//! DynamoDB 1 MB response boundary tests.
2//!
3//! These tests prove the paging loop works correctly when the result set spans
4//! DynamoDB's 1 MB response boundary. DynamoDB's Query API returns at most 1 MB
5//! of data per call and sets `LastEvaluatedKey` when there are more results.
6//! With ~10 KB items, ~100 items ≈ 1 MB, so seeding 200 items and querying with
7//! `.limit(150)` forces at least 2 DynamoDB API calls, exercising the pagination
8//!  loop in the driver.
9//!
10//! IMPORTANT: The `payload` field is intentionally large (10,000 bytes). Do NOT
11//! reduce its size. The tests depend on the payload being large enough to push
12//! each batch of ~100 items past the 1 MB boundary so that at least two DynamoDB
13//! API calls are required to satisfy the limit.
14
15use crate::prelude::*;
16
17// ── Base table tests (composite partition + sort key) ────────────────────────
18
19#[driver_test(requires(not(sql)))]
20pub async fn limit_spans_page_boundary(t: &mut Test) -> Result<()> {
21    #[derive(Debug, toasty::Model)]
22    #[key(partition = kind, local = seq)]
23    struct Item {
24        kind: String,
25        seq: i64,
26        payload: String,
27    }
28
29    let mut db = t.setup_db(models!(Item)).await;
30
31    let payload = "x".repeat(10_000);
32    for i in 0..200_i64 {
33        toasty::create!(Item {
34            kind: "boundary",
35            seq: i,
36            payload: payload.clone(),
37        })
38        .exec(&mut db)
39        .await?;
40    }
41
42    let items: Vec<_> = Item::filter_by_kind("boundary")
43        .order_by(Item::fields().seq().asc())
44        .limit(150)
45        .exec(&mut db)
46        .await?;
47
48    assert_eq!(items.len(), 150);
49    assert_eq!(items[0].seq, 0);
50    assert_eq!(items[149].seq, 149);
51
52    Ok(())
53}
54
55#[driver_test(requires(not(sql)))]
56pub async fn limit_offset_spans_page_boundary(t: &mut Test) -> Result<()> {
57    #[derive(Debug, toasty::Model)]
58    #[key(partition = kind, local = seq)]
59    struct Item {
60        kind: String,
61        seq: i64,
62        payload: String,
63    }
64
65    let mut db = t.setup_db(models!(Item)).await;
66
67    let payload = "x".repeat(10_000);
68    for i in 0..200_i64 {
69        toasty::create!(Item {
70            kind: "boundary",
71            seq: i,
72            payload: payload.clone(),
73        })
74        .exec(&mut db)
75        .await?;
76    }
77
78    let items: Vec<_> = Item::filter_by_kind("boundary")
79        .order_by(Item::fields().seq().asc())
80        .limit(100)
81        .offset(50)
82        .exec(&mut db)
83        .await?;
84
85    assert_eq!(items.len(), 100);
86    assert_eq!(items[0].seq, 50);
87    assert_eq!(items[99].seq, 149);
88
89    Ok(())
90}
91
92// ── GSI tests (non-unique index on a UUID-keyed model) ────────────────────────
93
94#[driver_test(requires(not(sql)))]
95pub async fn limit_spans_page_boundary_gsi(t: &mut Test) -> Result<()> {
96    #[derive(Debug, toasty::Model)]
97    struct GsiItem {
98        #[key]
99        #[auto]
100        id: uuid::Uuid,
101
102        #[index]
103        category: String,
104
105        seq: i64,
106        payload: String,
107    }
108
109    let mut db = t.setup_db(models!(GsiItem)).await;
110
111    let payload = "x".repeat(10_000);
112    for i in 0..200_i64 {
113        toasty::create!(GsiItem {
114            category: "boundary",
115            seq: i,
116            payload: payload.clone(),
117        })
118        .exec(&mut db)
119        .await?;
120    }
121
122    // DDB GSI ordering is only guaranteed on the GSI sort key; seq is not the
123    // sort key here, so only assert the count.
124    let items: Vec<_> = GsiItem::filter_by_category("boundary")
125        .limit(150)
126        .exec(&mut db)
127        .await?;
128
129    assert_eq!(items.len(), 150);
130
131    Ok(())
132}
133
134#[driver_test(requires(not(sql)))]
135pub async fn limit_offset_spans_page_boundary_gsi(t: &mut Test) -> Result<()> {
136    #[derive(Debug, toasty::Model)]
137    struct GsiItem {
138        #[key]
139        #[auto]
140        id: uuid::Uuid,
141
142        #[index]
143        category: String,
144
145        seq: i64,
146        payload: String,
147    }
148
149    let mut db = t.setup_db(models!(GsiItem)).await;
150
151    let payload = "x".repeat(10_000);
152    for i in 0..200_i64 {
153        toasty::create!(GsiItem {
154            category: "boundary",
155            seq: i,
156            payload: payload.clone(),
157        })
158        .exec(&mut db)
159        .await?;
160    }
161
162    // DDB GSI ordering is only guaranteed on the GSI sort key; seq is not the
163    // sort key here, so only assert the count.
164    let items: Vec<_> = GsiItem::filter_by_category("boundary")
165        .limit(100)
166        .offset(50)
167        .exec(&mut db)
168        .await?;
169
170    assert_eq!(items.len(), 100);
171
172    Ok(())
173}