@@ -2,81 +2,191 @@ package com.example.shoppingcart.impl
22
33import java .util .UUID
44
5- import akka .actor .ActorSystem
6- import akka .actor .BootstrapSetup
7- import akka .actor .setup .ActorSystemSetup
5+ import akka .actor .testkit .typed .scaladsl .LogCapturing
86import akka .actor .testkit .typed .scaladsl .ScalaTestWithActorTestKit
9- import akka .actor .typed
10- import akka .persistence .testkit .scaladsl .EventSourcedBehaviorTestKit
117import akka .persistence .typed .PersistenceId
12- import com .example .shoppingcart .impl .ShoppingCart .AddItem
13- import com .example .shoppingcart .impl .ShoppingCart .Confirmation
14- import com .lightbend .lagom .scaladsl .playjson .JsonSerializerRegistry
15- import com .typesafe .config .Config
16- import com .typesafe .config .ConfigFactory
178import org .scalatest .wordspec .AnyWordSpecLike
189
19-
20- /**
21- * ConfigFactory.load will read the serialization settings from application.conf
22- */
2310class ShoppingCartEntitySpec
24- extends AbstractShoppingCartEntitySpec (
25- EventSourcedBehaviorTestKit .config.withFallback(ConfigFactory .load)
26- )
27-
28- /**
29- * CustomConfigShoppingCartEntitySpec demonstrates an alternative to ShoppingCartEntitySpec that
30- * uses custom configuration instead of relying on `ConfigFactory.load`
31- */
32- object CustomConfigShoppingCartEntitySpec {
33- val testConfig =
34- ConfigFactory .parseString("""
35- |akka.actor {
36- | serialization-bindings {
37- | "com.example.shoppingcart.impl.ShoppingCart$CommandSerializable" = jackson-json
38- | }
39- |}
40- |""" .stripMargin)
41- }
42-
43- class CustomConfigShoppingCartEntitySpec
44- extends AbstractShoppingCartEntitySpec (
45- EventSourcedBehaviorTestKit .config.withFallback(CustomConfigShoppingCartEntitySpec .testConfig)
46- )
47-
48- object AbstractShoppingCartEntitySpec {
49- private val userSerializationRegistry = ShoppingCartSerializerRegistry
50- // This method is unexpected complexity in order to build a typed ActorSystem with
51- // the user's `ShoppingCartSerializerRegistry` registered so that user messages can
52- // still use Lagom's play-json serializers with Akka Persistence Typed.
53- def typedActorSystem (name : String , config : Config ): typed.ActorSystem [Nothing ] = {
54- val setup : ActorSystemSetup =
55- ActorSystemSetup (
56- BootstrapSetup (classLoader = Some (classOf [AbstractShoppingCartEntitySpec ].getClassLoader), config = Some (config), None ),
57- JsonSerializerRegistry .serializationSetupFor(userSerializationRegistry)
58- )
59- import akka .actor .typed .scaladsl .adapter ._
60- ActorSystem (name, setup).toTyped
61- }
62-
63- }
64-
65- abstract class AbstractShoppingCartEntitySpec (config : Config )
66- extends ScalaTestWithActorTestKit (AbstractShoppingCartEntitySpec .typedActorSystem(" ShoppingCartEntitySpec" , config))
67- with AnyWordSpecLike {
11+ extends ScalaTestWithActorTestKit (s """
12+ |akka.persistence.journal.plugin = "akka.persistence.journal.inmem"
13+ |akka.persistence.snapshot-store.plugin = "akka.persistence.snapshot-store.local"
14+ |akka.persistence.snapshot-store.local.dir = "target/snapshot- ${UUID
15+ .randomUUID()
16+ .toString}"
17+ | """ .stripMargin)
18+ with AnyWordSpecLike
19+ with LogCapturing {
6820
6921 private def randomId (): String = UUID .randomUUID().toString
7022
7123 " ShoppingCart" must {
7224 " add an item" in {
73- val entity = EventSourcedBehaviorTestKit [ShoppingCart .Command , ShoppingCart .Event , ShoppingCart ](
74- system,
75- ShoppingCart (PersistenceId (" ShoppingCart" , randomId()))
76- )
25+ val probe = createTestProbe[ShoppingCart .Confirmation ]()
26+ val shoppingCart = spawn(ShoppingCart (PersistenceId (" ShoppingCart" , randomId())))
27+ shoppingCart ! ShoppingCart .AddItem (UUID .randomUUID().toString, 2 , probe.ref)
28+
29+ probe.expectMessageType[ShoppingCart .Accepted ]
30+ }
31+
32+ " remove an item" in {
33+ val probe = createTestProbe[ShoppingCart .Confirmation ]()
34+ val shoppingCart = spawn(ShoppingCart (PersistenceId (" ShoppingCart" , randomId())))
35+
36+ // First add a item
37+ val itemId = randomId()
38+ shoppingCart ! ShoppingCart .AddItem (itemId, 2 , probe.ref)
39+ probe.expectMessageType[ShoppingCart .Accepted ]
40+
41+ // Then remove the item
42+ shoppingCart ! ShoppingCart .RemoveItem (itemId, probe.ref)
43+ probe.receiveMessage() match {
44+ case ShoppingCart .Accepted (summary) => summary.items.contains(itemId) shouldBe false
45+ case ShoppingCart .Rejected (reason) => fail(s " Message was rejected with reason: $reason" )
46+ }
47+ }
48+
49+ " update item quantity" in {
50+ val probe = createTestProbe[ShoppingCart .Confirmation ]()
51+ val shoppingCart = spawn(ShoppingCart (PersistenceId (" ShoppingCart" , randomId())))
52+
53+ // First add a item
54+ val itemId = randomId()
55+ shoppingCart ! ShoppingCart .AddItem (itemId, 2 , probe.ref)
56+ probe.expectMessageType[ShoppingCart .Accepted ]
57+
58+ // Update item quantity
59+ shoppingCart ! ShoppingCart .AdjustItemQuantity (itemId, 5 , probe.ref)
60+ probe.receiveMessage() match {
61+ case ShoppingCart .Accepted (summary) => summary.items.get(itemId) shouldBe Some (5 )
62+ case ShoppingCart .Rejected (reason) => fail(s " Message was rejected with reason: $reason" )
63+ }
64+ }
65+
66+ " allow checking out" in {
67+ val probe = createTestProbe[ShoppingCart .Confirmation ]()
68+ val shoppingCart = spawn(ShoppingCart (PersistenceId (" ShoppingCart" , randomId())))
69+
70+ // First add a item
71+ shoppingCart ! ShoppingCart .AddItem (randomId(), 2 , probe.ref)
72+ probe.expectMessageType[ShoppingCart .Accepted ]
73+
74+ // Checkout shopping cart
75+ shoppingCart ! ShoppingCart .Checkout (probe.ref)
76+ probe.receiveMessage() match {
77+ case ShoppingCart .Accepted (summary) => summary.checkedOut shouldBe true
78+ case ShoppingCart .Rejected (reason) => fail(s " Message was rejected with reason: $reason" )
79+ }
80+ }
81+
82+ " allow getting shopping cart summary" in {
83+ val probeAdd = createTestProbe[ShoppingCart .Confirmation ]()
84+ val shoppingCart = spawn(ShoppingCart (PersistenceId (" ShoppingCart" , randomId())))
85+
86+ // First add a item
87+ val itemId = randomId()
88+ shoppingCart ! ShoppingCart .AddItem (itemId, 2 , probeAdd.ref)
89+
90+ // Get the summary
91+ // Use another probe since ShoppingCart.Get does not return a ShoppingCart.Confirmation
92+ val probeGet = createTestProbe[ShoppingCart .Summary ]()
93+ shoppingCart ! ShoppingCart .Get (probeGet.ref)
94+ probeGet.receiveMessage().items.get(itemId) shouldBe Some (2 )
95+ }
96+
97+ " fail when removing an item that isn't added" in {
98+ val probe = createTestProbe[ShoppingCart .Confirmation ]()
99+ val shoppingCart = spawn(ShoppingCart (PersistenceId (" ShoppingCart" , randomId())))
100+
101+ // First add a item
102+ val itemId = randomId()
103+ shoppingCart ! ShoppingCart .AddItem (itemId, 2 , probe.ref)
104+ probe.expectMessageType[ShoppingCart .Accepted ]
105+
106+ // Removing is idempotent, so command will not be Rejected
107+ val toRemoveItemId = randomId()
108+ shoppingCart ! ShoppingCart .RemoveItem (toRemoveItemId, probe.ref)
109+ probe.receiveMessage() match {
110+ case ShoppingCart .Accepted (summary) => summary.items.get(itemId) shouldBe Some (2 )
111+ case ShoppingCart .Rejected (reason) => fail(s " Message was rejected with reason: $reason" )
112+ }
113+ }
114+
115+ " fail when adding a negative number of items" in {
116+ val probe = createTestProbe[ShoppingCart .Confirmation ]()
117+ val shoppingCart = spawn(ShoppingCart (PersistenceId (" ShoppingCart" , randomId())))
118+
119+ val quantity = - 2
120+ shoppingCart ! ShoppingCart .AddItem (randomId(), quantity, probe.ref)
121+ probe.expectMessage(ShoppingCart .Rejected (" Quantity must be greater than zero" ))
122+ }
123+
124+ " fail when adjusting item quantity to negative number" in {
125+ val probe = createTestProbe[ShoppingCart .Confirmation ]()
126+ val shoppingCart = spawn(ShoppingCart (PersistenceId (" ShoppingCart" , randomId())))
127+
128+ // First add a item so it is possible to checkout
129+ val itemId = randomId()
130+ shoppingCart ! ShoppingCart .AddItem (itemId, 2 , probe.ref)
131+ probe.expectMessageType[ShoppingCart .Accepted ]
132+
133+ val quantity = - 2
134+ shoppingCart ! ShoppingCart .AdjustItemQuantity (itemId, quantity, probe.ref)
135+ probe.expectMessage(ShoppingCart .Rejected (" Quantity must be greater than zero" ))
136+ }
137+
138+ " fail when adjusting quantity for an item that isn't added" in {
139+ val probe = createTestProbe[ShoppingCart .Confirmation ]()
140+ val shoppingCart = spawn(ShoppingCart (PersistenceId (" ShoppingCart" , randomId())))
141+
142+ val itemId = randomId()
143+ shoppingCart ! ShoppingCart .AdjustItemQuantity (itemId, 2 , probe.ref)
144+ probe.expectMessage(ShoppingCart .Rejected (s " Cannot adjust quantity for item ' $itemId'. Item not present on cart " ))
145+ }
146+
147+ " fail when adding an item to a checked out cart" in {
148+ val probe = createTestProbe[ShoppingCart .Confirmation ]()
149+ val shoppingCart = spawn(ShoppingCart (PersistenceId (" ShoppingCart" , randomId())))
150+
151+ // First add a item so it is possible to checkout
152+ val itemId = randomId()
153+ shoppingCart ! ShoppingCart .AddItem (itemId, 2 , probe.ref)
154+ probe.expectMessageType[ShoppingCart .Accepted ]
155+
156+ // Then checkout the shopping cart
157+ shoppingCart ! ShoppingCart .Checkout (probe.ref)
158+ probe.expectMessageType[ShoppingCart .Accepted ]
159+
160+ // Then fail when adding new items
161+ shoppingCart ! ShoppingCart .AddItem (randomId(), 2 , probe.ref)
162+ probe.expectMessage(ShoppingCart .Rejected (" Cannot add an item to a checked-out cart" ))
163+ }
164+
165+ " fail when checking out twice" in {
166+ val probe = createTestProbe[ShoppingCart .Confirmation ]()
167+ val shoppingCart = spawn(ShoppingCart (PersistenceId (" ShoppingCart" , randomId())))
168+
169+ // First add a item so it is possible to checkout
170+ val itemId = randomId()
171+ shoppingCart ! ShoppingCart .AddItem (itemId, 2 , probe.ref)
172+ probe.expectMessageType[ShoppingCart .Accepted ]
173+
174+ // Then checkout the shopping cart
175+ shoppingCart ! ShoppingCart .Checkout (probe.ref)
176+ probe.expectMessageType[ShoppingCart .Accepted ]
177+
178+ // Then fail to checkout again
179+ shoppingCart ! ShoppingCart .Checkout (probe.ref)
180+ probe.expectMessage(ShoppingCart .Rejected (" Cannot checkout a checked-out cart" ))
181+ }
182+
183+ " fail when checking out an empty cart" in {
184+ val probe = createTestProbe[ShoppingCart .Confirmation ]()
185+ val shoppingCart = spawn(ShoppingCart (PersistenceId (" ShoppingCart" , randomId())))
77186
78- val result = entity.runCommand(AddItem (" 1" , 1 , _))
79- result.reply shouldBe a[Confirmation ]
187+ // Fail to checkout empty shopping cart
188+ shoppingCart ! ShoppingCart .Checkout (probe.ref)
189+ probe.expectMessage(ShoppingCart .Rejected (" Cannot checkout an empty shopping cart" ))
80190 }
81191 }
82192}
0 commit comments