22using BookStore . ApiService . Commands ;
33using BookStore . ApiService . Events ;
44using BookStore . ApiService . Handlers . Authors ;
5- using BookStore . ApiService . Infrastructure ;
6- using BookStore . Shared . Models ;
7- using Marten ;
85using Microsoft . AspNetCore . Http ;
9- using Microsoft . Extensions . Caching . Hybrid ;
10- using Microsoft . Extensions . Logging ;
11- using Microsoft . Extensions . Options ;
12- using NSubstitute ;
136
147namespace BookStore . ApiService . UnitTests . Handlers ;
158
16- public class AuthorHandlerTests
9+ public class AuthorHandlerTests : HandlerTestBase
1710{
1811 [ Test ]
1912 [ Category ( "Unit" ) ]
@@ -25,24 +18,14 @@ public async Task CreateAuthorHandler_ShouldStartStreamWithAuthorAddedEvent()
2518 new Dictionary < string , AuthorTranslationDto > { [ "en" ] = new AuthorTranslationDto ( "Uncle Bob" ) }
2619 ) ;
2720
28- var session = Substitute . For < IDocumentSession > ( ) ;
29- _ = session . CorrelationId . Returns ( "test-correlation-id" ) ;
30-
31- var localizationOptions = Options . Create ( new LocalizationOptions
32- {
33- DefaultCulture = "en" ,
34- SupportedCultures = [ "en" ]
35- } ) ;
36-
3721 // Act
38- var result = await AuthorHandlers . Handle ( command , session , localizationOptions , Substitute . For < HybridCache > ( ) ,
39- Substitute . For < ILogger > ( ) ) ;
22+ var result = await AuthorHandlers . Handle ( command , Session , LocalizationOptions , Cache , Logger ) ;
4023
4124 // Assert
4225 _ = await Assert . That ( result ) . IsNotNull ( ) ;
43- _ = session . Events . Received ( 1 ) . StartStream < AuthorAggregate > (
26+ _ = Session . Events . Received ( 1 ) . StartStream < AuthorAggregate > (
4427 command . Id ,
45- Arg . Is < AuthorAdded > ( e =>
28+ Arg . Is < AuthorAdded > ( ( AuthorAdded e ) =>
4629 e . Name == "Robert C. Martin" ) ) ;
4730 }
4831
@@ -59,21 +42,12 @@ public async Task CreateAuthorHandler_WithInvalidCulture_ShouldReturnBadRequest(
5942 new Dictionary < string , AuthorTranslationDto > { [ invalidCulture ] = new AuthorTranslationDto ( "Uncle Bob" ) }
6043 ) ;
6144
62- var session = Substitute . For < IDocumentSession > ( ) ;
63- var localizationOptions = Options . Create ( new LocalizationOptions
64- {
65- DefaultCulture = "en" ,
66- SupportedCultures = [ "en" ]
67- } ) ;
68-
6945 // Act
70- var result = await AuthorHandlers . Handle ( command , session , localizationOptions , Substitute . For < HybridCache > ( ) ,
71- Substitute . For < ILogger > ( ) ) ;
46+ var result = await AuthorHandlers . Handle ( command , Session , LocalizationOptions , Cache , Logger ) ;
7247
7348 // Assert
74- // Assert
75- _ = await Assert . That ( result ) . IsAssignableTo < Microsoft . AspNetCore . Http . IStatusCodeHttpResult > ( ) ;
76- var badRequestResult = ( Microsoft . AspNetCore . Http . IStatusCodeHttpResult ) result ;
49+ _ = await Assert . That ( result ) . IsAssignableTo < IStatusCodeHttpResult > ( ) ;
50+ var badRequestResult = ( IStatusCodeHttpResult ) result ;
7751 _ = await Assert . That ( badRequestResult . StatusCode ) . IsEqualTo ( 400 ) ;
7852 }
7953
@@ -89,33 +63,24 @@ public async Task UpdateAuthorHandler_ShouldAppendAuthorUpdatedEvent()
8963 )
9064 { ETag = "test-etag" } ;
9165
92- var session = Substitute . For < IDocumentSession > ( ) ;
93- var httpContext = new DefaultHttpContext ( ) ;
94- var httpContextAccessor = Substitute . For < IHttpContextAccessor > ( ) ;
95- _ = httpContextAccessor . HttpContext . Returns ( httpContext ) ;
96-
97- var localizationOptions = Options . Create ( new LocalizationOptions
98- {
99- DefaultCulture = "en" ,
100- SupportedCultures = [ "en" ]
101- } ) ;
102-
10366 // Mock Stream State
104- _ = session . Events . FetchStreamStateAsync ( command . Id ) . Returns ( new Marten . Events . StreamState { Version = 1 } ) ;
67+ _ = Session . Events . FetchStreamStateAsync ( command . Id ) . Returns ( new Marten . Events . StreamState { Version = 1 } ) ;
10568
10669 // Mock Aggregate Load
107- var existingAggregate = CreateAuthorAggregate ( command . Id , "Old Name" , false ) ;
108- _ = session . Events . AggregateStreamAsync < AuthorAggregate > ( command . Id ) . Returns ( existingAggregate ) ;
70+ var existingAggregate = AggregateFactory . Hydrate < AuthorAggregate > (
71+ new AuthorAdded ( command . Id , "Old Name" , new Dictionary < string , AuthorTranslation > { [ "en" ] = new ( "Bio" ) } ,
72+ DateTimeOffset . UtcNow ) ) ;
73+ _ = Session . Events . AggregateStreamAsync < AuthorAggregate > ( command . Id ) . Returns ( existingAggregate ) ;
10974
11075 // Act
111- var result = await AuthorHandlers . Handle ( command , session , httpContextAccessor , localizationOptions ,
112- Substitute . For < HybridCache > ( ) , Substitute . For < ILogger > ( ) ) ;
76+ var result =
77+ await AuthorHandlers . Handle ( command , Session , HttpContextAccessor , LocalizationOptions , Cache , Logger ) ;
11378
11479 // Assert
11580 _ = await Assert . That ( result ) . IsTypeOf < Microsoft . AspNetCore . Http . HttpResults . NoContent > ( ) ;
116- _ = session . Events . Received ( 1 ) . Append (
81+ _ = Session . Events . Received ( 1 ) . Append (
11782 command . Id ,
118- Arg . Is < AuthorUpdated > ( e =>
83+ Arg . Is < AuthorUpdated > ( ( AuthorUpdated e ) =>
11984 e . Name == "Robert C. Martin Updated" ) ) ;
12085 }
12186
@@ -127,25 +92,21 @@ public async Task SoftDeleteAuthorHandler_ShouldAppendAuthorSoftDeletedEvent()
12792 var id = Guid . CreateVersion7 ( ) ;
12893 var command = new SoftDeleteAuthor ( id ) ;
12994
130- var session = Substitute . For < IDocumentSession > ( ) ;
131- var httpContext = new DefaultHttpContext ( ) ;
132- var httpContextAccessor = Substitute . For < IHttpContextAccessor > ( ) ;
133- _ = httpContextAccessor . HttpContext . Returns ( httpContext ) ;
134-
13595 // Mock Stream State
136- _ = session . Events . FetchStreamStateAsync ( id ) . Returns ( new Marten . Events . StreamState { Version = 1 } ) ;
96+ _ = Session . Events . FetchStreamStateAsync ( id ) . Returns ( new Marten . Events . StreamState { Version = 1 } ) ;
13797
13898 // Mock Aggregate Load
139- var existingAggregate = CreateAuthorAggregate ( id , "Author" , false ) ;
140- _ = session . Events . AggregateStreamAsync < AuthorAggregate > ( id ) . Returns ( existingAggregate ) ;
99+ var existingAggregate = AggregateFactory . Hydrate < AuthorAggregate > (
100+ new AuthorAdded ( id , "Author" , new Dictionary < string , AuthorTranslation > { [ "en" ] = new ( "Bio" ) } ,
101+ DateTimeOffset . UtcNow ) ) ;
102+ _ = Session . Events . AggregateStreamAsync < AuthorAggregate > ( id ) . Returns ( existingAggregate ) ;
141103
142104 // Act
143- var result = await AuthorHandlers . Handle ( command , session , httpContextAccessor , Substitute . For < HybridCache > ( ) ,
144- Substitute . For < ILogger > ( ) ) ;
105+ var result = await AuthorHandlers . Handle ( command , Session , HttpContextAccessor , Cache , Logger ) ;
145106
146107 // Assert
147108 _ = await Assert . That ( result ) . IsTypeOf < Microsoft . AspNetCore . Http . HttpResults . NoContent > ( ) ;
148- _ = session . Events . Received ( 1 ) . Append (
109+ _ = Session . Events . Received ( 1 ) . Append (
149110 id ,
150111 Arg . Is < AuthorSoftDeleted > ( e => e . Id == id ) ) ;
151112 }
@@ -158,43 +119,23 @@ public async Task RestoreAuthorHandler_ShouldAppendAuthorRestoredEvent()
158119 var id = Guid . CreateVersion7 ( ) ;
159120 var command = new RestoreAuthor ( id ) ;
160121
161- var session = Substitute . For < IDocumentSession > ( ) ;
162- var httpContext = new DefaultHttpContext ( ) ;
163- var httpContextAccessor = Substitute . For < IHttpContextAccessor > ( ) ;
164- _ = httpContextAccessor . HttpContext . Returns ( httpContext ) ;
165-
166122 // Mock Stream State
167- _ = session . Events . FetchStreamStateAsync ( id ) . Returns ( new Marten . Events . StreamState { Version = 1 } ) ;
123+ _ = Session . Events . FetchStreamStateAsync ( id ) . Returns ( new Marten . Events . StreamState { Version = 1 } ) ;
168124
169125 // Mock Aggregate Load - Create DELETED aggregate
170- var existingAggregate = CreateAuthorAggregate ( id , "Author" , true ) ;
171- _ = session . Events . AggregateStreamAsync < AuthorAggregate > ( id ) . Returns ( existingAggregate ) ;
126+ var existingAggregate = AggregateFactory . Hydrate < AuthorAggregate > (
127+ new AuthorAdded ( id , "Author" , new Dictionary < string , AuthorTranslation > { [ "en" ] = new ( "Bio" ) } ,
128+ DateTimeOffset . UtcNow ) ,
129+ new AuthorSoftDeleted ( id , DateTimeOffset . UtcNow ) ) ;
130+ _ = Session . Events . AggregateStreamAsync < AuthorAggregate > ( id ) . Returns ( existingAggregate ) ;
172131
173132 // Act
174- var result = await AuthorHandlers . Handle ( command , session , httpContextAccessor , Substitute . For < HybridCache > ( ) ,
175- Substitute . For < ILogger > ( ) ) ;
133+ var result = await AuthorHandlers . Handle ( command , Session , HttpContextAccessor , Cache , Logger ) ;
176134
177135 // Assert
178136 _ = await Assert . That ( result ) . IsTypeOf < Microsoft . AspNetCore . Http . HttpResults . NoContent > ( ) ;
179- _ = session . Events . Received ( 1 ) . Append (
137+ _ = Session . Events . Received ( 1 ) . Append (
180138 id ,
181139 Arg . Is < AuthorRestored > ( e => e . Id == id ) ) ;
182140 }
183-
184- static AuthorAggregate CreateAuthorAggregate ( Guid id , string name , bool isDeleted )
185- {
186- // Create instance (AuthorAggregate has parameterless constructor but it might be private/internal?)
187- // It's public class, but properties are private set.
188- // Assuming default constructor is accessible or we use Activator.
189- var aggregate = ( AuthorAggregate ) Activator . CreateInstance ( typeof ( AuthorAggregate ) , true ) ! ;
190-
191- // Set properties via reflection
192- typeof ( AuthorAggregate ) . GetProperty ( nameof ( AuthorAggregate . Id ) ) ! . SetValue ( aggregate , id ) ;
193- typeof ( AuthorAggregate ) . GetProperty ( nameof ( AuthorAggregate . Name ) ) ! . SetValue ( aggregate , name ) ;
194- typeof ( AuthorAggregate ) . GetProperty ( nameof ( AuthorAggregate . Deleted ) ) ! . SetValue ( aggregate , isDeleted ) ;
195- typeof ( AuthorAggregate ) . GetProperty ( nameof ( AuthorAggregate . Translations ) ) ! . SetValue ( aggregate ,
196- new Dictionary < string , AuthorTranslation > { [ "en" ] = new ( "Bio" ) } ) ;
197-
198- return aggregate ;
199- }
200141}
0 commit comments