Repository navigation
Fix/user login does not check active flags #368
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
Merged
Merged
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
4381aa0
Fixing issue where user login doesn't check current/future flags
bsedwards 3bda2d1
Fixing condition
bsedwards f5bbd5f
Use AsNoTracking for user retrieval from cache
bsedwards ef61630
Code review comments - handling cache misses, users in cache too long…
bsedwards 5e358a3
Fixing missing using statement
bsedwards 68287bf
Adding test that an inactive user with the same loginid as an active …
bsedwards File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,162 @@ | ||
| using Microsoft.EntityFrameworkCore; | ||
| using Microsoft.Extensions.Caching.Memory; | ||
| using Microsoft.Extensions.Internal; | ||
| using Viper.Classes.SQLContext; | ||
| using Viper.Models.AAUD; | ||
| using Viper.test.Students; | ||
|
|
||
| namespace Viper.test.Classes | ||
| { | ||
| [Collection(HttpHelperCacheCollection.Name)] | ||
| public sealed class UserHelperGetByLoginIdTests : IDisposable | ||
| { | ||
| private const string LoginId = "jdoe"; | ||
|
|
||
| private readonly FakeClock _clock = new(); | ||
| private readonly MemoryCache _memoryCache; | ||
| private readonly TestableAAUDContext _context; | ||
|
|
||
| public UserHelperGetByLoginIdTests() | ||
| { | ||
| _memoryCache = new MemoryCache(new MemoryCacheOptions { Clock = _clock }); | ||
| HttpHelper.Configure(_memoryCache, null!, null!, null!, null!, null!, null!); | ||
|
|
||
| var options = new DbContextOptionsBuilder<AAUDContext>() | ||
| .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString()) | ||
| .Options; | ||
| _context = new TestableAAUDContext(options); | ||
| } | ||
|
|
||
| // Clear, not dispose: HttpHelper.Cache is a process-wide static, so a disposed instance would | ||
| // make every later test throw. | ||
| public void Dispose() | ||
| { | ||
| _memoryCache.Clear(); | ||
| _context.Dispose(); | ||
| } | ||
|
|
||
| private const int ActiveUserId = 1; | ||
| private const string ActiveMothraId = "00012345"; | ||
|
|
||
| private void AddUser(int current = 1, int future = 0, int aaudUserId = ActiveUserId, string mothraId = ActiveMothraId) | ||
| { | ||
| _context.AaudUsers.Add(new AaudUser | ||
| { | ||
| AaudUserId = aaudUserId, | ||
| ClientId = "C" + aaudUserId, | ||
| MothraId = mothraId, | ||
| LoginId = LoginId, | ||
| FirstName = "Jane", | ||
| LastName = "Doe", | ||
| DisplayFirstName = "Jane", | ||
| DisplayLastName = "Doe", | ||
| DisplayFullName = "Jane Doe", | ||
| Current = current, | ||
| Future = future, | ||
| }); | ||
| _context.SaveChanges(); | ||
| _context.ChangeTracker.Clear(); | ||
| } | ||
|
|
||
| private void RemoveUsers() | ||
| { | ||
| _context.AaudUsers.RemoveRange(_context.AaudUsers); | ||
| _context.SaveChanges(); | ||
| } | ||
|
|
||
| [Theory] | ||
| [InlineData(1, 0)] | ||
| [InlineData(0, 1)] | ||
| public void GetByLoginId_ReturnsCurrentOrFutureUser(int current, int future) | ||
| { | ||
| AddUser(current, future); | ||
|
|
||
| var result = new UserHelper().GetByLoginId(_context, LoginId); | ||
|
|
||
| Assert.NotNull(result); | ||
| Assert.Equal(ActiveUserId, result.AaudUserId); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void GetByLoginId_ReturnsNullForInactiveUser() | ||
| { | ||
| AddUser(current: 0, future: 0); | ||
|
|
||
| Assert.Null(new UserHelper().GetByLoginId(_context, LoginId)); | ||
| } | ||
|
|
||
| [Theory] | ||
| [InlineData(true)] | ||
| [InlineData(false)] | ||
| public void GetByLoginId_PrefersActiveUserOverFormerUserWithSameLoginId(bool formerInsertedFirst) | ||
| { | ||
| // Both orders, so the active-flag filter is proven rather than the row order. | ||
| if (formerInsertedFirst) | ||
| { | ||
| AddUser(current: 0, future: 0, aaudUserId: 2, mothraId: "00099999"); | ||
| AddUser(); | ||
| } | ||
| else | ||
| { | ||
| AddUser(); | ||
| AddUser(current: 0, future: 0, aaudUserId: 2, mothraId: "00099999"); | ||
| } | ||
|
|
||
| var result = new UserHelper().GetByLoginId(_context, LoginId); | ||
|
|
||
| Assert.NotNull(result); | ||
| Assert.Equal(ActiveUserId, result.AaudUserId); | ||
| Assert.Equal(ActiveMothraId, result.MothraId); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void GetByLoginId_ServesFoundUserFromCache() | ||
| { | ||
| AddUser(); | ||
| var helper = new UserHelper(); | ||
| helper.GetByLoginId(_context, LoginId); | ||
|
|
||
| RemoveUsers(); | ||
|
|
||
| Assert.Equal(ActiveUserId, helper.GetByLoginId(_context, LoginId)?.AaudUserId); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void GetByLoginId_DoesNotCacheMiss() | ||
| { | ||
| var helper = new UserHelper(); | ||
| Assert.Null(helper.GetByLoginId(_context, LoginId)); | ||
|
|
||
| // A user who becomes active after a failed lookup must not be locked out by a cached null. | ||
| AddUser(); | ||
|
|
||
| Assert.Equal(ActiveUserId, helper.GetByLoginId(_context, LoginId)?.AaudUserId); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void GetByLoginId_CachedUserExpires() | ||
| { | ||
| AddUser(); | ||
| var helper = new UserHelper(); | ||
| helper.GetByLoginId(_context, LoginId); | ||
|
|
||
| // A deactivated user must drop out once the cache entry expires. | ||
| RemoveUsers(); | ||
| AddUser(current: 0, future: 0); | ||
| _clock.UtcNow = _clock.UtcNow.AddMinutes(11); | ||
|
|
||
| Assert.Null(helper.GetByLoginId(_context, LoginId)); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void GetByLoginId_ReturnsNullForNullLoginId() | ||
| { | ||
| Assert.Null(new UserHelper().GetByLoginId(_context, null)); | ||
| } | ||
|
|
||
| private sealed class FakeClock : ISystemClock | ||
| { | ||
| public DateTimeOffset UtcNow { get; set; } = DateTimeOffset.UtcNow; | ||
| } | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.