Skip to content

Commit d3ca7e9

Browse files
committed
Adding api tests
1 parent 779e6a7 commit d3ca7e9

File tree

8 files changed

+232
-7
lines changed

8 files changed

+232
-7
lines changed

ApiTests.sln

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,72 @@
1+
12
Microsoft Visual Studio Solution File, Format Version 12.00
23
# Visual Studio Version 17
34
VisualStudioVersion = 17.5.2.0
45
MinimumVisualStudioVersion = 10.0.40219.1
56
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ApiTests", "ApiTests.csproj", "{8517DADC-CDA5-0FC7-2FB4-251BD5AE90E0}"
67
EndProject
8+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{827E0CD3-B72D-47B6-A68D-7590B98EB39B}"
9+
EndProject
10+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ApiClient", "src\ApiClient\ApiClient.csproj", "{407EF9DC-EBE3-4E42-BBE2-8D1F2E717831}"
11+
EndProject
12+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{0AB3BF05-4346-4AA6-1389-037BE0695223}"
13+
EndProject
14+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ApiClient.Tests", "tests\ApiClient.Tests\ApiClient.Tests.csproj", "{CF6D01D6-608B-470F-998F-C8562A290D33}"
15+
EndProject
716
Global
817
GlobalSection(SolutionConfigurationPlatforms) = preSolution
918
Debug|Any CPU = Debug|Any CPU
19+
Debug|x64 = Debug|x64
20+
Debug|x86 = Debug|x86
1021
Release|Any CPU = Release|Any CPU
22+
Release|x64 = Release|x64
23+
Release|x86 = Release|x86
1124
EndGlobalSection
1225
GlobalSection(ProjectConfigurationPlatforms) = postSolution
1326
{8517DADC-CDA5-0FC7-2FB4-251BD5AE90E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
1427
{8517DADC-CDA5-0FC7-2FB4-251BD5AE90E0}.Debug|Any CPU.Build.0 = Debug|Any CPU
28+
{8517DADC-CDA5-0FC7-2FB4-251BD5AE90E0}.Debug|x64.ActiveCfg = Debug|Any CPU
29+
{8517DADC-CDA5-0FC7-2FB4-251BD5AE90E0}.Debug|x64.Build.0 = Debug|Any CPU
30+
{8517DADC-CDA5-0FC7-2FB4-251BD5AE90E0}.Debug|x86.ActiveCfg = Debug|Any CPU
31+
{8517DADC-CDA5-0FC7-2FB4-251BD5AE90E0}.Debug|x86.Build.0 = Debug|Any CPU
1532
{8517DADC-CDA5-0FC7-2FB4-251BD5AE90E0}.Release|Any CPU.ActiveCfg = Release|Any CPU
1633
{8517DADC-CDA5-0FC7-2FB4-251BD5AE90E0}.Release|Any CPU.Build.0 = Release|Any CPU
34+
{8517DADC-CDA5-0FC7-2FB4-251BD5AE90E0}.Release|x64.ActiveCfg = Release|Any CPU
35+
{8517DADC-CDA5-0FC7-2FB4-251BD5AE90E0}.Release|x64.Build.0 = Release|Any CPU
36+
{8517DADC-CDA5-0FC7-2FB4-251BD5AE90E0}.Release|x86.ActiveCfg = Release|Any CPU
37+
{8517DADC-CDA5-0FC7-2FB4-251BD5AE90E0}.Release|x86.Build.0 = Release|Any CPU
38+
{407EF9DC-EBE3-4E42-BBE2-8D1F2E717831}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
39+
{407EF9DC-EBE3-4E42-BBE2-8D1F2E717831}.Debug|Any CPU.Build.0 = Debug|Any CPU
40+
{407EF9DC-EBE3-4E42-BBE2-8D1F2E717831}.Debug|x64.ActiveCfg = Debug|Any CPU
41+
{407EF9DC-EBE3-4E42-BBE2-8D1F2E717831}.Debug|x64.Build.0 = Debug|Any CPU
42+
{407EF9DC-EBE3-4E42-BBE2-8D1F2E717831}.Debug|x86.ActiveCfg = Debug|Any CPU
43+
{407EF9DC-EBE3-4E42-BBE2-8D1F2E717831}.Debug|x86.Build.0 = Debug|Any CPU
44+
{407EF9DC-EBE3-4E42-BBE2-8D1F2E717831}.Release|Any CPU.ActiveCfg = Release|Any CPU
45+
{407EF9DC-EBE3-4E42-BBE2-8D1F2E717831}.Release|Any CPU.Build.0 = Release|Any CPU
46+
{407EF9DC-EBE3-4E42-BBE2-8D1F2E717831}.Release|x64.ActiveCfg = Release|Any CPU
47+
{407EF9DC-EBE3-4E42-BBE2-8D1F2E717831}.Release|x64.Build.0 = Release|Any CPU
48+
{407EF9DC-EBE3-4E42-BBE2-8D1F2E717831}.Release|x86.ActiveCfg = Release|Any CPU
49+
{407EF9DC-EBE3-4E42-BBE2-8D1F2E717831}.Release|x86.Build.0 = Release|Any CPU
50+
{CF6D01D6-608B-470F-998F-C8562A290D33}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
51+
{CF6D01D6-608B-470F-998F-C8562A290D33}.Debug|Any CPU.Build.0 = Debug|Any CPU
52+
{CF6D01D6-608B-470F-998F-C8562A290D33}.Debug|x64.ActiveCfg = Debug|Any CPU
53+
{CF6D01D6-608B-470F-998F-C8562A290D33}.Debug|x64.Build.0 = Debug|Any CPU
54+
{CF6D01D6-608B-470F-998F-C8562A290D33}.Debug|x86.ActiveCfg = Debug|Any CPU
55+
{CF6D01D6-608B-470F-998F-C8562A290D33}.Debug|x86.Build.0 = Debug|Any CPU
56+
{CF6D01D6-608B-470F-998F-C8562A290D33}.Release|Any CPU.ActiveCfg = Release|Any CPU
57+
{CF6D01D6-608B-470F-998F-C8562A290D33}.Release|Any CPU.Build.0 = Release|Any CPU
58+
{CF6D01D6-608B-470F-998F-C8562A290D33}.Release|x64.ActiveCfg = Release|Any CPU
59+
{CF6D01D6-608B-470F-998F-C8562A290D33}.Release|x64.Build.0 = Release|Any CPU
60+
{CF6D01D6-608B-470F-998F-C8562A290D33}.Release|x86.ActiveCfg = Release|Any CPU
61+
{CF6D01D6-608B-470F-998F-C8562A290D33}.Release|x86.Build.0 = Release|Any CPU
1762
EndGlobalSection
1863
GlobalSection(SolutionProperties) = preSolution
1964
HideSolutionNode = FALSE
2065
EndGlobalSection
66+
GlobalSection(NestedProjects) = preSolution
67+
{407EF9DC-EBE3-4E42-BBE2-8D1F2E717831} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
68+
{CF6D01D6-608B-470F-998F-C8562A290D33} = {0AB3BF05-4346-4AA6-1389-037BE0695223}
69+
EndGlobalSection
2170
GlobalSection(ExtensibilityGlobals) = postSolution
2271
SolutionGuid = {B8DBD63D-8BBB-42C3-839A-67370477DC92}
2372
EndGlobalSection

src/ApiClient/ApiClient.cs

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,32 @@
1-
using System.Net.Http;
2-
using System.Threading;
3-
using System.Threading.Tasks;
1+
using System.Net.Http.Headers;
2+
using System.Net.Http.Json;
43

5-
public sealed class ApiClient
4+
namespace ApiClientLib;
5+
6+
public sealed class HttpApiClient(HttpClient http)
67
{
7-
private readonly HttpClient _http;
8-
public ApiClient(HttpClient http) => _http = http;
8+
private readonly HttpClient _http = http ?? throw new ArgumentNullException(nameof(http));
99

1010
public async Task<bool> HealthAsync(CancellationToken ct = default)
1111
{
1212
var res = await _http.GetAsync("/health", ct);
1313
return res.IsSuccessStatusCode;
1414
}
15-
}
15+
16+
public async Task<string> LoginAsync(string path, string user, string pass, CancellationToken ct = default)
17+
{
18+
var payload = new { username = user, password = pass };
19+
var res = await _http.PostAsJsonAsync(path, payload, ct);
20+
res.EnsureSuccessStatusCode();
21+
var token = await res.Content.ReadAsStringAsync(ct);
22+
if (string.IsNullOrWhiteSpace(token)) throw new InvalidOperationException("Empty token");
23+
return token;
24+
}
25+
26+
public async Task<HttpResponseMessage> GetWithApiKeyAsync(string path, string headerName, string key, CancellationToken ct = default)
27+
{
28+
using var req = new HttpRequestMessage(HttpMethod.Get, path);
29+
req.Headers.TryAddWithoutValidation(headerName, key);
30+
return await _http.SendAsync(req, ct);
31+
}
32+
}

src/ApiClient/ApiClient.csproj

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net8.0</TargetFramework>
5+
<ImplicitUsings>enable</ImplicitUsings>
6+
<Nullable>enable</Nullable>
7+
</PropertyGroup>
8+
9+
</Project>

src/ApiClient/Class1.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
namespace ApiClient;
2+
3+
public class Class1
4+
{
5+
6+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net8.0</TargetFramework>
5+
<LangVersion>latest</LangVersion>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
<Nullable>enable</Nullable>
8+
<IsPackable>false</IsPackable>
9+
</PropertyGroup>
10+
11+
<ItemGroup>
12+
<PackageReference Include="coverlet.collector" Version="6.0.2" />
13+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
14+
<PackageReference Include="NUnit" Version="4.2.2" />
15+
<PackageReference Include="NUnit.Analyzers" Version="4.4.0" />
16+
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0" />
17+
</ItemGroup>
18+
19+
<ItemGroup>
20+
<Using Include="NUnit.Framework" />
21+
</ItemGroup>
22+
23+
<ItemGroup>
24+
<ProjectReference Include="..\..\src\ApiClient\ApiClient.csproj" />
25+
</ItemGroup>
26+
27+
</Project>
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
using NUnit.Framework;
2+
using WireMock.Server;
3+
using WireMock.RequestBuilders;
4+
using WireMock.ResponseBuilders;
5+
using ApiClientLib;
6+
7+
namespace ApiClient.Tests;
8+
9+
[TestFixture]
10+
public class ApiKeyHeaderTests
11+
{
12+
private WireMockServer _server = default!;
13+
private HttpApiClient _client = default!;
14+
private const string Header = "X-API-Key";
15+
private const string Key = "k-123";
16+
17+
[SetUp]
18+
public void Setup()
19+
{
20+
_server = WireMockServer.Start();
21+
var http = new HttpClient { BaseAddress = new Uri(_server.Urls[0]) };
22+
_client = new HttpApiClient(http);
23+
}
24+
25+
[TearDown]
26+
public void Teardown() => _server.Stop();
27+
28+
[Test, Description("Sends API key header")]
29+
public async Task Sends_Api_Key_Header()
30+
{
31+
_server.Given(Request.Create().WithPath("/secure").UsingGet()
32+
.WithHeader(Header, Key))
33+
.RespondWith(Response.Create().WithStatusCode(200));
34+
35+
var res = await _client.GetWithApiKeyAsync("/secure", Header, Key);
36+
Assert.That((int)res.StatusCode, Is.EqualTo(200));
37+
}
38+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
using NUnit.Framework;
2+
using WireMock.Server;
3+
using WireMock.RequestBuilders;
4+
using WireMock.ResponseBuilders;
5+
using ApiClientLib;
6+
7+
namespace ApiClient.Tests;
8+
9+
[TestFixture]
10+
public class HealthTests
11+
{
12+
private WireMockServer _server = default!;
13+
private HttpApiClient _client = default!;
14+
15+
[SetUp]
16+
public void Setup()
17+
{
18+
_server = WireMockServer.Start();
19+
var http = new HttpClient { BaseAddress = new Uri(_server.Urls[0]) };
20+
_client = new HttpApiClient(http);
21+
}
22+
23+
[TearDown]
24+
public void Teardown() => _server.Stop();
25+
26+
[Test, Description("Health returns true on 200")]
27+
public async Task Health_Returns_True_On_200()
28+
{
29+
_server.Given(Request.Create().WithPath("/health").UsingGet())
30+
.RespondWith(Response.Create().WithStatusCode(200));
31+
32+
Assert.That(await _client.HealthAsync());
33+
}
34+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
using NUnit.Framework;
2+
using WireMock.Server;
3+
using WireMock.RequestBuilders;
4+
using WireMock.ResponseBuilders;
5+
using ApiClientLib;
6+
7+
namespace ApiClient.Tests;
8+
9+
[TestFixture]
10+
public class LoginTests
11+
{
12+
private WireMockServer _server = default!;
13+
private HttpApiClient _client = default!;
14+
15+
[SetUp]
16+
public void Setup()
17+
{
18+
_server = WireMockServer.Start();
19+
var http = new HttpClient { BaseAddress = new Uri(_server.Urls[0]) };
20+
_client = new HttpApiClient(http);
21+
}
22+
23+
[TearDown]
24+
public void Teardown() => _server.Stop();
25+
26+
[Test, Description("Login returns token string on 200")]
27+
public async Task Login_Returns_Token()
28+
{
29+
_server.Given(Request.Create().WithPath("/auth/login").UsingPost())
30+
.RespondWith(Response.Create().WithStatusCode(200).WithBody("fake-token-123"));
31+
32+
var token = await _client.LoginAsync("/auth/login", "user", "pass");
33+
Assert.That(token, Is.EqualTo("fake-token-123"));
34+
}
35+
36+
[Test, Description("Login throws on 401")]
37+
public void Login_Throws_On_401()
38+
{
39+
_server.Given(Request.Create().WithPath("/auth/login").UsingPost())
40+
.RespondWith(Response.Create().WithStatusCode(401));
41+
42+
Assert.ThrowsAsync<HttpRequestException>(async () =>
43+
await _client.LoginAsync("/auth/login", "bad", "creds"));
44+
}
45+
}

0 commit comments

Comments
 (0)