11// Copyright The OpenTelemetry Authors
22// SPDX-License-Identifier: Apache-2.0
33using System ;
4+ using System . Collections . Generic ;
5+ using System . Linq ;
46using System . Threading . Tasks ;
57using Grpc . Net . Client ;
68using Oteldemo ;
9+ using Microsoft . AspNetCore . Builder ;
10+ using Microsoft . AspNetCore . Hosting ;
711using Microsoft . AspNetCore . TestHost ;
12+ using Microsoft . Extensions . DependencyInjection ;
813using Microsoft . Extensions . Hosting ;
14+ using Microsoft . Extensions . Logging . Abstractions ;
15+ using OpenFeature ;
916using Xunit ;
17+ using cart . cartstore ;
1018using static Oteldemo . CartService ;
1119
1220namespace cart . tests ;
@@ -17,59 +25,68 @@ public class CartServiceTests
1725
1826 public CartServiceTests ( )
1927 {
28+ var valkeyAddress = Environment . GetEnvironmentVariable ( "VALKEY_ADDR" ) ?? "localhost:6379" ;
29+
2030 _host = new HostBuilder ( ) . ConfigureWebHost ( webBuilder =>
2131 {
2232 webBuilder
23- // .UseStartup<Startup>()
24- . UseTestServer ( ) ;
33+ . UseTestServer ( )
34+ . ConfigureServices ( services =>
35+ {
36+ services . AddGrpc ( ) ;
37+ services . AddSingleton < ICartStore > ( _ =>
38+ {
39+ var store = new ValkeyCartStore ( NullLogger < ValkeyCartStore > . Instance , valkeyAddress ) ;
40+ store . Initialize ( ) ;
41+ return store ;
42+ } ) ;
43+ services . AddSingleton ( sp =>
44+ new cart . services . CartService (
45+ sp . GetRequiredService < ICartStore > ( ) ,
46+ new ValkeyCartStore ( NullLogger < ValkeyCartStore > . Instance , "badhost:1234" ) ,
47+ Api . Instance . GetClient ( ) ) ) ;
48+ } )
49+ . Configure ( app =>
50+ {
51+ app . UseRouting ( ) ;
52+ app . UseEndpoints ( endpoints => endpoints . MapGrpcService < cart . services . CartService > ( ) ) ;
53+ } ) ;
2554 } ) ;
2655 }
2756
28- [ Fact ( Skip = "See https://github.com/open-telemetry/opentelemetry-demo/pull/746#discussion_r1107931240" ) ]
29- public async Task GetItem_NoAddItemBefore_EmptyCartReturned ( )
57+ private async Task < ( IHost server , CartServiceClient client ) > StartAsync ( )
3058 {
31- // Setup test server and client
32- using var server = await _host . StartAsync ( ) ;
59+ var server = await _host . StartAsync ( ) ;
3360 var httpClient = server . GetTestClient ( ) ;
34-
35- string userId = Guid . NewGuid ( ) . ToString ( ) ;
36-
37- // Create a GRPC communication channel between the client and the server
3861 var channel = GrpcChannel . ForAddress ( httpClient . BaseAddress , new GrpcChannelOptions
3962 {
4063 HttpClient = httpClient
4164 } ) ;
65+ return ( server , new CartServiceClient ( channel ) ) ;
66+ }
4267
43- var cartClient = new CartServiceClient ( channel ) ;
68+ [ Fact ]
69+ public async Task GetItem_NoAddItemBefore_EmptyCartReturned ( )
70+ {
71+ var ( server , client ) = await StartAsync ( ) ;
72+ using var _ = server ;
4473
45- var request = new GetCartRequest
46- {
47- UserId = userId ,
48- } ;
74+ string userId = Guid . NewGuid ( ) . ToString ( ) ;
4975
50- var cart = await cartClient . GetCartAsync ( request ) ;
76+ var cart = await client . GetCartAsync ( new GetCartRequest { UserId = userId } ) ;
5177 Assert . NotNull ( cart ) ;
5278
5379 // All grpc objects implement IEquitable, so we can compare equality with by-value semantics
5480 Assert . Equal ( new Cart ( ) , cart ) ;
5581 }
5682
57- [ Fact ( Skip = "See https://github.com/open-telemetry/opentelemetry-demo/pull/746#discussion_r1107931240" ) ]
83+ [ Fact ]
5884 public async Task AddItem_ItemExists_Updated ( )
5985 {
60- // Setup test server and client
61- using var server = await _host . StartAsync ( ) ;
62- var httpClient = server . GetTestClient ( ) ;
86+ var ( server , client ) = await StartAsync ( ) ;
87+ using var _ = server ;
6388
6489 string userId = Guid . NewGuid ( ) . ToString ( ) ;
65-
66- // Create a GRPC communication channel between the client and the server
67- var channel = GrpcChannel . ForAddress ( httpClient . BaseAddress , new GrpcChannelOptions
68- {
69- HttpClient = httpClient
70- } ) ;
71-
72- var client = new CartServiceClient ( channel ) ;
7390 var request = new AddItemRequest
7491 {
7592 UserId = userId ,
@@ -86,11 +103,7 @@ public async Task AddItem_ItemExists_Updated()
86103 // Second add of existing product - quantity should be updated
87104 await client . AddItemAsync ( request ) ;
88105
89- var getCartRequest = new GetCartRequest
90- {
91- UserId = userId
92- } ;
93- var cart = await client . GetCartAsync ( getCartRequest ) ;
106+ var cart = await client . GetCartAsync ( new GetCartRequest { UserId = userId } ) ;
94107 Assert . NotNull ( cart ) ;
95108 Assert . Equal ( userId , cart . UserId ) ;
96109 Assert . Single ( cart . Items ) ;
@@ -100,24 +113,13 @@ public async Task AddItem_ItemExists_Updated()
100113 await client . EmptyCartAsync ( new EmptyCartRequest { UserId = userId } ) ;
101114 }
102115
103- [ Fact ( Skip = "See https://github.com/open-telemetry/opentelemetry-demo/pull/746#discussion_r1107931240" ) ]
116+ [ Fact ]
104117 public async Task AddItem_New_Inserted ( )
105118 {
106- // Setup test server and client
107- using var server = await _host . StartAsync ( ) ;
108- var httpClient = server . GetTestClient ( ) ;
119+ var ( server , client ) = await StartAsync ( ) ;
120+ using var _ = server ;
109121
110122 string userId = Guid . NewGuid ( ) . ToString ( ) ;
111-
112- // Create a GRPC communication channel between the client and the server
113- var channel = GrpcChannel . ForAddress ( httpClient . BaseAddress , new GrpcChannelOptions
114- {
115- HttpClient = httpClient
116- } ) ;
117-
118- // Create a proxy object to work with the server
119- var client = new CartServiceClient ( channel ) ;
120-
121123 var request = new AddItemRequest
122124 {
123125 UserId = userId ,
@@ -130,10 +132,7 @@ public async Task AddItem_New_Inserted()
130132
131133 await client . AddItemAsync ( request ) ;
132134
133- var getCartRequest = new GetCartRequest
134- {
135- UserId = userId
136- } ;
135+ var getCartRequest = new GetCartRequest { UserId = userId } ;
137136 var cart = await client . GetCartAsync ( getCartRequest ) ;
138137 Assert . NotNull ( cart ) ;
139138 Assert . Equal ( userId , cart . UserId ) ;
@@ -143,4 +142,62 @@ public async Task AddItem_New_Inserted()
143142 cart = await client . GetCartAsync ( getCartRequest ) ;
144143 Assert . Empty ( cart . Items ) ;
145144 }
145+
146+ [ Fact ]
147+ public async Task AddItem_ConcurrentCallsSameProduct_NoLostUpdates ( )
148+ {
149+ var ( server , client ) = await StartAsync ( ) ;
150+ using var _ = server ;
151+
152+ string userId = Guid . NewGuid ( ) . ToString ( ) ;
153+ const int concurrentCalls = 20 ;
154+
155+ var tasks = Enumerable . Range ( 0 , concurrentCalls ) . Select ( _ => client . AddItemAsync ( new AddItemRequest
156+ {
157+ UserId = userId ,
158+ Item = new CartItem { ProductId = "race-product" , Quantity = 1 }
159+ } ) . ResponseAsync ) ;
160+
161+ await Task . WhenAll ( tasks ) ;
162+
163+ var cart = await client . GetCartAsync ( new GetCartRequest { UserId = userId } ) ;
164+ Assert . Single ( cart . Items ) ;
165+ Assert . Equal ( concurrentCalls , cart . Items [ 0 ] . Quantity ) ;
166+
167+ await client . EmptyCartAsync ( new EmptyCartRequest { UserId = userId } ) ;
168+ }
169+
170+ [ Fact ]
171+ public async Task AddItem_NegativeDeltaDuringCheckoutRace_OnlyRemovesOrderedQuantity ( )
172+ {
173+ var ( server , client ) = await StartAsync ( ) ;
174+ using var _ = server ;
175+
176+ string userId = Guid . NewGuid ( ) . ToString ( ) ;
177+
178+ await client . AddItemAsync ( new AddItemRequest
179+ {
180+ UserId = userId ,
181+ Item = new CartItem { ProductId = "ordered-product" , Quantity = 2 }
182+ } ) ;
183+
184+ await client . AddItemAsync ( new AddItemRequest
185+ {
186+ UserId = userId ,
187+ Item = new CartItem { ProductId = "concurrently-added-product" , Quantity = 1 }
188+ } ) ;
189+
190+ await client . AddItemAsync ( new AddItemRequest
191+ {
192+ UserId = userId ,
193+ Item = new CartItem { ProductId = "ordered-product" , Quantity = - 2 }
194+ } ) ;
195+
196+ var cart = await client . GetCartAsync ( new GetCartRequest { UserId = userId } ) ;
197+ var remaining = Assert . Single ( cart . Items ) ;
198+ Assert . Equal ( "concurrently-added-product" , remaining . ProductId ) ;
199+ Assert . Equal ( 1 , remaining . Quantity ) ;
200+
201+ await client . EmptyCartAsync ( new EmptyCartRequest { UserId = userId } ) ;
202+ }
146203}
0 commit comments