Skip to main content

toasty_driver_integration_suite/tests/
field_version.rs

1use crate::prelude::*;
2
3/// A newly created record starts with version == 1.
4#[driver_test(requires(not(sql)), scenario(crate::scenarios::versioned_item))]
5pub async fn create_initializes_version(test: &mut Test) -> Result<()> {
6    let mut db = setup(test).await;
7
8    let item = toasty::create!(Item { name: "hello" })
9        .exec(&mut db)
10        .await?;
11    assert_eq!(item.version, 1);
12
13    Ok(())
14}
15
16/// Updating a record increments the version.
17#[driver_test(requires(not(sql)), scenario(crate::scenarios::versioned_item))]
18pub async fn update_increments_version(test: &mut Test) -> Result<()> {
19    let mut db = setup(test).await;
20
21    let mut item = toasty::create!(Item { name: "hello" })
22        .exec(&mut db)
23        .await?;
24    assert_eq!(item.version, 1);
25
26    item.update().name("world").exec(&mut db).await?;
27    assert_eq!(item.version, 2);
28
29    item.update().name("again").exec(&mut db).await?;
30    assert_eq!(item.version, 3);
31
32    Ok(())
33}
34
35/// Two updates from the same stale snapshot — the second should fail with a
36/// condition-check error because the DB version has already moved to 2.
37#[driver_test(requires(not(sql)), scenario(crate::scenarios::versioned_item))]
38pub async fn stale_update_fails(test: &mut Test) -> Result<()> {
39    let mut db = setup(test).await;
40
41    let mut item = toasty::create!(Item { name: "hello" })
42        .exec(&mut db)
43        .await?;
44    assert_eq!(item.version, 1);
45
46    // Load a second handle from the DB — same record, version == 1.
47    let mut stale = Item::filter_by_id(item.id).get(&mut db).await?;
48    assert_eq!(stale.version, 1);
49
50    // First update succeeds: DB version goes 1 → 2.
51    item.update().name("updated").exec(&mut db).await?;
52    assert_eq!(item.version, 2);
53
54    // Stale handle still has version == 1; this should fail.
55    let result: Result<()> = stale.update().name("should fail").exec(&mut db).await;
56    assert!(
57        result.is_err(),
58        "expected stale update to fail, but it succeeded"
59    );
60
61    Ok(())
62}
63
64/// Creating the same primary key twice should fail because of the
65/// attribute_not_exists condition on the version column.
66#[driver_test(requires(not(sql)), scenario(crate::scenarios::versioned_item))]
67pub async fn duplicate_create_fails(test: &mut Test) -> Result<()> {
68    let mut db = setup(test).await;
69
70    let item = toasty::create!(Item { name: "original" })
71        .exec(&mut db)
72        .await?;
73
74    let result = toasty::create!(Item {
75        id: item.id,
76        name: "duplicate"
77    })
78    .exec(&mut db)
79    .await;
80
81    assert!(
82        result.is_err(),
83        "expected duplicate create to fail, but it succeeded"
84    );
85
86    Ok(())
87}
88
89/// Batch-creating multiple versioned items should initialize all versions to 1,
90/// and a duplicate within the batch should fail.
91#[driver_test(requires(not(sql)), scenario(crate::scenarios::versioned_item))]
92pub async fn batch_insert_checks_version(test: &mut Test) -> Result<()> {
93    let mut db = setup(test).await;
94
95    // Create two items in a single batch — both should succeed with version == 1.
96    let items = toasty::create!(Item::[
97        { name: "first" },
98        { name: "second" },
99    ])
100    .exec(&mut db)
101    .await?;
102
103    assert_eq!(items.len(), 2);
104    assert!(items.iter().all(|i| i.version == 1));
105
106    // Attempt to batch-create a new item alongside a duplicate ID — should fail.
107    let existing_id = items[0].id;
108    let result = toasty::create!(Item::[
109        { id: existing_id, name: "duplicate" },
110        { name: "new" },
111    ])
112    .exec(&mut db)
113    .await;
114
115    assert!(
116        result.is_err(),
117        "expected batch create with duplicate to fail"
118    );
119
120    Ok(())
121}
122
123/// Query-based update on a versioned model: exercises update_by_key path 2
124/// (no unique index, N keys via transact_write_items on DDB).
125///
126/// Query-based updates don't carry a per-item version condition, so the version
127/// column is not incremented. The test verifies that the multi-key transact
128/// path executes without error and applies all assignments.
129#[driver_test(requires(not(sql)), scenario(crate::scenarios::tagged_item))]
130pub async fn query_update_multi_key_works(test: &mut Test) -> Result<()> {
131    let mut db = setup(test).await;
132
133    // Create two items sharing the same tag
134    let items = toasty::create!(Item::[
135        { tag: "batch", status: "active", name: "alpha" },
136        { tag: "batch", status: "active", name: "beta" },
137    ])
138    .exec(&mut db)
139    .await?;
140
141    // Update all items with tag == "batch" in one query-based operation.
142    Item::filter_by_tag("batch")
143        .update()
144        .name("updated")
145        .exec(&mut db)
146        .await?;
147
148    let a2 = Item::filter_by_id(items[0].id).get(&mut db).await?;
149    let b2 = Item::filter_by_id(items[1].id).get(&mut db).await?;
150    assert_eq!(a2.name, "updated");
151    assert_eq!(b2.name, "updated");
152
153    Ok(())
154}
155
156/// Updating a record through the unique-index path (path 3) increments the
157/// version when the unique column changes.
158#[driver_test(
159    requires(not(sql)),
160    scenario(crate::scenarios::versioned_user_unique_email)
161)]
162pub async fn unique_index_update_increments_version(test: &mut Test) -> Result<()> {
163    let mut db = setup(test).await;
164
165    let mut user = toasty::create!(User {
166        email: "alice@example.com"
167    })
168    .exec(&mut db)
169    .await?;
170    assert_eq!(user.version, 1);
171
172    user.update()
173        .email("alice2@example.com")
174        .exec(&mut db)
175        .await?;
176    assert_eq!(user.version, 2);
177
178    user.update()
179        .email("alice3@example.com")
180        .exec(&mut db)
181        .await?;
182    assert_eq!(user.version, 3);
183
184    Ok(())
185}
186
187/// Stale update on a model with a unique index: the second update from a stale
188/// snapshot should fail.
189#[driver_test(
190    requires(not(sql)),
191    scenario(crate::scenarios::versioned_user_unique_email)
192)]
193pub async fn unique_index_stale_update_fails(test: &mut Test) -> Result<()> {
194    let mut db = setup(test).await;
195
196    let mut user = toasty::create!(User {
197        email: "bob@example.com"
198    })
199    .exec(&mut db)
200    .await?;
201    assert_eq!(user.version, 1);
202
203    let mut stale = User::filter_by_email("bob@example.com")
204        .get(&mut db)
205        .await?;
206    assert_eq!(stale.version, 1);
207
208    // Advance user.version to 2
209    user.update()
210        .email("bob2@example.com")
211        .exec(&mut db)
212        .await?;
213    assert_eq!(user.version, 2);
214
215    // Stale handle (version == 1) should fail
216    let result: Result<()> = stale.update().email("bob3@example.com").exec(&mut db).await;
217    assert!(
218        result.is_err(),
219        "expected stale unique-index update to fail"
220    );
221
222    Ok(())
223}
224
225/// Deleting a record checks the version — a fresh handle succeeds.
226#[driver_test(requires(not(sql)), scenario(crate::scenarios::versioned_item))]
227pub async fn delete_checks_version(test: &mut Test) -> Result<()> {
228    let mut db = setup(test).await;
229
230    let item = toasty::create!(Item { name: "hello" })
231        .exec(&mut db)
232        .await?;
233    assert_eq!(item.version, 1);
234    let id = item.id;
235
236    item.delete().exec(&mut db).await?;
237
238    // Item should be gone — get() should return not-found
239    let after_delete = Item::filter_by_id(id).get(&mut db).await;
240    assert!(after_delete.is_err(), "item should have been deleted");
241
242    Ok(())
243}
244
245/// Deleting from a stale snapshot (wrong version) should fail.
246#[driver_test(requires(not(sql)), scenario(crate::scenarios::versioned_item))]
247pub async fn stale_delete_fails(test: &mut Test) -> Result<()> {
248    let mut db = setup(test).await;
249
250    let mut item = toasty::create!(Item { name: "hello" })
251        .exec(&mut db)
252        .await?;
253    assert_eq!(item.version, 1);
254
255    // Load a stale copy and then advance item.version to 2.
256    let stale = Item::filter_by_id(item.id).get(&mut db).await?;
257    item.update().name("updated").exec(&mut db).await?;
258    assert_eq!(item.version, 2);
259
260    // stale.version == 1 — delete should fail.
261    let result: Result<()> = stale.delete().exec(&mut db).await;
262    assert!(result.is_err(), "expected stale delete to fail");
263
264    // Item should still exist.
265    let _ = Item::filter_by_id(item.id)
266        .get(&mut db)
267        .await
268        .expect("item should still exist");
269
270    Ok(())
271}