-
-
Notifications
You must be signed in to change notification settings - Fork 89
RG-T135 Fixing Setup Wizard and Setup Report Issue #532
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,91 @@ | ||
| using System; | ||
| using System.Collections.Generic; | ||
| using System.Security.Claims; | ||
| using System.Threading; | ||
| using System.Threading.Tasks; | ||
| using Microsoft.AspNetCore.Http; | ||
| using Microsoft.AspNetCore.Mvc; | ||
| using Microsoft.AspNetCore.Mvc.Rendering; | ||
| using Microsoft.AspNetCore.Mvc.ViewComponents; | ||
| using Microsoft.AspNetCore.Mvc.ViewEngines; | ||
| using Moq; | ||
| using NUnit.Framework; | ||
| using Resgrid.AdminAssist; | ||
| using Resgrid.Model.AdminAssist; | ||
| using Resgrid.Providers.Claims; | ||
| using Resgrid.Web.Helpers; | ||
| using Resgrid.Web.ViewComponents; | ||
|
|
||
| namespace Resgrid.Tests.AdminAssist | ||
| { | ||
| /// <summary>The dashboard's Setup Wizard banner is for department administrators only.</summary> | ||
| [TestFixture, NonParallelizable] | ||
| public class SetupPromptVisibilityTests | ||
| { | ||
| private static readonly AdminAssistActor Actor = new(7, "user-1"); | ||
| private static readonly ConfigurationCatalog Catalog = new(); | ||
| private IHttpContextAccessor _previousAccessor; | ||
| private Mock<IAdminAssistAccessService> _access; | ||
| private Mock<IAdminAssistRepository> _repository; | ||
|
|
||
| [SetUp] | ||
| public void SetUp() | ||
| { | ||
| _previousAccessor = ClaimsAuthorizationHelper._httpContextAccessor; | ||
| _access = new Mock<IAdminAssistAccessService>(MockBehavior.Strict); | ||
| _repository = new Mock<IAdminAssistRepository>(MockBehavior.Strict); | ||
| } | ||
|
|
||
| [TearDown] | ||
| public void TearDown() => ClaimsAuthorizationHelper._httpContextAccessor = _previousAccessor; | ||
|
|
||
| [Test] | ||
| public async Task Member_never_sees_the_banner_and_costs_no_flag_or_workspace_read() | ||
| { | ||
| var result = await InvokeAsync(admin: false); | ||
|
|
||
| Assert.That(result, Is.InstanceOf<ContentViewComponentResult>()); | ||
| Assert.That(((ContentViewComponentResult)result).Content, Is.Empty); | ||
| _access.VerifyNoOtherCalls(); _repository.VerifyNoOtherCalls(); | ||
| } | ||
|
|
||
| [Test] | ||
| public async Task Admin_sees_the_banner_while_setup_is_open() | ||
| { | ||
| _access.Setup(a => a.CanAccessAsync(It.Is<AdminAssistActor>(x => x.DepartmentId == Actor.DepartmentId && x.UserId == Actor.UserId), true, It.IsAny<CancellationToken>())).ReturnsAsync(true); | ||
| _repository.Setup(r => r.GetWorkspaceAsync(Actor.DepartmentId, Actor.UserId, Catalog.Version, It.IsAny<CancellationToken>())) | ||
| .ReturnsAsync(new SetupWorkspace(Actor.DepartmentId, 0, SetupMode.Fresh, new Dictionary<string, SetupAreaChoice>(), Array.Empty<string>(), Array.Empty<string>(), Catalog.Version, null)); | ||
|
|
||
| var result = await InvokeAsync(admin: true); | ||
|
|
||
| Assert.That(result, Is.InstanceOf<ViewViewComponentResult>()); | ||
| } | ||
|
|
||
| [Test] | ||
| public async Task Admin_claim_alone_is_not_enough_when_fresh_access_is_refused() | ||
| { | ||
| // The claim may be stale; the fresh membership and rollout check still decides. | ||
| _access.Setup(a => a.CanAccessAsync(It.IsAny<AdminAssistActor>(), true, It.IsAny<CancellationToken>())).ReturnsAsync(false); | ||
|
|
||
| var result = await InvokeAsync(admin: true); | ||
|
|
||
| Assert.That(((ContentViewComponentResult)result).Content, Is.Empty); | ||
| _repository.VerifyNoOtherCalls(); | ||
| } | ||
|
|
||
| private async Task<IViewComponentResult> InvokeAsync(bool admin) | ||
| { | ||
| var claims = new List<Claim> { new(ClaimTypes.PrimarySid, Actor.UserId), new(ClaimTypes.PrimaryGroupSid, Actor.DepartmentId.ToString()) }; | ||
| if (admin) claims.Add(new Claim(ResgridClaimTypes.Resources.Department, ResgridClaimTypes.Actions.Update)); | ||
| var http = new DefaultHttpContext { User = new ClaimsPrincipal(new ClaimsIdentity(claims, "Test")) }; | ||
| ClaimsAuthorizationHelper._httpContextAccessor = new HttpContextAccessor { HttpContext = http }; | ||
|
|
||
| var component = new AdminAssistSetupPromptViewComponent(_access.Object, _repository.Object, Catalog) | ||
| { | ||
| ViewComponentContext = new ViewComponentContext { ViewContext = new ViewContext { HttpContext = http } }, | ||
| ViewEngine = Mock.Of<ICompositeViewEngine>() | ||
| }; | ||
| return await component.InvokeAsync(); | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| using System; | ||
| using System.Globalization; | ||
| using System.IO; | ||
| using System.Linq; | ||
| using System.Text.RegularExpressions; | ||
| using FluentAssertions; | ||
| using Microsoft.AspNetCore.Mvc; | ||
| using Microsoft.Extensions.DependencyInjection; | ||
| using Microsoft.Extensions.Localization; | ||
| using NUnit.Framework; | ||
| using AdminAssistLabels = Resgrid.Localization.Areas.User.AdminAssist.AdminAssist; | ||
|
|
||
| namespace Resgrid.Tests.Web | ||
| { | ||
| /// <summary> | ||
| /// The API host never registered localization, so every v4 controller taking an IStringLocalizer (Admin Assist among | ||
| /// them) failed to construct and returned 500. The Setup Wizard and Setup Report showed only "The report could not be | ||
| /// loaded" because their Catalog and Overview reads never reached the controller. | ||
| /// </summary> | ||
| [TestFixture] | ||
| public class ApiLocalizationRegistrationTests | ||
| { | ||
| [Test] | ||
| public void Api_startup_registers_localization_for_controllers_that_take_string_localizers() | ||
| { | ||
| var localized = typeof(Resgrid.Web.Services.Controllers.v4.AdminAssistController).Assembly.GetTypes() | ||
| .Where(t => typeof(ControllerBase).IsAssignableFrom(t) && !t.IsAbstract) | ||
| .Where(t => t.GetConstructors().Any(c => c.GetParameters().Any(p => p.ParameterType.IsGenericType && | ||
| p.ParameterType.GetGenericTypeDefinition() == typeof(IStringLocalizer<>)))) | ||
| .Select(t => t.Name) | ||
| .ToList(); | ||
| localized.Should().Contain("AdminAssistController"); | ||
|
|
||
| var startup = File.ReadAllText(FindRepositoryFile("Web/Resgrid.Web.Services/Startup.cs")); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. An Kody rule violation: Add try-catch blocks for external calls string startup;
try
{
startup = File.ReadAllText(FindRepositoryFile("Web/Resgrid.Web.Services/Startup.cs"));
}
catch (IOException exception)
{
Assert.Fail($"Unable to read Startup.cs: {exception.Message}");
throw;
}Prompt for LLMTalk to Kody by mentioning @kody Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction. |
||
| Regex.IsMatch(startup, @"^\s*services\.AddLocalization\(", RegexOptions.Multiline) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Regular expression processing without a timeout in Kody rule violation: Specify Timeout for Regular Expressions Prompt for LLMTalk to Kody by mentioning @kody Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction. |
||
| .Should().BeTrue($"the API constructs {string.Join(", ", localized)} with an IStringLocalizer"); | ||
| } | ||
|
|
||
| [TestCase("")] | ||
| [TestCase("en")] | ||
| [TestCase("de")] | ||
| public void Admin_assist_catalog_strings_resolve_from_the_registered_localizer(string culture) | ||
| { | ||
| var previous = CultureInfo.CurrentUICulture; | ||
| try | ||
| { | ||
| CultureInfo.CurrentUICulture = CultureInfo.GetCultureInfo(culture); | ||
| // The host supplies logging; the localizer factory depends on it. | ||
| using var provider = new ServiceCollection().AddLogging().AddLocalization().BuildServiceProvider(); | ||
| var labels = provider.GetRequiredService<IStringLocalizer<AdminAssistLabels>>(); | ||
|
|
||
| // The same projection the Catalog endpoint returns to the page. | ||
| var strings = labels.GetAllStrings(true).GroupBy(s => s.Name).ToDictionary(g => g.Key, g => g.First().Value); | ||
|
|
||
| strings.Should().ContainKey("Ui.Error"); | ||
| strings["Ui.Title"].Should().Be("Admin Assist"); | ||
| } | ||
| finally { CultureInfo.CurrentUICulture = previous; } | ||
| } | ||
|
|
||
| private static string FindRepositoryFile(string relativePath) | ||
| { | ||
| var directory = new DirectoryInfo(TestContext.CurrentContext.TestDirectory); | ||
|
|
||
| while (directory != null && !File.Exists(Path.Combine(directory.FullName, "Resgrid.sln"))) | ||
| directory = directory.Parent; | ||
|
|
||
| if (directory == null) | ||
| throw new InvalidOperationException("Unable to locate the repository root."); | ||
|
|
||
| return Path.Combine(directory.FullName, relativePath.Replace('/', Path.DirectorySeparatorChar)); | ||
| } | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The long LINQ chain obscures the separate filtering and projection steps for
controllersandlocalized. Store the controller filter in a named intermediate query before applying the localization filter and projection.Kody rule violation: Limit Lengthy LINQ Chains
Prompt for LLM
Talk to Kody by mentioning @kody
Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.