18a21010bb
Software compositor that renders a scene into the encoder's master VideoFrame, mirroring the XAML preview minus editing chrome: backdrop -> background -> elements (UniformToFill cover-crop, round clip, mirror, opacity, border) -> branding flash. NOTE FOR USERS: this change shows NO difference in the app's UI — it is pure backend scaffolding laying the groundwork for live video capture/streaming. The preview you see is unchanged. - Services/Compositor/: SceneCompositor (Render(scene, frameFor resolver, flashFrame, CompositorOptions)), CompositorOptions (source rect + output size; 16:9 full master, vertical 607x1080 -> 1080x1920), StretchMath (pure UniformToFill + bilinear), StaticPixelCache (asset bytes -> BGRA8 frame) - Frame sources injected via Func<SceneElement, VideoFrame?> resolver, so the compositor is pure, WPF-free, and hermetic to test (D3D11 upgrade behind the same seam later) - SceneElement.TryGetBorderColor public (shared hex parse), stale MainViewModel comment fixed, pre-existing CS1998 in YouTubeAuthServiceTests cleaned up - Tests: SceneCompositorTests integration (full scene + vertical tier + flash) + StretchMath units, docs updated (72 tests passing, 0 warnings)
87 lines
3.1 KiB
C#
87 lines
3.1 KiB
C#
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 Task<HttpResponseMessage> SendAsync(
|
|
HttpRequestMessage request, CancellationToken cancellationToken)
|
|
{
|
|
var isToken = request.RequestUri!.PathAndQuery.Contains("/token");
|
|
var body = isToken ? _tokenResponse : _channelResponse;
|
|
return Task.FromResult(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));
|
|
}
|
|
|
|
[Fact]
|
|
public void ClearSession_DropsCurrentChannel()
|
|
{
|
|
var service = CreateService("""{"access_token":"a","refresh_token":"r","expires_in":3600}""",
|
|
"""{"items":[]}""");
|
|
service.SetSession(new YouTubeChannel { DisplayName = "Test Channel" });
|
|
|
|
service.ClearSession();
|
|
|
|
Assert.Null(service.CurrentChannel);
|
|
}
|
|
}
|