OAuth session persists via DPAPI; sign-in lives in the Start Stream dialog; add xUnit test project (6 passing)
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
using System.IO;
|
||||
using Xunit;
|
||||
using ytLive.Helpers;
|
||||
using ytLive.Models;
|
||||
|
||||
namespace ytLive.Tests;
|
||||
|
||||
public class TokenStoreTests
|
||||
{
|
||||
private static string TempPath() =>
|
||||
Path.Combine(Path.GetTempPath(), "ytLive_tests", $"{Guid.NewGuid():N}.auth");
|
||||
|
||||
[Fact]
|
||||
public void Save_Load_RoundtripsChannelAndTokens()
|
||||
{
|
||||
var path = TempPath();
|
||||
var channel = new YouTubeChannel
|
||||
{
|
||||
ChannelId = "UC123",
|
||||
DisplayName = "Test Channel",
|
||||
ProfileImageUrl = "http://example/thumb.jpg",
|
||||
AccessToken = "acc-token",
|
||||
RefreshToken = "refresh-token",
|
||||
TokenExpiry = DateTime.UtcNow.AddHours(1),
|
||||
};
|
||||
|
||||
TokenStore.Save(channel, path);
|
||||
|
||||
var loaded = TokenStore.Load(path);
|
||||
Assert.NotNull(loaded);
|
||||
Assert.Equal("UC123", loaded!.ChannelId);
|
||||
Assert.Equal("Test Channel", loaded.DisplayName);
|
||||
Assert.Equal("http://example/thumb.jpg", loaded.ProfileImageUrl);
|
||||
Assert.Equal("acc-token", loaded.AccessToken);
|
||||
Assert.Equal("refresh-token", loaded.RefreshToken);
|
||||
Assert.Equal(channel.TokenExpiry, loaded.TokenExpiry);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Load_MissingFile_ReturnsNull()
|
||||
{
|
||||
Assert.Null(TokenStore.Load(TempPath()));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Load_CorruptFile_ReturnsNull()
|
||||
{
|
||||
var path = TempPath();
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(path)!);
|
||||
File.WriteAllBytes(path, [1, 2, 3, 4, 5]);
|
||||
Assert.Null(TokenStore.Load(path));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Clear_RemovesSavedToken()
|
||||
{
|
||||
var path = TempPath();
|
||||
TokenStore.Save(new YouTubeChannel { DisplayName = "X" }, path);
|
||||
TokenStore.Clear(path);
|
||||
Assert.Null(TokenStore.Load(path));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using Xunit;
|
||||
using ytLive.Models;
|
||||
using ytLive.Services;
|
||||
|
||||
namespace ytLive.Tests;
|
||||
|
||||
public class YouTubeAuthServiceTests
|
||||
{
|
||||
private sealed class FakeHandler : HttpMessageHandler
|
||||
{
|
||||
private readonly string _tokenResponse;
|
||||
private readonly string _channelResponse;
|
||||
|
||||
public FakeHandler(string tokenResponse, string channelResponse)
|
||||
{
|
||||
_tokenResponse = tokenResponse;
|
||||
_channelResponse = channelResponse;
|
||||
}
|
||||
|
||||
protected override async Task<HttpResponseMessage> SendAsync(
|
||||
HttpRequestMessage request, CancellationToken cancellationToken)
|
||||
{
|
||||
var isToken = request.RequestUri!.PathAndQuery.Contains("/token");
|
||||
var body = isToken ? _tokenResponse : _channelResponse;
|
||||
return new HttpResponseMessage(HttpStatusCode.OK)
|
||||
{
|
||||
Content = new StringContent(body, Encoding.UTF8, "application/json"),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private static YouTubeAuthService CreateService(string tokenResponse, string channelResponse) =>
|
||||
new("test-id", "test-secret", new HttpClient(new FakeHandler(tokenResponse, channelResponse)));
|
||||
|
||||
[Fact]
|
||||
public async Task ExchangeCodeForToken_MockServer_ParsesChannel()
|
||||
{
|
||||
var service = CreateService(
|
||||
"""{"access_token":"acc-123","refresh_token":"ref-123","expires_in":3600}""",
|
||||
"""{"items":[{"id":"UC123","snippet":{"title":"Test Channel","thumbnails":{"default":{"url":"http://example/thumb.jpg"}}}}]}""");
|
||||
|
||||
var channel = await service.ExchangeCodeForToken("auth-code", "http://localhost:8765/oauth2/callback");
|
||||
|
||||
Assert.NotNull(channel);
|
||||
Assert.Equal("UC123", channel!.ChannelId);
|
||||
Assert.Equal("Test Channel", channel.DisplayName);
|
||||
Assert.Equal("http://example/thumb.jpg", channel.ProfileImageUrl);
|
||||
Assert.Equal("acc-123", channel.AccessToken);
|
||||
Assert.Equal("ref-123", channel.RefreshToken);
|
||||
Assert.InRange(channel.TokenExpiry, DateTime.UtcNow.AddSeconds(3590), DateTime.UtcNow.AddSeconds(3610));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RefreshToken_MockServer_UpdatesAccessTokenAndExpiry()
|
||||
{
|
||||
var service = CreateService(
|
||||
"""{"access_token":"new-acc","expires_in":3600}""",
|
||||
"""{"items":[]}""");
|
||||
service.SetSession(new YouTubeChannel
|
||||
{
|
||||
RefreshToken = "ref-123",
|
||||
TokenExpiry = DateTime.UtcNow.AddMinutes(-5),
|
||||
});
|
||||
|
||||
var ok = await service.RefreshToken();
|
||||
|
||||
Assert.True(ok);
|
||||
Assert.Equal("new-acc", service.CurrentChannel!.AccessToken);
|
||||
Assert.InRange(service.CurrentChannel.TokenExpiry,
|
||||
DateTime.UtcNow.AddSeconds(3590), DateTime.UtcNow.AddSeconds(3610));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<IsPackable>false</IsPackable>
|
||||
<EnableWindowsTargeting>true</EnableWindowsTargeting>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1"/>
|
||||
<PackageReference Include="xunit" Version="2.9.2"/>
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\ytLive.csproj"/>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
Reference in New Issue
Block a user