11using System ;
2+ using System . Linq ;
3+ using System . Net . Http ;
24using System . Threading . Tasks ;
5+ using AngleSharp . Html . Dom ;
6+ using AngleSharp . Html . Parser ;
37using LinkDotNet . Blog . Infrastructure . Persistence ;
8+ using LinkDotNet . Blog . Infrastructure . Persistence . Sql ;
9+ using LinkDotNet . Blog . TestUtilities ;
410using LinkDotNet . Blog . Web ;
511using Microsoft . AspNetCore . Mvc . Testing ;
12+ using Microsoft . EntityFrameworkCore ;
13+ using Microsoft . Extensions . DependencyInjection ;
614using TestContext = Xunit . TestContext ;
715
816namespace LinkDotNet . Blog . IntegrationTests ;
917
1018public sealed class SmokeTests : IClassFixture < WebApplicationFactory < Program > > , IDisposable , IAsyncDisposable
1119{
1220 private readonly WebApplicationFactory < Program > factory ;
21+ private readonly IServiceScope scope ;
22+ private readonly HttpClient client ;
1323
1424 public SmokeTests ( WebApplicationFactory < Program > factory )
1525 {
1626 this . factory = factory . WithWebHostBuilder ( builder =>
1727 {
28+ builder . UseSetting ( "BlogName" , "Tests Title" ) ;
1829 builder . UseSetting ( "PersistenceProvider" , PersistenceProvider . Sqlite . Key ) ;
1930 builder . UseSetting ( "ConnectionString" , "DataSource=file::memory:?cache=shared" ) ;
2031 } ) ;
32+
33+ scope = this . factory . Services . CreateScope ( ) ;
34+ client = this . factory . CreateClient ( ) ;
2135 }
2236
2337 [ Fact ]
2438 public async Task ShouldBootUpApplication ( )
2539 {
26- using var client = factory . CreateClient ( ) ;
27-
28- var result = await client . GetAsync ( "/" , cancellationToken : TestContext . Current . CancellationToken ) ;
29-
30- result . IsSuccessStatusCode . ShouldBeTrue ( ) ;
31- }
32-
33- [ Fact ]
34- public async Task ShouldBootUpWithSqlDatabase ( )
35- {
36- await using var sqlFactory = factory . WithWebHostBuilder ( builder =>
37- {
38- builder . UseSetting ( "PersistenceProvider" , PersistenceProvider . Sqlite . Key ) ;
39- builder . UseSetting ( "ConnectionString" , "DataSource=file::memory:?cache=shared" ) ;
40- } ) ;
41- using var client = sqlFactory . CreateClient ( ) ;
42-
4340 var result = await client . GetAsync ( "/" , cancellationToken : TestContext . Current . CancellationToken ) ;
4441
4542 result . IsSuccessStatusCode . ShouldBeTrue ( ) ;
@@ -48,8 +45,6 @@ public async Task ShouldBootUpWithSqlDatabase()
4845 [ Fact ]
4946 public async Task ShouldAllowDotsForTagSearch ( )
5047 {
51- using var client = factory . CreateClient ( ) ;
52-
5348 var result = await client . GetAsync ( "/searchByTag/.NET5" , cancellationToken : TestContext . Current . CancellationToken ) ;
5449
5550 result . IsSuccessStatusCode . ShouldBeTrue ( ) ;
@@ -58,8 +53,6 @@ public async Task ShouldAllowDotsForTagSearch()
5853 [ Fact ]
5954 public async Task ShouldAllowDotsForFreeTextSearch ( )
6055 {
61- using var client = factory . CreateClient ( ) ;
62-
6356 var result = await client . GetAsync ( "/search/.NET5" , cancellationToken : TestContext . Current . CancellationToken ) ;
6457
6558 result . IsSuccessStatusCode . ShouldBeTrue ( ) ;
@@ -69,7 +62,6 @@ public async Task ShouldAllowDotsForFreeTextSearch()
6962 public async Task RssFeedShouldBeRateLimited ( )
7063 {
7164 const int numberOfRequests = 16 ;
72- using var client = factory . CreateClient ( ) ;
7365
7466 for ( var i = 0 ; i < numberOfRequests - 1 ; i ++ )
7567 {
@@ -81,10 +73,55 @@ public async Task RssFeedShouldBeRateLimited()
8173 lastResult . IsSuccessStatusCode . ShouldBeFalse ( ) ;
8274 }
8375
84- public void Dispose ( ) => factory ? . Dispose ( ) ;
76+ [ Fact ]
77+ public async Task ShowingBlogPost_ShouldOnlyHaveOneTitle ( )
78+ {
79+ var blogPost = new BlogPostBuilder ( ) . WithTitle ( "My Blog Post" ) . Build ( ) ;
80+ var contextProvider = scope . ServiceProvider . GetRequiredService < IDbContextFactory < BlogDbContext > > ( ) ;
81+ await using var context = await contextProvider . CreateDbContextAsync ( TestContext . Current . CancellationToken ) ;
82+ await context . BlogPosts . AddAsync ( blogPost , TestContext . Current . CancellationToken ) ;
83+ await context . SaveChangesAsync ( TestContext . Current . CancellationToken ) ;
84+
85+ var result = await client . GetAsync ( $ "/blogPost/{ blogPost . Id } ", cancellationToken : TestContext . Current . CancellationToken ) ;
86+
87+ result . IsSuccessStatusCode . ShouldBeTrue ( ) ;
88+ var content = await result . Content . ReadAsStringAsync ( TestContext . Current . CancellationToken ) ;
89+ var document = GetHtmlDocument ( content ) ;
90+ var titleTags = document . QuerySelectorAll ( "title" ) ;
91+ titleTags . Length . ShouldBe ( 1 ) ;
92+ titleTags . Single ( ) . TextContent . ShouldBe ( "My Blog Post" ) ;
93+ }
94+
95+ [ Fact ]
96+ public async Task IndexPage_HasTitleFromConfiguration ( )
97+ {
98+ var result = await client . GetAsync ( "/" , cancellationToken : TestContext . Current . CancellationToken ) ;
99+
100+ result . IsSuccessStatusCode . ShouldBeTrue ( ) ;
101+ var content = await result . Content . ReadAsStringAsync ( TestContext . Current . CancellationToken ) ;
102+ var document = GetHtmlDocument ( content ) ;
103+ var titleTags = document . QuerySelectorAll ( "title" ) ;
104+ titleTags . Length . ShouldBe ( 1 ) ;
105+ titleTags . Single ( ) . TextContent . ShouldBe ( "Tests Title" ) ;
106+ }
107+
108+ public void Dispose ( )
109+ {
110+ scope . Dispose ( ) ;
111+ client . Dispose ( ) ;
112+ factory ? . Dispose ( ) ;
113+ }
85114
86115 public async ValueTask DisposeAsync ( )
87116 {
117+ scope . Dispose ( ) ;
118+ client . Dispose ( ) ;
88119 await factory . DisposeAsync ( ) ;
89120 }
121+
122+ private static IHtmlDocument GetHtmlDocument ( string html )
123+ {
124+ var parser = new HtmlParser ( ) ;
125+ return parser . ParseDocument ( html ) ;
126+ }
90127}
0 commit comments