Skip to content

Commit cfa32bc

Browse files
CopilotPhantomDave
andcommitted
Add unit tests and initial integration test structure
Co-authored-by: PhantomDave <34485699+PhantomDave@users.noreply.github.com>
1 parent ce1255d commit cfa32bc

File tree

8 files changed

+799
-0
lines changed

8 files changed

+799
-0
lines changed

Directory.Packages.props

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,15 @@
2525
<!-- File Processing -->
2626
<PackageVersion Include="CsvHelper" Version="33.1.0" />
2727
<PackageVersion Include="EPPlus" Version="8.2.1" />
28+
29+
<!-- Testing -->
30+
<PackageVersion Include="xunit" Version="2.9.2" />
31+
<PackageVersion Include="xunit.runner.visualstudio" Version="3.0.0" />
32+
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
33+
<PackageVersion Include="coverlet.collector" Version="6.0.2" />
34+
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="10.0.0" />
35+
<PackageVersion Include="Moq" Version="4.20.72" />
36+
<PackageVersion Include="FluentAssertions" Version="7.0.0" />
37+
<PackageVersion Include="Microsoft.EntityFrameworkCore.InMemory" Version="10.0.0" />
2838
</ItemGroup>
2939
</Project>
Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
using System.Net.Http.Json;
2+
using System.Text;
3+
using System.Text.Json;
4+
using FluentAssertions;
5+
using PhantomDave.BankTracking.IntegrationTests.Helpers;
6+
7+
namespace PhantomDave.BankTracking.IntegrationTests.GraphQL;
8+
9+
public class AccountIntegrationTests : IClassFixture<GraphQLTestFactory>
10+
{
11+
private readonly HttpClient _client;
12+
private readonly GraphQLTestFactory _factory;
13+
14+
public AccountIntegrationTests(GraphQLTestFactory factory)
15+
{
16+
_factory = factory;
17+
_client = factory.CreateClient();
18+
}
19+
20+
[Fact]
21+
public async Task CreateAccount_WithValidData_ReturnsNewAccount()
22+
{
23+
// Arrange
24+
var query = @"
25+
mutation {
26+
createAccount(email: ""test@example.com"", password: ""Password123!"") {
27+
id
28+
email
29+
createdAt
30+
}
31+
}";
32+
33+
var request = new
34+
{
35+
query = query
36+
};
37+
38+
// Act
39+
var response = await _client.PostAsJsonAsync("/graphql", request);
40+
41+
// Assert
42+
response.EnsureSuccessStatusCode();
43+
var content = await response.Content.ReadAsStringAsync();
44+
content.Should().Contain("test@example.com");
45+
content.Should().Contain("\"id\"");
46+
}
47+
48+
[Fact]
49+
public async Task CreateAccount_WithDuplicateEmail_ReturnsError()
50+
{
51+
// Arrange
52+
var createQuery = @"
53+
mutation {
54+
createAccount(email: ""duplicate@example.com"", password: ""Password123!"") {
55+
id
56+
email
57+
}
58+
}";
59+
60+
await _client.PostAsJsonAsync("/graphql", new { query = createQuery });
61+
62+
// Act
63+
var response = await _client.PostAsJsonAsync("/graphql", new { query = createQuery });
64+
65+
// Assert
66+
var content = await response.Content.ReadAsStringAsync();
67+
content.Should().Contain("errors");
68+
}
69+
70+
[Fact]
71+
public async Task Login_WithValidCredentials_ReturnsToken()
72+
{
73+
// Arrange
74+
var email = "login@example.com";
75+
var password = "Password123!";
76+
77+
var createQuery = $@"
78+
mutation {{
79+
createAccount(email: ""{email}"", password: ""{password}"") {{
80+
id
81+
}}
82+
}}";
83+
84+
await _client.PostAsJsonAsync("/graphql", new { query = createQuery });
85+
86+
var loginQuery = $@"
87+
mutation {{
88+
loginAccount(email: ""{email}"", password: ""{password}"") {{
89+
token
90+
account {{
91+
id
92+
email
93+
}}
94+
}}
95+
}}";
96+
97+
// Act
98+
var response = await _client.PostAsJsonAsync("/graphql", new { query = loginQuery });
99+
100+
// Assert
101+
response.EnsureSuccessStatusCode();
102+
var content = await response.Content.ReadAsStringAsync();
103+
content.Should().Contain("token");
104+
content.Should().Contain(email);
105+
}
106+
107+
[Fact]
108+
public async Task Login_WithInvalidPassword_ReturnsError()
109+
{
110+
// Arrange
111+
var email = "wrongpass@example.com";
112+
var correctPassword = "CorrectPassword123!";
113+
var wrongPassword = "WrongPassword123!";
114+
115+
var createQuery = $@"
116+
mutation {{
117+
createAccount(email: ""{email}"", password: ""{correctPassword}"") {{
118+
id
119+
}}
120+
}}";
121+
122+
await _client.PostAsJsonAsync("/graphql", new { query = createQuery });
123+
124+
var loginQuery = $@"
125+
mutation {{
126+
loginAccount(email: ""{email}"", password: ""{wrongPassword}"") {{
127+
token
128+
}}
129+
}}";
130+
131+
// Act
132+
var response = await _client.PostAsJsonAsync("/graphql", new { query = loginQuery });
133+
134+
// Assert
135+
var content = await response.Content.ReadAsStringAsync();
136+
content.Should().Contain("errors");
137+
}
138+
139+
[Fact]
140+
public async Task VerifyToken_WithValidToken_ReturnsAccountInfo()
141+
{
142+
// Arrange
143+
var email = "verify@example.com";
144+
var password = "Password123!";
145+
146+
var createQuery = $@"
147+
mutation {{
148+
createAccount(email: ""{email}"", password: ""{password}"") {{
149+
id
150+
}}
151+
}}";
152+
153+
await _client.PostAsJsonAsync("/graphql", new { query = createQuery });
154+
155+
var loginQuery = $@"
156+
mutation {{
157+
loginAccount(email: ""{email}"", password: ""{password}"") {{
158+
token
159+
}}
160+
}}";
161+
162+
var loginResponse = await _client.PostAsJsonAsync("/graphql", new { query = loginQuery });
163+
var loginContent = await loginResponse.Content.ReadAsStringAsync();
164+
165+
using var doc = JsonDocument.Parse(loginContent);
166+
var token = doc.RootElement.GetProperty("data").GetProperty("loginAccount").GetProperty("token").GetString();
167+
168+
var verifyQuery = $@"
169+
mutation {{
170+
verifyToken(token: ""{token}"") {{
171+
id
172+
email
173+
}}
174+
}}";
175+
176+
// Act
177+
var response = await _client.PostAsJsonAsync("/graphql", new { query = verifyQuery });
178+
179+
// Assert
180+
response.EnsureSuccessStatusCode();
181+
var content = await response.Content.ReadAsStringAsync();
182+
content.Should().Contain(email);
183+
}
184+
185+
[Fact]
186+
public async Task GetAccount_WithAuthentication_ReturnsAccountData()
187+
{
188+
// Arrange
189+
var email = "getaccount@example.com";
190+
var password = "Password123!";
191+
192+
var createQuery = $@"
193+
mutation {{
194+
createAccount(email: ""{email}"", password: ""{password}"") {{
195+
id
196+
}}
197+
}}";
198+
199+
await _client.PostAsJsonAsync("/graphql", new { query = createQuery });
200+
201+
var loginQuery = $@"
202+
mutation {{
203+
loginAccount(email: ""{email}"", password: ""{password}"") {{
204+
token
205+
account {{
206+
id
207+
}}
208+
}}
209+
}}";
210+
211+
var loginResponse = await _client.PostAsJsonAsync("/graphql", new { query = loginQuery });
212+
var loginContent = await loginResponse.Content.ReadAsStringAsync();
213+
214+
using var doc = JsonDocument.Parse(loginContent);
215+
var token = doc.RootElement.GetProperty("data").GetProperty("loginAccount").GetProperty("token").GetString();
216+
var accountId = doc.RootElement.GetProperty("data").GetProperty("loginAccount").GetProperty("account").GetProperty("id").GetInt32();
217+
218+
_client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
219+
220+
var getAccountQuery = $@"
221+
query {{
222+
getAccount(id: {accountId}) {{
223+
id
224+
email
225+
currentBalance
226+
}}
227+
}}";
228+
229+
// Act
230+
var response = await _client.PostAsJsonAsync("/graphql", new { query = getAccountQuery });
231+
232+
// Assert
233+
response.EnsureSuccessStatusCode();
234+
var content = await response.Content.ReadAsStringAsync();
235+
content.Should().Contain(email);
236+
content.Should().Contain("currentBalance");
237+
}
238+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
using Microsoft.AspNetCore.Hosting;
2+
using Microsoft.AspNetCore.Mvc.Testing;
3+
using Microsoft.AspNetCore.TestHost;
4+
using Microsoft.EntityFrameworkCore;
5+
using Microsoft.Extensions.Configuration;
6+
using Microsoft.Extensions.DependencyInjection;
7+
using Microsoft.Extensions.DependencyInjection.Extensions;
8+
using PhantomDave.BankTracking.Data.Context;
9+
10+
namespace PhantomDave.BankTracking.IntegrationTests.Helpers;
11+
12+
public class GraphQLTestFactory : WebApplicationFactory<PhantomDave.BankTracking.Api.Program>
13+
{
14+
protected override void ConfigureWebHost(IWebHostBuilder builder)
15+
{
16+
builder.ConfigureAppConfiguration((context, config) =>
17+
{
18+
config.AddInMemoryCollection(new Dictionary<string, string?>
19+
{
20+
["ConnectionStrings:DefaultConnection"] = "Host=localhost;Database=banktrackertest;Username=test;Password=test",
21+
["Jwt:Secret"] = "ThisIsASecretKeyForTestingPurposesOnly123456789",
22+
["Jwt:Issuer"] = "BankTrackerTestIssuer",
23+
["Jwt:Audience"] = "BankTrackerTestAudience",
24+
["Jwt:ExpiryMinutes"] = "60"
25+
});
26+
});
27+
28+
builder.ConfigureTestServices(services =>
29+
{
30+
var descriptor = services.SingleOrDefault(
31+
d => d.ServiceType == typeof(DbContextOptions<BankTrackerDbContext>));
32+
33+
if (descriptor != null)
34+
{
35+
services.Remove(descriptor);
36+
}
37+
38+
services.AddDbContext<BankTrackerDbContext>(options =>
39+
{
40+
options.UseInMemoryDatabase("InMemoryTestDb");
41+
});
42+
});
43+
44+
builder.UseEnvironment("Testing");
45+
}
46+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net10.0</TargetFramework>
5+
<ImplicitUsings>enable</ImplicitUsings>
6+
<Nullable>enable</Nullable>
7+
<IsPackable>false</IsPackable>
8+
</PropertyGroup>
9+
10+
<ItemGroup>
11+
<PackageReference Include="coverlet.collector" />
12+
<PackageReference Include="Microsoft.NET.Test.Sdk" />
13+
<PackageReference Include="xunit" />
14+
<PackageReference Include="xunit.runner.visualstudio" />
15+
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" />
16+
<PackageReference Include="FluentAssertions" />
17+
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" />
18+
</ItemGroup>
19+
20+
<ItemGroup>
21+
<Using Include="Xunit" />
22+
</ItemGroup>
23+
24+
<ItemGroup>
25+
<ProjectReference Include="..\PhantomDave.BankTracking.Api\PhantomDave.BankTracking.Api.csproj" />
26+
</ItemGroup>
27+
28+
</Project>
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net10.0</TargetFramework>
5+
<ImplicitUsings>enable</ImplicitUsings>
6+
<Nullable>enable</Nullable>
7+
<IsPackable>false</IsPackable>
8+
</PropertyGroup>
9+
10+
<ItemGroup>
11+
<PackageReference Include="coverlet.collector" />
12+
<PackageReference Include="Microsoft.NET.Test.Sdk" />
13+
<PackageReference Include="xunit" />
14+
<PackageReference Include="xunit.runner.visualstudio" />
15+
<PackageReference Include="Moq" />
16+
<PackageReference Include="FluentAssertions" />
17+
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" />
18+
</ItemGroup>
19+
20+
<ItemGroup>
21+
<Using Include="Xunit" />
22+
</ItemGroup>
23+
24+
<ItemGroup>
25+
<ProjectReference Include="..\PhantomDave.BankTracking.Api\PhantomDave.BankTracking.Api.csproj" />
26+
<ProjectReference Include="..\PhantomDave.BankTracking.Data\PhantomDave.BankTracking.Data.csproj" />
27+
<ProjectReference Include="..\PhantomDave.BankTracking.Library\PhantomDave.BankTracking.Library.csproj" />
28+
</ItemGroup>
29+
30+
</Project>

0 commit comments

Comments
 (0)