using System; using System.Linq; using System.Threading.Tasks; using AutoFixture; using AutoFixture.AutoMoq; using Emby.Server.Implementations.QuickConnect; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Authentication; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.QuickConnect; using MediaBrowser.Model.Configuration; using Moq; using Xunit; namespace Jellyfin.Server.Implementations.Tests.QuickConnect { public class QuickConnectManagerTests { private static readonly AuthorizationInfo _quickConnectAuthInfo = new AuthorizationInfo { Device = "Device", DeviceId = "DeviceId", Client = "Client", Version = "1.0.0" }; private readonly Fixture _fixture; private readonly ServerConfiguration _config; private readonly QuickConnectManager _quickConnectManager; public QuickConnectManagerTests() { _config = new ServerConfiguration(); var configManager = new Mock(); configManager.Setup(x => x.Configuration).Returns(_config); _fixture = new Fixture(); _fixture.Customize(new AutoMoqCustomization { ConfigureMembers = true }).Inject(configManager.Object); _fixture.Inject(new InMemoryQuickConnectStore()); // User object contains circular references. _fixture.Behaviors.OfType().ToList() .ForEach(b => _fixture.Behaviors.Remove(b)); _fixture.Behaviors.Add(new OmitOnRecursionBehavior()); _quickConnectManager = _fixture.Create(); } [Fact] public void IsEnabled_QuickConnectUnavailable_False() { _config.QuickConnectAvailable = false; Assert.False(_quickConnectManager.IsEnabled); } [Theory] [InlineData("", "DeviceId", "Client", "1.0.0")] [InlineData("Device", "", "Client", "1.0.0")] [InlineData("Device", "DeviceId", "", "1.0.0")] [InlineData("Device", "DeviceId", "Client", "")] public async Task TryConnect_InvalidAuthorizationInfo_ThrowsArgumentException(string device, string deviceId, string client, string version) => await Assert.ThrowsAsync(() => _quickConnectManager.TryConnect( new AuthorizationInfo { Device = device, DeviceId = deviceId, Client = client, Version = version })); [Fact] public async Task TryConnect_QuickConnectUnavailable_ThrowsAuthenticationException() { _config.QuickConnectAvailable = false; await Assert.ThrowsAsync(() => _quickConnectManager.TryConnect(_quickConnectAuthInfo)); } [Fact] public async Task CheckRequestStatus_QuickConnectUnavailable_ThrowsAuthenticationException() { _config.QuickConnectAvailable = false; await Assert.ThrowsAsync(() => _quickConnectManager.CheckRequestStatus(string.Empty)); } [Fact] public async Task AuthorizeRequest_QuickConnectUnavailable_ThrowsAuthenticationException() { _config.QuickConnectAvailable = false; await Assert.ThrowsAsync(() => _quickConnectManager.AuthorizeRequest(Guid.Empty, string.Empty)); } [Fact] public async Task GetAuthorizedRequest_QuickConnectUnavailable_ThrowsAuthenticationException() { _config.QuickConnectAvailable = false; await Assert.ThrowsAsync(() => _quickConnectManager.GetAuthorizedRequest(string.Empty)); } [Fact] public void IsEnabled_QuickConnectAvailable_True() { _config.QuickConnectAvailable = true; Assert.True(_quickConnectManager.IsEnabled); } [Fact] public async Task CheckRequestStatus_QuickConnectAvailable_Success() { _config.QuickConnectAvailable = true; var res1 = await _quickConnectManager.TryConnect(_quickConnectAuthInfo); var res2 = await _quickConnectManager.CheckRequestStatus(res1.Secret); Assert.Equal(res1.Secret, res2.Secret); Assert.Equal(res1.Code, res2.Code); } [Fact] public async Task CheckRequestStatus_UnknownSecret_ThrowsResourceNotFoundException() { _config.QuickConnectAvailable = true; await Assert.ThrowsAsync(() => _quickConnectManager.CheckRequestStatus("Unknown secret")); } [Fact] public async Task GetAuthorizedRequest_UnknownSecret_ThrowsResourceNotFoundException() { _config.QuickConnectAvailable = true; await Assert.ThrowsAsync(() => _quickConnectManager.GetAuthorizedRequest("Unknown secret")); } [Fact] public async Task AuthorizeRequest_QuickConnectAvailable_Success() { _config.QuickConnectAvailable = true; var res = await _quickConnectManager.TryConnect(_quickConnectAuthInfo); Assert.True(await _quickConnectManager.AuthorizeRequest(Guid.Empty, res.Code)); } [Fact] public async Task AuthorizeRequest_RacedOnOneCode_SucceedsOnce() { _config.QuickConnectAvailable = true; var res = await _quickConnectManager.TryConnect(_quickConnectAuthInfo); var outcomes = await Task.WhenAll( Task.Run(() => AuthorizeAsync(res.Code)), Task.Run(() => AuthorizeAsync(res.Code))); Assert.Single(outcomes, authorized => authorized); } [Fact] public async Task GetAuthorizedRequest_SecondExchange_ReturnsTheSameResult() { _config.QuickConnectAvailable = true; var res = await _quickConnectManager.TryConnect(_quickConnectAuthInfo); await _quickConnectManager.AuthorizeRequest(Guid.Empty, res.Code); var first = await _quickConnectManager.GetAuthorizedRequest(res.Secret); var second = await _quickConnectManager.GetAuthorizedRequest(res.Secret); Assert.Same(first, second); } [Fact] public async Task AuthorizeRequest_OfAnAuthorizedRequest_ThrowsConflictException() { _config.QuickConnectAvailable = true; var res = await _quickConnectManager.TryConnect(_quickConnectAuthInfo); await _quickConnectManager.AuthorizeRequest(Guid.Empty, res.Code); await Assert.ThrowsAsync(() => _quickConnectManager.AuthorizeRequest(Guid.Empty, res.Code)); } private async Task AuthorizeAsync(string code) { try { return await _quickConnectManager.AuthorizeRequest(Guid.Empty, code).ConfigureAwait(false); } catch (ConflictException) { return false; } } } }