1
1
using System ;
2
+ using System . Linq ;
3
+ using System . Net . Http ;
2
4
using System . Threading . Tasks ;
5
+ using AngleSharp . Html . Dom ;
6
+ using AngleSharp . Html . Parser ;
3
7
using LinkDotNet . Blog . Infrastructure . Persistence ;
8
+ using LinkDotNet . Blog . Infrastructure . Persistence . Sql ;
9
+ using LinkDotNet . Blog . TestUtilities ;
4
10
using LinkDotNet . Blog . Web ;
5
11
using Microsoft . AspNetCore . Mvc . Testing ;
12
+ using Microsoft . EntityFrameworkCore ;
13
+ using Microsoft . Extensions . DependencyInjection ;
6
14
using TestContext = Xunit . TestContext ;
7
15
8
16
namespace LinkDotNet . Blog . IntegrationTests ;
9
17
10
18
public sealed class SmokeTests : IClassFixture < WebApplicationFactory < Program > > , IDisposable , IAsyncDisposable
11
19
{
12
20
private readonly WebApplicationFactory < Program > factory ;
21
+ private readonly IServiceScope scope ;
22
+ private readonly HttpClient client ;
13
23
14
24
public SmokeTests ( WebApplicationFactory < Program > factory )
15
25
{
16
26
this . factory = factory . WithWebHostBuilder ( builder =>
17
27
{
28
+ builder . UseSetting ( "BlogName" , "Tests Title" ) ;
18
29
builder . UseSetting ( "PersistenceProvider" , PersistenceProvider . Sqlite . Key ) ;
19
30
builder . UseSetting ( "ConnectionString" , "DataSource=file::memory:?cache=shared" ) ;
20
31
} ) ;
32
+
33
+ scope = this . factory . Services . CreateScope ( ) ;
34
+ client = this . factory . CreateClient ( ) ;
21
35
}
22
36
23
37
[ Fact ]
24
38
public async Task ShouldBootUpApplication ( )
25
39
{
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
-
43
40
var result = await client . GetAsync ( "/" , cancellationToken : TestContext . Current . CancellationToken ) ;
44
41
45
42
result . IsSuccessStatusCode . ShouldBeTrue ( ) ;
@@ -48,8 +45,6 @@ public async Task ShouldBootUpWithSqlDatabase()
48
45
[ Fact ]
49
46
public async Task ShouldAllowDotsForTagSearch ( )
50
47
{
51
- using var client = factory . CreateClient ( ) ;
52
-
53
48
var result = await client . GetAsync ( "/searchByTag/.NET5" , cancellationToken : TestContext . Current . CancellationToken ) ;
54
49
55
50
result . IsSuccessStatusCode . ShouldBeTrue ( ) ;
@@ -58,8 +53,6 @@ public async Task ShouldAllowDotsForTagSearch()
58
53
[ Fact ]
59
54
public async Task ShouldAllowDotsForFreeTextSearch ( )
60
55
{
61
- using var client = factory . CreateClient ( ) ;
62
-
63
56
var result = await client . GetAsync ( "/search/.NET5" , cancellationToken : TestContext . Current . CancellationToken ) ;
64
57
65
58
result . IsSuccessStatusCode . ShouldBeTrue ( ) ;
@@ -69,7 +62,6 @@ public async Task ShouldAllowDotsForFreeTextSearch()
69
62
public async Task RssFeedShouldBeRateLimited ( )
70
63
{
71
64
const int numberOfRequests = 16 ;
72
- using var client = factory . CreateClient ( ) ;
73
65
74
66
for ( var i = 0 ; i < numberOfRequests - 1 ; i ++ )
75
67
{
@@ -81,10 +73,55 @@ public async Task RssFeedShouldBeRateLimited()
81
73
lastResult . IsSuccessStatusCode . ShouldBeFalse ( ) ;
82
74
}
83
75
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
+ }
85
114
86
115
public async ValueTask DisposeAsync ( )
87
116
{
117
+ scope . Dispose ( ) ;
118
+ client . Dispose ( ) ;
88
119
await factory . DisposeAsync ( ) ;
89
120
}
121
+
122
+ private static IHtmlDocument GetHtmlDocument ( string html )
123
+ {
124
+ var parser = new HtmlParser ( ) ;
125
+ return parser . ParseDocument ( html ) ;
126
+ }
90
127
}
0 commit comments