Skip to content

Commit 0c02718

Browse files
committed
Tests
1 parent 4d4d8d1 commit 0c02718

File tree

3 files changed

+127
-8
lines changed

3 files changed

+127
-8
lines changed

src/FSharp.AWS.DynamoDB/TableContext.fs

+1
Original file line numberDiff line numberDiff line change
@@ -805,6 +805,7 @@ type TableContext<'TRecord> internal
805805
/// <param name="clientRequestToken">The <c>ClientRequestToken</c> to supply as an idempotency key (10 minute window).</param>
806806
member _.TransactWriteItems(items : seq<TransactWrite<'TRecord>>, ?clientRequestToken) : Async<unit> = async {
807807
let reqs = TransactWriteItemsRequest.toTransactItems tableName template items
808+
if (Seq.isEmpty reqs) || reqs.Count > 25 then raise <| System.ArgumentOutOfRangeException(nameof items, "must be between 1 and 25 items.")
808809
let req = TransactWriteItemsRequest(ReturnConsumedCapacity = returnConsumedCapacity, TransactItems = reqs)
809810
clientRequestToken |> Option.iter (fun x -> req.ClientRequestToken <- x)
810811
let! ct = Async.CancellationToken

tests/FSharp.AWS.DynamoDB.Tests/MetricsCollectorTests.fs

+38-1
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ type TestCollector() =
4747

4848
member _.Metrics = metrics |> Seq.toList
4949

50+
member _.Clear() = metrics.Clear()
51+
5052
let (|TotalCu|) : ConsumedCapacity list -> float = Seq.sumBy (fun c -> c.CapacityUnits)
5153

5254
/// Tests without common setup
@@ -79,6 +81,42 @@ type Tests(fixture : TableFixture) =
7981
cu > 0
8082
| _ -> false @>
8183

84+
let compile = rawTable.Template.PrecomputeConditionalExpr
85+
86+
let [<Fact>] ``Collect Metrics on Transactional PutItem`` () = async {
87+
let item = mkItem (guid()) (guid()) 0
88+
let _ = sut.PutItem item
89+
let simpleCu = trap <@ match collector.Metrics with [{ ConsumedCapacity = TotalCu cu }] -> cu | x -> failwithf "Unexpected %A" x @>
90+
collector.Clear()
91+
92+
let item = mkItem (guid()) (guid()) 0
93+
let requests = [TransactWrite.Put (item, Some (compile <@ fun t -> NOT_EXISTS t.RangeKey @>)) ]
94+
95+
do! sut.TransactWriteItems requests
96+
97+
test <@ match collector.Metrics with
98+
| [{ ItemCount = 1; Operation = TransactWriteItems; TableName = ExpectedTableName; ConsumedCapacity = TotalCu cu }] ->
99+
cu >= simpleCu * 2. // doing it transactionally costs at least double
100+
| _ -> false @>
101+
102+
let! itemFound = sut.ContainsKeyAsync(sut.Template.ExtractKey item)
103+
test <@ itemFound @> }
104+
105+
let [<Fact>] ``No Metrics on Canceled PutItem`` () = async {
106+
let collector = TestCollector()
107+
let sut = rawTable.WithMetricsCollector(collector.Collect)
108+
109+
let item = mkItem (guid()) (guid()) 0
110+
111+
// The check will fail, which triggers a throw from the underlying AWS SDK; there's no way to extract the consumption info in that case
112+
let requests = [TransactWrite.Put (item, Some (compile <@ fun t -> EXISTS t.RangeKey @>)) ]
113+
114+
let mutable failed = false
115+
try do! sut.TransactWriteItems requests
116+
with TransactWriteItemsRequest.TransactionCanceledConditionalCheckFailed -> failed <- true
117+
true =! failed
118+
[] =! collector.Metrics }
119+
82120
interface IClassFixture<TableFixture>
83121

84122
/// Tests that look up a specific item. Each test run gets a fresh individual item
@@ -90,7 +128,6 @@ type ItemTests(fixture : TableFixture) =
90128
let item = mkItem (guid()) (guid()) 0
91129
do rawTable.PutItem item |> ignore
92130

93-
94131
let collector = TestCollector()
95132
let sut = rawTable.WithMetricsCollector(collector.Collect)
96133

tests/FSharp.AWS.DynamoDB.Tests/SimpleTableOperationTests.fs

+88-7
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
open System
44

5+
open FSharp.AWS.DynamoDB.Tests
56
open Swensen.Unquote
67
open Xunit
78

@@ -125,22 +126,102 @@ type ``Simple Table Operation Tests`` (fixture : TableFixture) =
125126
test <@ None = deletedItem @>
126127
test <@ not (table.ContainsKey key) @>
127128

129+
interface IClassFixture<TableFixture>
130+
131+
type TransactWriteItems(fixture : TableFixture) =
132+
133+
let rand = let r = Random() in fun () -> int64 <| r.Next()
134+
let mkItem() =
135+
{
136+
HashKey = guid() ; RangeKey = guid() ;
137+
Value = rand() ; Tuple = rand(), rand() ;
138+
Map = seq { for _ in 0L .. rand() % 5L -> "K" + guid(), rand() } |> Map.ofSeq
139+
Unions = [Choice1Of3 (guid()) ; Choice2Of3(rand()) ; Choice3Of3(Guid.NewGuid().ToByteArray())]
140+
}
141+
142+
let table = fixture.CreateEmpty<SimpleRecord>()
143+
let compile = table.Template.PrecomputeConditionalExpr
144+
let compileUpdate (e : Quotations.Expr<SimpleRecord -> SimpleRecord>) = table.Template.PrecomputeUpdateExpr e
145+
let doesntExistCondition = compile <@ fun t -> NOT_EXISTS t.Value @>
146+
let existsCondition = compile <@ fun t -> EXISTS t.Value @>
147+
148+
let [<Fact>] ``Minimal happy path`` () = async {
149+
let item = mkItem ()
150+
151+
let requests = [TransactWrite.Put (item, Some doesntExistCondition) ]
152+
153+
do! table.TransactWriteItems requests
154+
155+
let! itemFound = table.ContainsKeyAsync (table.Template.ExtractKey item)
156+
true =! itemFound }
157+
158+
let [<Fact>] ``Minimal Canceled path`` () = async {
159+
let item = mkItem ()
160+
161+
let requests = [TransactWrite.Put (item, Some existsCondition) ]
162+
163+
let mutable failed = false
164+
try do! table.TransactWriteItems requests
165+
with TransactWriteItemsRequest.TransactionCanceledConditionalCheckFailed -> failed <- true
166+
167+
true =! failed
168+
169+
let! itemFound = table.ContainsKeyAsync (table.Template.ExtractKey item)
170+
false =! itemFound }
171+
128172
let [<Theory; InlineData true; InlineData false>]
129173
``Condition Check outcome should affect sibling TransactWrite`` shouldFail = async {
130174
let item, item2 = mkItem (), mkItem ()
131-
let key = table.PutItem item
175+
let! key = table.PutItemAsync item
132176

133177
let requests = [
134-
if shouldFail then TransactWrite.Check (key, table.Template.PrecomputeConditionalExpr <@ fun t -> NOT_EXISTS t.Value @>)
135-
else TransactWrite.Check (key, table.Template.PrecomputeConditionalExpr <@ fun t -> EXISTS t.Value @>)
178+
if shouldFail then TransactWrite.Check (key, doesntExistCondition)
179+
else TransactWrite.Check (key, existsCondition)
136180
TransactWrite.Put (item2, None) ]
137181
let mutable failed = false
138182
try do! table.TransactWriteItems requests
139183
with TransactWriteItemsRequest.TransactionCanceledConditionalCheckFailed -> failed <- true
140184

141-
let failed = failed
142-
test <@ failed = shouldFail @>
143-
let item2Found = table.ContainsKey (table.Template.ExtractKey item2)
144-
test <@ not shouldFail = item2Found @> }
185+
failed =! shouldFail
186+
187+
let! item2Found = table.ContainsKeyAsync (table.Template.ExtractKey item2)
188+
failed =! not item2Found }
189+
190+
let [<Theory; InlineData true; InlineData false>]
191+
``All paths`` shouldFail = async {
192+
let item, item2, item3, item4, item5, item6, item7 = mkItem (), mkItem (), mkItem (), mkItem (), mkItem (), mkItem (), mkItem ()
193+
let! key = table.PutItemAsync item
194+
195+
let requests = [ TransactWrite.Update (key, Some existsCondition, compileUpdate <@ fun t -> { t with Value = 42 } @>)
196+
TransactWrite.Put (item2, None)
197+
TransactWrite.Put (item3, Some doesntExistCondition)
198+
TransactWrite.Delete (table.Template.ExtractKey item4, Some doesntExistCondition)
199+
TransactWrite.Delete (table.Template.ExtractKey item5, None)
200+
TransactWrite.Check (table.Template.ExtractKey item6, if shouldFail then existsCondition else doesntExistCondition)
201+
TransactWrite.Update (TableKey.Combined(item7.HashKey, item7.RangeKey), None, compileUpdate <@ fun t -> { t with Tuple = (42, 42)} @>) ]
202+
let mutable failed = false
203+
try do! table.TransactWriteItems requests
204+
with TransactWriteItemsRequest.TransactionCanceledConditionalCheckFailed -> failed <- true
205+
failed =! shouldFail
206+
207+
let! maybeItem = table.TryGetItemAsync key
208+
test <@ shouldFail <> (maybeItem |> Option.contains { item with Value = 42 }) @>
209+
210+
let! maybeItem2 = table.TryGetItemAsync(table.Template.ExtractKey item2)
211+
test <@ shouldFail <> (maybeItem2 |> Option.contains item2) @>
212+
213+
let! maybeItem3 = table.TryGetItemAsync(table.Template.ExtractKey item3)
214+
test <@ shouldFail <> (maybeItem3 |> Option.contains item3) @>
215+
216+
let! maybeItem7 = table.TryGetItemAsync(table.Template.ExtractKey item7)
217+
test <@ shouldFail <> (maybeItem7 |> Option.map (fun x -> x.Tuple) |> Option.contains (42, 42)) @> }
218+
219+
let [<Fact>] ``Empty is rejected`` () = async {
220+
221+
let! e = Async.Catch (table.TransactWriteItems [])
222+
223+
test <@ match e with
224+
| Choice1Of2 () -> false
225+
| Choice2Of2 e -> e :? ArgumentOutOfRangeException @> }
145226

146227
interface IClassFixture<TableFixture>

0 commit comments

Comments
 (0)