feat(ci): Add GitHub Actions workflows for test automation and status badges

Add comprehensive test automation setup with GitHub Actions:
- Create test.yml for running tests on main/develop branches
- Add pr-test.yml for PR validation with test results comments
- Add update-badges.yml for dynamic test status badge updates
- Configure code coverage reporting with Codecov integration

Documentation:
- Add BADGE_SETUP.md with instructions for configuring test status badges
- Add WORKFLOWS_GUIDE.md explaining CI/CD workflow setup
- Update README.md with build and test status badges

Test Framework:
- Configure test project to use .NET 9.0
- Set up test coverage reporting with coverlet
- Add integration tests with WireMock for API mocking
- Add unit tests for configuration and HTTP client components
- Document testing strategy in TestingStrategy.md

Build:
- Add Dockerfile.test for containerized testing
- Update .gitignore for test artifacts
- Configure test dependencies in VRCAuthProxy.Tests.csproj

This change enables automated testing on PRs and branches, with visual status indicators and detailed test results in PR comments.
This commit is contained in:
MiscFrizzy 2025-04-07 06:30:31 -04:00
parent 23b5a504b5
commit 319f1071bf
18 changed files with 939 additions and 12 deletions

View file

@ -0,0 +1,136 @@
using System.Net;
using System.Text;
using System.Text.Json;
using FluentAssertions;
using VRCAuthProxy.Tests.Helpers;
using VRCAuthProxy.types;
using WireMock.RequestBuilders;
using WireMock.ResponseBuilders;
using Xunit;
namespace VRCAuthProxy.Tests.Integration
{
public class VRChatAuthenticationTests : IClassFixture<TestSetup>
{
private readonly TestSetup _testSetup;
public VRChatAuthenticationTests(TestSetup testSetup)
{
_testSetup = testSetup;
}
[Fact]
public async Task Authentication_WithValidCredentials_ShouldReturnUserInfo()
{
// Arrange
var mockServer = _testSetup.MockVRChatApi;
// Mock the authentication endpoint
mockServer.Given(Request.Create()
.WithPath("/api/1/auth/user")
.UsingGet()
.WithHeader("Authorization", "*")) // Accept any authorization header
.RespondWith(Response.Create()
.WithStatusCode(200)
.WithHeader("Content-Type", "application/json")
.WithHeader("Set-Cookie", "auth=test-auth-token; path=/; secure; httponly")
.WithBodyAsJson(new User { displayName = "TestUser" }));
// Create a client with the test setup
var handler = new HttpClientHandler { UseCookies = true };
var httpClient = new HttpClient(handler)
{
BaseAddress = new Uri(mockServer.Urls.First())
};
// Act
string username = "testuser";
string password = "testpassword";
string authString = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{username}:{password}"));
var request = new HttpRequestMessage(HttpMethod.Get, "/api/1/auth/user");
request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", authString);
var response = await httpClient.SendAsync(request);
// Assert
response.StatusCode.Should().Be(HttpStatusCode.OK);
// Verify the response can be parsed
var responseContent = await response.Content.ReadAsStringAsync();
var user = JsonSerializer.Deserialize<User>(responseContent);
user.Should().NotBeNull();
user!.displayName.Should().Be("TestUser");
}
[Fact]
public async Task Authentication_WithTOTP_ShouldVerifyAndProvideUserInfo()
{
// Arrange
var mockServer = _testSetup.MockVRChatApi;
// Mock API behavior when TOTP is required
mockServer.Given(Request.Create()
.WithPath("/api/1/auth/user")
.UsingGet()
.WithHeader("Authorization", "*"))
.RespondWith(Response.Create()
.WithStatusCode(200)
.WithHeader("Content-Type", "application/json")
.WithBody(@"{""requiresTwoFactorAuth"":true,""totp"":true}"));
// Mock TOTP verification endpoint
mockServer.Given(Request.Create()
.WithPath("/api/1/auth/twofactorauth/totp/verify")
.UsingPost()
.WithBody(x => x.Contains("code")))
.RespondWith(Response.Create()
.WithStatusCode(200)
.WithHeader("Content-Type", "application/json")
.WithHeader("Set-Cookie", "auth=totp-verified-token; path=/; secure; httponly")
.WithBodyAsJson(new TotpVerifyResponse { verified = true }));
// Mock user info after successful TOTP verification
mockServer.Given(Request.Create()
.WithPath("/api/1/auth/user")
.UsingGet()
.WithCookie("auth", "totp-verified-token"))
.RespondWith(Response.Create()
.WithStatusCode(200)
.WithHeader("Content-Type", "application/json")
.WithBodyAsJson(new User { displayName = "TOTPVerifiedUser" }));
// Create a client with the test setup
var handler = new HttpClientHandler { UseCookies = true };
var httpClient = new HttpClient(handler)
{
BaseAddress = new Uri(mockServer.Urls.First())
};
// Act - First authenticate which will indicate TOTP is required
string username = "totpuser";
string password = "totppassword";
string authString = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{username}:{password}"));
var request = new HttpRequestMessage(HttpMethod.Get, "/api/1/auth/user");
request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", authString);
var response = await httpClient.SendAsync(request);
// Assert initial response
response.StatusCode.Should().Be(HttpStatusCode.OK);
var initialContent = await response.Content.ReadAsStringAsync();
initialContent.Should().Contain("requiresTwoFactorAuth");
// Now verify with TOTP
var verifyReq = new HttpRequestMessage(HttpMethod.Post, "/api/1/auth/twofactorauth/totp/verify");
verifyReq.Content = new StringContent(@"{""code"":""123456""}", Encoding.UTF8, "application/json");
var verifyResp = await httpClient.SendAsync(verifyReq);
// Assert TOTP verification
verifyResp.StatusCode.Should().Be(HttpStatusCode.OK);
var verifyContent = await verifyResp.Content.ReadAsStringAsync();
var verifyResult = JsonSerializer.Deserialize<TotpVerifyResponse>(verifyContent);
verifyResult.Should().NotBeNull();
verifyResult!.verified.Should().BeTrue();
}
}
}