From 2533e96fd34070b31bc52fce6f1dc04c5aa3b4cd Mon Sep 17 00:00:00 2001 From: erri120 Date: Wed, 16 Oct 2024 12:48:07 +0200 Subject: [PATCH] Convert nexus mods oauth login into a job --- .../IJobDefinition.cs | 18 ++-- .../Constants.cs | 12 --- .../IOAuthJob.cs | 11 +++ .../NexusMods.Abstractions.NexusWebApi.csproj | 4 + .../Auth/OAuth.cs | 61 +++++-------- .../OAuthJob.cs | 86 +++++++++++++++++++ .../Login/INexusLoginOverlayViewModel.cs | 8 +- .../NexusLoginOverlayDesignerViewModel.cs | 6 +- .../Login/NexusLoginOverlayService.cs | 74 ++++++++-------- .../Login/NexusLoginOverlayView.axaml.cs | 7 +- .../Login/NexusLoginOverlayViewModel.cs | 34 ++++---- src/NexusMods.Jobs/JobContext.cs | 2 +- .../OAuthTests.cs | 12 +-- .../Overlays/NexusLoginOverlayTests.cs | 36 -------- 14 files changed, 206 insertions(+), 165 deletions(-) delete mode 100644 src/Abstractions/NexusMods.Abstractions.NexusWebApi/Constants.cs create mode 100644 src/Abstractions/NexusMods.Abstractions.NexusWebApi/IOAuthJob.cs create mode 100644 src/Networking/NexusMods.Networking.NexusWebApi/OAuthJob.cs delete mode 100644 tests/NexusMods.UI.Tests/Overlays/NexusLoginOverlayTests.cs diff --git a/src/Abstractions/NexusMods.Abstractions.Jobs/IJobDefinition.cs b/src/Abstractions/NexusMods.Abstractions.Jobs/IJobDefinition.cs index 183d1c960c..d3424d810b 100644 --- a/src/Abstractions/NexusMods.Abstractions.Jobs/IJobDefinition.cs +++ b/src/Abstractions/NexusMods.Abstractions.Jobs/IJobDefinition.cs @@ -1,24 +1,24 @@ +using JetBrains.Annotations; + namespace NexusMods.Abstractions.Jobs; -public interface IJobDefinition -{ - -} +[PublicAPI] +public interface IJobDefinition; /// /// A typed job definition that returns a result /// +[PublicAPI] public interface IJobDefinition : IJobDefinition where TResultType : notnull; - /// /// A job definition that can be started with instance method /// -/// -/// -public interface IJobDefinitionWithStart : IJobDefinition - where TParent : IJobDefinition where TResultType : notnull +[PublicAPI] +public interface IJobDefinitionWithStart : IJobDefinition + where TParent : IJobDefinition + where TResultType : notnull { /// /// Starts the job diff --git a/src/Abstractions/NexusMods.Abstractions.NexusWebApi/Constants.cs b/src/Abstractions/NexusMods.Abstractions.NexusWebApi/Constants.cs deleted file mode 100644 index c246fc7425..0000000000 --- a/src/Abstractions/NexusMods.Abstractions.NexusWebApi/Constants.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace NexusMods.Abstractions.NexusWebApi; - -/// -/// Constant variables used throughout the library and friends. -/// -public static class Constants -{ - /// - /// The constant to use for the 'job/activity group' related to the OAuth2 login process. - /// - public static string OAuthActivityGroupName => "NexusWebApi/OAuth"; -} diff --git a/src/Abstractions/NexusMods.Abstractions.NexusWebApi/IOAuthJob.cs b/src/Abstractions/NexusMods.Abstractions.NexusWebApi/IOAuthJob.cs new file mode 100644 index 0000000000..0bcff4979a --- /dev/null +++ b/src/Abstractions/NexusMods.Abstractions.NexusWebApi/IOAuthJob.cs @@ -0,0 +1,11 @@ +using NexusMods.Abstractions.Jobs; + +namespace NexusMods.Abstractions.NexusWebApi; + +/// +/// Represents a job for logging in using OAuth. +/// +public interface IOAuthJob : IJobDefinition, IDisposable +{ + R3.Subject LoginUriSubject { get; } +} diff --git a/src/Abstractions/NexusMods.Abstractions.NexusWebApi/NexusMods.Abstractions.NexusWebApi.csproj b/src/Abstractions/NexusMods.Abstractions.NexusWebApi/NexusMods.Abstractions.NexusWebApi.csproj index 84556d1907..cde6b34cf2 100644 --- a/src/Abstractions/NexusMods.Abstractions.NexusWebApi/NexusMods.Abstractions.NexusWebApi.csproj +++ b/src/Abstractions/NexusMods.Abstractions.NexusWebApi/NexusMods.Abstractions.NexusWebApi.csproj @@ -16,4 +16,8 @@ + + + + diff --git a/src/Networking/NexusMods.Networking.NexusWebApi/Auth/OAuth.cs b/src/Networking/NexusMods.Networking.NexusWebApi/Auth/OAuth.cs index b1ff9cbc0e..59f89634e8 100644 --- a/src/Networking/NexusMods.Networking.NexusWebApi/Auth/OAuth.cs +++ b/src/Networking/NexusMods.Networking.NexusWebApi/Auth/OAuth.cs @@ -1,14 +1,12 @@ -using System.Reactive.Linq; using System.Reactive.Subjects; -using System.Security.Cryptography; -using System.Text; using System.Text.Json; +using DynamicData.Kernel; using Microsoft.AspNetCore.WebUtilities; using Microsoft.Extensions.Logging; +using NexusMods.Abstractions.Jobs; using NexusMods.Abstractions.NexusWebApi.DTOs.OAuth; using NexusMods.Abstractions.NexusWebApi.Types; using NexusMods.CrossPlatform.Process; -using NexusMods.Extensions.BCL; namespace NexusMods.Networking.NexusWebApi.Auth; @@ -23,6 +21,7 @@ public class OAuth private const string OAuthRedirectUrl = "nxm://oauth/callback"; private const string OAuthClientId = "nma"; + private readonly IJobMonitor _jobMonitor; private readonly ILogger _logger; private readonly HttpClient _http; private readonly IOSInterop _os; @@ -32,11 +31,14 @@ public class OAuth /// /// constructor /// - public OAuth(ILogger logger, + public OAuth( + IJobMonitor jobMonitor, + ILogger logger, HttpClient http, IIDGenerator idGenerator, IOSInterop os) { + _jobMonitor = jobMonitor; _logger = logger; _http = http; _os = os; @@ -47,39 +49,18 @@ public OAuth(ILogger logger, /// /// Make an authorization request /// - /// - /// task with the jwt token once we receive one public async Task AuthorizeRequest(CancellationToken cancellationToken) { - // see https://www.rfc-editor.org/rfc/rfc7636#section-4.1 - var codeVerifier = _idGenerator.UUIDv4().ToBase64(); - - // see https://www.rfc-editor.org/rfc/rfc7636#section-4.2 - var codeChallengeBytes = SHA256.HashData(Encoding.UTF8.GetBytes(codeVerifier)); - var codeChallenge = StringBase64Extensions.Base64UrlEncode(codeChallengeBytes); - - var state = _idGenerator.UUIDv4(); - - var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); - - // Start listening first, otherwise we might miss the message - var codeTask = _nxmUrlMessages - .Where(oauth => oauth.State == state) - .Select(url => url.OAuth.Code) - .Where(code => code is not null) - .Select(code => code!) - .ToAsyncEnumerable() - .FirstAsync(cts.Token); - - var url = GenerateAuthorizeUrl(codeChallenge, state); - - // see https://www.rfc-editor.org/rfc/rfc7636#section-4.3 - await _os.OpenUrl(url, cancellationToken: cancellationToken); - - cts.CancelAfter(TimeSpan.FromMinutes(3)); - var code = await codeTask; - - return await AuthorizeToken(codeVerifier, code, cancellationToken); + var job = OAuthJob.Create( + jobMonitor: _jobMonitor, + idGenerator: _idGenerator, + os: _os, + httpClient: _http, + nxmUrlMessages: _nxmUrlMessages + ); + + var res = await job; + return res.ValueOrDefault(); } /// @@ -112,7 +93,11 @@ public void AddUrl(NXMOAuthUrl url) return JsonSerializer.Deserialize(responseString); } - private async Task AuthorizeToken(string verifier, string code, CancellationToken cancel) + internal static async Task AuthorizeToken( + string verifier, + string code, + HttpClient httpClient, + CancellationToken cancel) { var request = new Dictionary { { "grant_type", "authorization_code" }, @@ -124,7 +109,7 @@ public void AddUrl(NXMOAuthUrl url) var content = new FormUrlEncodedContent(request); - var response = await _http.PostAsync($"{OAuthUrl}/token", content, cancel); + var response = await httpClient.PostAsync($"{OAuthUrl}/token", content, cancel); var responseString = await response.Content.ReadAsStringAsync(cancel); return JsonSerializer.Deserialize(responseString); } diff --git a/src/Networking/NexusMods.Networking.NexusWebApi/OAuthJob.cs b/src/Networking/NexusMods.Networking.NexusWebApi/OAuthJob.cs new file mode 100644 index 0000000000..8d40cdef59 --- /dev/null +++ b/src/Networking/NexusMods.Networking.NexusWebApi/OAuthJob.cs @@ -0,0 +1,86 @@ +using System.Reactive.Linq; +using System.Reactive.Subjects; +using System.Security.Cryptography; +using System.Text; +using DynamicData.Kernel; +using NexusMods.Abstractions.Jobs; +using NexusMods.Abstractions.NexusWebApi; +using NexusMods.Abstractions.NexusWebApi.DTOs.OAuth; +using NexusMods.Abstractions.NexusWebApi.Types; +using NexusMods.CrossPlatform.Process; +using NexusMods.Extensions.BCL; +using NexusMods.Networking.NexusWebApi.Auth; + +namespace NexusMods.Networking.NexusWebApi; + +internal sealed class OAuthJob : IOAuthJob, IJobDefinitionWithStart> +{ + private readonly IIDGenerator _idGenerator; + private readonly IOSInterop _os; + private readonly HttpClient _httpClient; + private readonly Subject _nxmUrlMessages; + + public R3.Subject LoginUriSubject { get; } = new(); + + private OAuthJob( + IIDGenerator idGenerator, + IOSInterop os, + HttpClient httpClient, + Subject nxmUrlMessages) + { + _idGenerator = idGenerator; + _os = os; + _httpClient = httpClient; + _nxmUrlMessages = nxmUrlMessages; + } + + public static IJobTask> Create( + IJobMonitor jobMonitor, + IIDGenerator idGenerator, + IOSInterop os, + HttpClient httpClient, + Subject nxmUrlMessages) + { + var job = new OAuthJob(idGenerator, os, httpClient, nxmUrlMessages); + return jobMonitor.Begin>(job); + } + + public async ValueTask> StartAsync(IJobContext context) + { + // see https://www.rfc-editor.org/rfc/rfc7636#section-4.1 + var codeVerifier = _idGenerator.UUIDv4().ToBase64(); + + // see https://www.rfc-editor.org/rfc/rfc7636#section-4.2 + var codeChallengeBytes = SHA256.HashData(Encoding.UTF8.GetBytes(codeVerifier)); + var codeChallenge = StringBase64Extensions.Base64UrlEncode(codeChallengeBytes); + + var state = _idGenerator.UUIDv4(); + var uri = OAuth.GenerateAuthorizeUrl(codeChallenge, state); + LoginUriSubject.OnNext(uri); + + var cts = CancellationTokenSource.CreateLinkedTokenSource(context.CancellationToken); + + // Start listening first, otherwise we might miss the message + var codeTask = _nxmUrlMessages + .Where(oauth => oauth.State == state) + .Select(url => url.OAuth.Code) + .Where(code => code is not null) + .Select(code => code!) + .ToAsyncEnumerable() + .FirstAsync(cts.Token); + + // see https://www.rfc-editor.org/rfc/rfc7636#section-4.3 + await _os.OpenUrl(uri, cancellationToken: context.CancellationToken); + + cts.CancelAfter(TimeSpan.FromMinutes(3)); + var code = await codeTask; + + var token = await OAuth.AuthorizeToken(codeVerifier, code, _httpClient, context.CancellationToken); + return token; + } + + public void Dispose() + { + LoginUriSubject.Dispose(); + } +} diff --git a/src/NexusMods.App.UI/Overlays/Login/INexusLoginOverlayViewModel.cs b/src/NexusMods.App.UI/Overlays/Login/INexusLoginOverlayViewModel.cs index 7f624abe30..7740783aa8 100644 --- a/src/NexusMods.App.UI/Overlays/Login/INexusLoginOverlayViewModel.cs +++ b/src/NexusMods.App.UI/Overlays/Login/INexusLoginOverlayViewModel.cs @@ -1,9 +1,7 @@ -using System.Windows.Input; - -namespace NexusMods.App.UI.Overlays.Login; +namespace NexusMods.App.UI.Overlays.Login; public interface INexusLoginOverlayViewModel : IOverlayViewModel { - public ICommand Cancel { get; } - public Uri Uri { get; } + public R3.ReactiveCommand Cancel { get; } + public Uri? Uri { get; } } diff --git a/src/NexusMods.App.UI/Overlays/Login/NexusLoginOverlayDesignerViewModel.cs b/src/NexusMods.App.UI/Overlays/Login/NexusLoginOverlayDesignerViewModel.cs index f0dcd50da9..a60a076222 100644 --- a/src/NexusMods.App.UI/Overlays/Login/NexusLoginOverlayDesignerViewModel.cs +++ b/src/NexusMods.App.UI/Overlays/Login/NexusLoginOverlayDesignerViewModel.cs @@ -1,9 +1,7 @@ -using System.Windows.Input; - -namespace NexusMods.App.UI.Overlays.Login; +namespace NexusMods.App.UI.Overlays.Login; public class NexusLoginOverlayDesignerViewModel : AOverlayViewModel, INexusLoginOverlayViewModel { - public ICommand Cancel { get; } = Initializers.ICommand; + public R3.ReactiveCommand Cancel { get; } = new(); public Uri Uri { get; } = new("https://www.nexusmods.com/some/login?name=John&key=1234567890"); } diff --git a/src/NexusMods.App.UI/Overlays/Login/NexusLoginOverlayService.cs b/src/NexusMods.App.UI/Overlays/Login/NexusLoginOverlayService.cs index f1434209c3..96146150cd 100644 --- a/src/NexusMods.App.UI/Overlays/Login/NexusLoginOverlayService.cs +++ b/src/NexusMods.App.UI/Overlays/Login/NexusLoginOverlayService.cs @@ -2,58 +2,62 @@ using DynamicData; using JetBrains.Annotations; using Microsoft.Extensions.Hosting; +using NexusMods.Abstractions.Jobs; using NexusMods.Abstractions.NexusWebApi; namespace NexusMods.App.UI.Overlays.Login; - -/// -/// Very simple service that connects the activity monitor to the overlay controller. Looks for OAuth login activities and -/// displays an overlay for them. We have to do it this way as the overlay controller doesn't know about the login overlays, -/// and the activity monitory and login manager are way on the backend. So this is a bit of a UI/Backend bridge. -/// [UsedImplicitly] -public class NexusLoginOverlayService(IOverlayController overlayController) : IHostedService +public class NexusLoginOverlayService : IHostedService { - private readonly CompositeDisposable _compositeDisposable = new(); + private readonly IOverlayController _overlayController; + private readonly IJobMonitor _jobMonitor; + private readonly CompositeDisposable _compositeDisposable; private NexusLoginOverlayViewModel? _overlayViewModel; + private IJob? _currentJob; + + public NexusLoginOverlayService(IOverlayController overlayController, IJobMonitor jobMonitor) + { + _overlayController = overlayController; + _jobMonitor = jobMonitor; + _compositeDisposable = new CompositeDisposable(); + } public Task StartAsync(CancellationToken cancellationToken) { - // TODO: - // activityMonitor.Activities - // .ToObservableChangeSet(x => x.Id) - // .Filter(x => x.Group.Value == Constants.OAuthActivityGroupName) - // .OnUI() - // .SubscribeWithErrorLogging(changeSet => - // { - // if (changeSet.Removes > 0 && _currentLoginActivity is not null) - // { - // if (changeSet.Any(x => x.Reason == ChangeReason.Remove && x.Current == _currentLoginActivity)) - // { - // _currentLoginActivity = null; - // _overlayViewModel?.Close(); - // } - // } - // - // if (changeSet.Adds > 0) - // { - // if (_currentLoginActivity is not null) return; - // - // _currentLoginActivity = changeSet.First(x => x.Reason == ChangeReason.Add).Current; - // _overlayViewModel = new NexusLoginOverlayViewModel(_currentLoginActivity); - // - // overlayController.Enqueue(_overlayViewModel); - // } - // }) - // .DisposeWith(_compositeDisposable); + _jobMonitor + .ObserveActiveJobs() + .OnUI() + .SubscribeWithErrorLogging(changeSet => + { + if (changeSet.Removes > 0 && _currentJob is not null) + { + if (changeSet.Any(x => x.Reason == ChangeReason.Remove && x.Current == _currentJob)) + { + _currentJob = null; + _overlayViewModel?.Close(); + } + } + + if (changeSet.Adds > 0) + { + if (_currentJob is not null) return; + + _currentJob = changeSet.First(x => x.Reason == ChangeReason.Add).Current; + _overlayViewModel = new NexusLoginOverlayViewModel(_currentJob); + + _overlayController.Enqueue(_overlayViewModel); + } + }) + .DisposeWith(_compositeDisposable); return Task.CompletedTask; } public Task StopAsync(CancellationToken cancellationToken) { + _currentJob = null; _overlayViewModel?.Close(); _compositeDisposable.Dispose(); diff --git a/src/NexusMods.App.UI/Overlays/Login/NexusLoginOverlayView.axaml.cs b/src/NexusMods.App.UI/Overlays/Login/NexusLoginOverlayView.axaml.cs index a5f8884927..85151d7907 100644 --- a/src/NexusMods.App.UI/Overlays/Login/NexusLoginOverlayView.axaml.cs +++ b/src/NexusMods.App.UI/Overlays/Login/NexusLoginOverlayView.axaml.cs @@ -15,15 +15,14 @@ public NexusLoginOverlayView() { CopyButton.Command = ReactiveCommand.CreateFromTask(async () => { - await TopLevel.GetTopLevel(this)!.Clipboard!.SetTextAsync(ViewModel!.Uri.ToString()); + await TopLevel.GetTopLevel(this)!.Clipboard!.SetTextAsync(ViewModel?.Uri?.ToString()); }); this.BindCommand(ViewModel, vm => vm.Cancel, v => v.CancelButton) .DisposeWith(d); - this.WhenAnyValue(view => view.ViewModel!.Uri) - .BindTo(this, view => view.UrlTextBlock.Text) - .DisposeWith(d); + this.OneWayBind(ViewModel, vm => vm.Uri, view => view.UrlTextBlock.Text) + .DisposeWith(d); }); } } diff --git a/src/NexusMods.App.UI/Overlays/Login/NexusLoginOverlayViewModel.cs b/src/NexusMods.App.UI/Overlays/Login/NexusLoginOverlayViewModel.cs index 9f4b7fe133..f394e9934c 100644 --- a/src/NexusMods.App.UI/Overlays/Login/NexusLoginOverlayViewModel.cs +++ b/src/NexusMods.App.UI/Overlays/Login/NexusLoginOverlayViewModel.cs @@ -1,26 +1,28 @@ -using System.Windows.Input; +using NexusMods.Abstractions.Jobs; +using NexusMods.Abstractions.NexusWebApi; +using R3; +using ReactiveUI.Fody.Helpers; namespace NexusMods.App.UI.Overlays.Login; public class NexusLoginOverlayViewModel : AOverlayViewModel, INexusLoginOverlayViewModel { - public NexusLoginOverlayViewModel() + public NexusLoginOverlayViewModel(IJob job) { - // TODO: - Uri = null!; - Cancel = null!; + if (job.Definition is IOAuthJob oAuthJob) + { + oAuthJob.LoginUriSubject + .ObserveOnUIThreadDispatcher() + .Subscribe(this, static (uri, self) => self.Uri = uri); + } - // Uri = (Uri)activity.Payload!; - // Cancel = ReactiveCommand.Create(() => - // { - // if (activity is IActivitySource activitySource) - // activitySource.Dispose(); - // Close(); - // } - // ); + Cancel = new ReactiveCommand(execute: _ => + { + // TODO: cancel job + Close(); + }); } - public ICommand Cancel { get; } - - public Uri Uri { get; } + public ReactiveCommand Cancel { get; } + [Reactive] public Uri? Uri { get; private set; } } diff --git a/src/NexusMods.Jobs/JobContext.cs b/src/NexusMods.Jobs/JobContext.cs index 51087a241c..0d7591ac5e 100644 --- a/src/NexusMods.Jobs/JobContext.cs +++ b/src/NexusMods.Jobs/JobContext.cs @@ -1,4 +1,3 @@ -using System.Diagnostics; using System.Numerics; using System.Reactive.Subjects; using DynamicData.Kernel; @@ -113,6 +112,7 @@ public async ValueTask DisposeAsync() public void Dispose() { + // ReSharper disable once SuspiciousTypeConversion.Global if (_definition is IDisposable disposable) { disposable.Dispose(); diff --git a/tests/Networking/NexusMods.Networking.NexusWebApi.Tests/OAuthTests.cs b/tests/Networking/NexusMods.Networking.NexusWebApi.Tests/OAuthTests.cs index 1080753094..d85b2e77c4 100644 --- a/tests/Networking/NexusMods.Networking.NexusWebApi.Tests/OAuthTests.cs +++ b/tests/Networking/NexusMods.Networking.NexusWebApi.Tests/OAuthTests.cs @@ -2,6 +2,7 @@ using System.Text.Json; using FluentAssertions; using Microsoft.Extensions.Logging; +using NexusMods.Abstractions.Jobs; using NexusMods.Abstractions.NexusWebApi.DTOs.OAuth; using NexusMods.Abstractions.NexusWebApi.Types; using NexusMods.CrossPlatform.Process; @@ -15,9 +16,10 @@ public class OAuthTests // ReSharper disable once InconsistentNaming private readonly Uri ExpectedAuthURL = new("https://users.nexusmods.com/oauth/authorize?response_type=code&scope=openid profile email&code_challenge_method=S256&client_id=nma&redirect_uri=nxm%3A%2F%2Foauth%2Fcallback&code_challenge=QMZ4D7BLeehAXINE9NZ8dho2i5AYVTbfqJ8PhQ4eUrE&state=00000000-0000-0000-0000-000000000000"); private readonly ILogger _logger; + private readonly IJobMonitor _jobMonitor; // ReSharper disable once ContextualLoggerProblem - public OAuthTests(ILogger logger) + public OAuthTests(ILogger logger, IJobMonitor jobMonitor) { _logger = logger; } @@ -46,7 +48,7 @@ public async void AuthorizeRequestTest() #endregion #region Execution - var oauth = new OAuth(_logger, httpClient, idGen, os); + var oauth = new OAuth(_jobMonitor, _logger, httpClient, idGen, os); var tokenTask = oauth.AuthorizeRequest(CancellationToken.None); oauth.AddUrl(NXMUrl.Parse($"nxm://oauth/callback?state={stateId}&code=code").OAuth); var result = await tokenTask; @@ -84,7 +86,7 @@ public async void RefreshTokenTest() #endregion #region Execution - var oauth = new OAuth(_logger, httpClient, idGen, os); + var oauth = new OAuth(_jobMonitor, _logger, httpClient, idGen, os); var token = await oauth.RefreshToken("refresh_token", CancellationToken.None); #endregion @@ -121,7 +123,7 @@ public async void ThrowsOnInvalidResponse() #endregion #region Execution - var oauth = new OAuth(_logger, httpClient, idGen, os); + var oauth = new OAuth(_jobMonitor, _logger, httpClient, idGen, os); Func call = () => oauth.AuthorizeRequest(CancellationToken.None); var tokenTask = call.Should().ThrowAsync(); oauth.AddUrl(NXMUrl.Parse($"nxm://oauth/callback?state={stateId}&code=code").OAuth); @@ -146,7 +148,7 @@ public async void AuthorizationCanBeCanceled() #endregion #region Execution - var oauth = new OAuth(_logger, httpClient, idGen, os); + var oauth = new OAuth(_jobMonitor, _logger, httpClient, idGen, os); Func call = () => oauth.AuthorizeRequest(cts.Token); var task = call.Should().ThrowAsync(); cts.Cancel(); diff --git a/tests/NexusMods.UI.Tests/Overlays/NexusLoginOverlayTests.cs b/tests/NexusMods.UI.Tests/Overlays/NexusLoginOverlayTests.cs deleted file mode 100644 index 92de077e15..0000000000 --- a/tests/NexusMods.UI.Tests/Overlays/NexusLoginOverlayTests.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reactive.Linq; -using DynamicData; -using DynamicData.Binding; -using FluentAssertions; -using NexusMods.App.UI.Overlays; -using NexusMods.App.UI.Overlays.Login; -using NexusMods.Networking.NexusWebApi.Auth; - -namespace NexusMods.UI.Tests.Overlays; - -public class NexusLoginOverlayTests -{ - - // [Fact] - // [Trait("FlakeyTest", "True")] - // public async Task LoginTasksCreateOverlays() - // { - // var overlayController = new OverlayController(); - // var nexusLoginService = new NexusLoginOverlayService(overlayController); - // - // await nexusLoginService.StartAsync(CancellationToken.None); - // - // var url = new Uri("http://foo/bar"); - // - // var listOfJobs = new List>(); - // using var subbed = activityMonitor.Activities.ToObservableChangeSet() - // .Subscribe(change => listOfJobs.Add(change)); - // - // - // - // var job = activityMonitor.CreateWithPayload(OAuth.Group, url, "Logging into Nexus Mods, redirecting to {Url}", url); - // activityMonitor.Activities.Should().Contain((IReadOnlyActivity)job); - // overlayController.CurrentOverlay.Should().BeOfType(); - // - // } -}