/* * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) * See https://github.com/openiddict/openiddict-core for more information concerning * the license and the contributors participating to this project. */ using System.Collections.Immutable; using System.ComponentModel.DataAnnotations; using System.Text.Json; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Moq; using Xunit; namespace OpenIddict.Core.Tests; public class OpenIddictSessionManagerTests { [Fact] public void Constructor_ThrowsAnExceptionForNullCache() { // Arrange var cache = (IOpenIddictSessionCache) null!; var logger = Mock.Of>>(); var options = Mock.Of>(); var store = Mock.Of>(); // Act and assert var exception = Assert.Throws( () => new OpenIddictSessionManager(cache, logger, options, store)); Assert.Equal("cache", exception.ParamName); } [Fact] public void Constructor_ThrowsAnExceptionForNullLogger() { // Arrange var cache = Mock.Of>(); var logger = (ILogger>) null!; var options = Mock.Of>(); var store = Mock.Of>(); // Act and assert var exception = Assert.Throws( () => new OpenIddictSessionManager(cache, logger, options, store)); Assert.Equal("logger", exception.ParamName); } [Fact] public void Constructor_ThrowsAnExceptionForNullOptions() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = (IOptionsMonitor) null!; var store = Mock.Of>(); // Act and assert var exception = Assert.Throws( () => new OpenIddictSessionManager(cache, logger, options, store)); Assert.Equal("options", exception.ParamName); } [Fact] public void Constructor_ThrowsAnExceptionForNullStore() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = (IOpenIddictSessionStore) null!; // Act and assert var exception = Assert.Throws( () => new OpenIddictSessionManager(cache, logger, options, store)); Assert.Equal("store", exception.ParamName); } [Fact] public async Task CountAsync_CallsStoreMethod() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>( mock => mock.CurrentValue == new OpenIddictCoreOptions()); var store = new Mock>(); store.Setup(store => store.CountAsync(It.IsAny())) .ReturnsAsync(42); var manager = new OpenIddictSessionManager(cache, logger, options, store.Object); // Act var count = await manager.CountAsync(); // Assert Assert.Equal(42, count); store.Verify(store => store.CountAsync(It.IsAny()), Times.Once()); } [Fact] public async Task CountAsync_WithQuery_ThrowsAnExceptionForNullQuery() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = Mock.Of>(); var manager = new OpenIddictSessionManager(cache, logger, options, store); // Act and assert var exception = await Assert.ThrowsAsync( () => manager.CountAsync(query: null!).AsTask()); Assert.Equal("query", exception.ParamName); } [Fact] public async Task CreateAsync_ThrowsAnExceptionForNullSession() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>( mock => mock.CurrentValue == new OpenIddictCoreOptions()); var store = Mock.Of>(); var manager = new OpenIddictSessionManager(cache, logger, options, store); // Act and assert var exception = await Assert.ThrowsAsync( () => manager.CreateAsync(session: null!).AsTask()); Assert.Equal("session", exception.ParamName); } [Fact] public async Task DeleteAsync_ThrowsAnExceptionForNullSession() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>( mock => mock.CurrentValue == new OpenIddictCoreOptions()); var store = Mock.Of>(); var manager = new OpenIddictSessionManager(cache, logger, options, store); // Act and assert var exception = await Assert.ThrowsAsync( () => manager.DeleteAsync(session: null!).AsTask()); Assert.Equal("session", exception.ParamName); } [Fact] public async Task DeleteAsync_RemovessessionFromCache_WhenCachingIsEnabled() { // Arrange var cache = new Mock>(); var logger = Mock.Of>>(); var options = Mock.Of>( mock => mock.CurrentValue == new OpenIddictCoreOptions { DisableEntityCaching = false }); var store = new Mock>(); var session = new CustomSession(); var manager = new OpenIddictSessionManager(cache.Object, logger, options, store.Object); // Act await manager.DeleteAsync(session); // Assert cache.Verify(cache => cache.RemoveAsync(session, It.IsAny()), Times.Once()); store.Verify(store => store.DeleteAsync(session, It.IsAny()), Times.Once()); } [Fact] public async Task DeleteAsync_DoesNotRemoveFromCache_WhenCachingIsDisabled() { // Arrange var cache = new Mock>(); var logger = Mock.Of>>(); var options = Mock.Of>( mock => mock.CurrentValue == new OpenIddictCoreOptions { DisableEntityCaching = true }); var store = new Mock>(); var session = new CustomSession(); var manager = new OpenIddictSessionManager(cache.Object, logger, options, store.Object); // Act await manager.DeleteAsync(session); // Assert cache.Verify(cache => cache.RemoveAsync(It.IsAny(), It.IsAny()), Times.Never()); store.Verify(store => store.DeleteAsync(session, It.IsAny()), Times.Once()); } [Fact] public async Task FindByIdAsync_ThrowsAnExceptionForNullIdentifier() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = Mock.Of>(); var manager = new OpenIddictSessionManager(cache, logger, options, store); // Act and assert var exception = await Assert.ThrowsAsync( () => manager.FindByIdAsync(identifier: null!).AsTask()); Assert.Equal("identifier", exception.ParamName); } [Fact] public async Task FindByIdAsync_ThrowsAnExceptionForEmptyIdentifier() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = Mock.Of>(); var manager = new OpenIddictSessionManager(cache, logger, options, store); // Act and assert var exception = await Assert.ThrowsAsync( () => manager.FindByIdAsync(identifier: string.Empty).AsTask()); Assert.Equal("identifier", exception.ParamName); } [Fact] public async Task FindByIdAsync_UsesCache_WhenCachingIsEnabled() { // Arrange var session = new CustomSession(); var cache = new Mock>(); var logger = Mock.Of>>(); var options = Mock.Of>( mock => mock.CurrentValue == new OpenIddictCoreOptions { DisableEntityCaching = false }); var store = new Mock>(); cache.Setup(cache => cache.FindByIdAsync("id", It.IsAny())) .ReturnsAsync(session); store.Setup(store => store.GetIdAsync(session, It.IsAny())) .ReturnsAsync("id"); var manager = new OpenIddictSessionManager(cache.Object, logger, options, store.Object); // Act var result = await manager.FindByIdAsync("id"); // Assert Assert.Same(session, result); cache.Verify(cache => cache.FindByIdAsync("id", It.IsAny()), Times.Once()); store.Verify(store => store.FindByIdAsync(It.IsAny(), It.IsAny()), Times.Never()); } [Fact] public async Task FindByIdAsync_UsesStore_WhenCachingIsDisabled() { // Arrange var session = new CustomSession(); var cache = new Mock>(); var logger = Mock.Of>>(); var options = Mock.Of>( mock => mock.CurrentValue == new OpenIddictCoreOptions { DisableEntityCaching = true }); var store = new Mock>(); store.Setup(store => store.FindByIdAsync("id", It.IsAny())) .ReturnsAsync(session); store.Setup(store => store.GetIdAsync(session, It.IsAny())) .ReturnsAsync("id"); var manager = new OpenIddictSessionManager(cache.Object, logger, options, store.Object); // Act var result = await manager.FindByIdAsync("id"); // Assert Assert.Same(session, result); cache.Verify(cache => cache.FindByIdAsync(It.IsAny(), It.IsAny()), Times.Never()); store.Verify(store => store.FindByIdAsync("id", It.IsAny()), Times.Once()); } [Fact] public async Task GetAsync_WithQuery_ThrowsAnExceptionForNullQuery() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = Mock.Of>(); var manager = new OpenIddictSessionManager(cache, logger, options, store); // Act and assert var exception = await Assert.ThrowsAsync( () => manager.GetAsync(query: null!).AsTask()); Assert.Equal("query", exception.ParamName); } [Fact] public async Task GetAsync_WithQueryAndState_ThrowsAnExceptionForNullQuery() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = Mock.Of>(); var manager = new OpenIddictSessionManager(cache, logger, options, store); // Act and assert var exception = await Assert.ThrowsAsync( () => manager.GetAsync(query: null!, state: null!).AsTask()); Assert.Equal("query", exception.ParamName); } [Fact] public async Task GetIdAsync_ThrowsAnExceptionForNullSession() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = Mock.Of>(); var manager = new OpenIddictSessionManager(cache, logger, options, store); // Act and assert var exception = await Assert.ThrowsAsync( () => manager.GetIdAsync(session: null!).AsTask()); Assert.Equal("session", exception.ParamName); } [Fact] public async Task GetIdAsync_ReturnsIdentifierFromStore() { // Arrange var session = new CustomSession(); var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = new Mock>(); store.Setup(store => store.GetIdAsync(session, It.IsAny())) .ReturnsAsync("unique-session-id"); var manager = new OpenIddictSessionManager(cache, logger, options, store.Object); // Act var id = await manager.GetIdAsync(session); // Assert Assert.Equal("unique-session-id", id); store.Verify(store => store.GetIdAsync(session, It.IsAny()), Times.Once()); } [Fact] public async Task GetApplicationIdAsync_ThrowsAnExceptionForNullSession() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = Mock.Of>(); var manager = new OpenIddictSessionManager(cache, logger, options, store); // Act and assert var exception = await Assert.ThrowsAsync( () => manager.GetApplicationIdAsync(session: null!).AsTask()); Assert.Equal("session", exception.ParamName); } [Fact] public async Task GetApplicationIdAsync_ReturnsIdentifierFromStore() { // Arrange var session = new CustomSession(); var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = new Mock>(); store.Setup(store => store.GetApplicationIdAsync(session, It.IsAny())) .ReturnsAsync("application-id"); var manager = new OpenIddictSessionManager(cache, logger, options, store.Object); // Act var applicationId = await manager.GetApplicationIdAsync(session); // Assert Assert.Equal("application-id", applicationId); store.Verify(store => store.GetApplicationIdAsync(session, It.IsAny()), Times.Once()); } [Fact] public async Task GetAuthorizationIdAsync_ThrowsAnExceptionForNullSession() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = Mock.Of>(); var manager = new OpenIddictSessionManager(cache, logger, options, store); // Act and assert var exception = await Assert.ThrowsAsync( () => manager.GetAuthorizationIdAsync(session: null!).AsTask()); Assert.Equal("session", exception.ParamName); } [Fact] public async Task GetAuthorizationIdAsync_ReturnsIdentifierFromStore() { // Arrange var session = new CustomSession(); var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = new Mock>(); store.Setup(store => store.GetAuthorizationIdAsync(session, It.IsAny())) .ReturnsAsync("authorization-id"); var manager = new OpenIddictSessionManager(cache, logger, options, store.Object); // Act var authorizationId = await manager.GetAuthorizationIdAsync(session); // Assert Assert.Equal("authorization-id", authorizationId); store.Verify(store => store.GetAuthorizationIdAsync(session, It.IsAny()), Times.Once()); } [Fact] public async Task GetCreationDateAsync_ThrowsAnExceptionForNullSession() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = Mock.Of>(); var manager = new OpenIddictSessionManager(cache, logger, options, store); // Act and assert var exception = await Assert.ThrowsAsync( () => manager.GetCreationDateAsync(session: null!).AsTask()); Assert.Equal("session", exception.ParamName); } [Fact] public async Task GetCreationDateAsync_ReturnsCreationDateFromStore() { // Arrange var session = new CustomSession(); var creationDate = new DateTimeOffset(2026, 1, 1, 0, 0, 0, TimeSpan.Zero); var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = new Mock>(); store.Setup(store => store.GetCreationDateAsync(session, It.IsAny())) .ReturnsAsync(creationDate); var manager = new OpenIddictSessionManager(cache, logger, options, store.Object); // Act var result = await manager.GetCreationDateAsync(session); // Assert Assert.Equal(creationDate, result); store.Verify(store => store.GetCreationDateAsync(session, It.IsAny()), Times.Once()); } [Fact] public async Task GetLoginIdAsync_ThrowsAnExceptionForNullSession() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = Mock.Of>(); var manager = new OpenIddictSessionManager(cache, logger, options, store); // Act and assert var exception = await Assert.ThrowsAsync( () => manager.GetLoginIdAsync(session: null!).AsTask()); Assert.Equal("session", exception.ParamName); } [Fact] public async Task GetLoginIdAsync_ReturnsIdentifierFromStore() { // Arrange var session = new CustomSession(); var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = new Mock>(); store.Setup(store => store.GetLoginIdAsync(session, It.IsAny())) .ReturnsAsync("login-id"); var manager = new OpenIddictSessionManager(cache, logger, options, store.Object); // Act var loginId = await manager.GetLoginIdAsync(session); // Assert Assert.Equal("login-id", loginId); store.Verify(store => store.GetLoginIdAsync(session, It.IsAny()), Times.Once()); } [Fact] public async Task GetPropertiesAsync_ThrowsAnExceptionForNullSession() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = Mock.Of>(); var manager = new OpenIddictSessionManager(cache, logger, options, store); // Act and assert var exception = await Assert.ThrowsAsync( () => manager.GetPropertiesAsync(session: null!).AsTask()); Assert.Equal("session", exception.ParamName); } [Fact] public async Task GetPropertiesAsync_ReturnsPropertiesFromStore() { // Arrange var session = new CustomSession(); var properties = ImmutableDictionary.Empty; var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = new Mock>(); store.Setup(store => store.GetPropertiesAsync(session, It.IsAny())) .ReturnsAsync(properties); var manager = new OpenIddictSessionManager(cache, logger, options, store.Object); // Act var result = await manager.GetPropertiesAsync(session); // Assert Assert.Same(properties, result); store.Verify(store => store.GetPropertiesAsync(session, It.IsAny()), Times.Once()); } [Fact] public async Task GetStatusAsync_ThrowsAnExceptionForNullSession() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = Mock.Of>(); var manager = new OpenIddictSessionManager(cache, logger, options, store); // Act and assert var exception = await Assert.ThrowsAsync( () => manager.GetStatusAsync(session: null!).AsTask()); Assert.Equal("session", exception.ParamName); } [Fact] public async Task GetStatusAsync_ReturnsStatusFromStore() { // Arrange var session = new CustomSession(); var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = new Mock>(); store.Setup(store => store.GetStatusAsync(session, It.IsAny())) .ReturnsAsync(Statuses.Valid); var manager = new OpenIddictSessionManager(cache, logger, options, store.Object); // Act var status = await manager.GetStatusAsync(session); // Assert Assert.Equal(Statuses.Valid, status); store.Verify(store => store.GetStatusAsync(session, It.IsAny()), Times.Once()); } [Fact] public async Task GetSubjectAsync_ThrowsAnExceptionForNullSession() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = Mock.Of>(); var manager = new OpenIddictSessionManager(cache, logger, options, store); // Act and assert var exception = await Assert.ThrowsAsync( () => manager.GetSubjectAsync(session: null!).AsTask()); Assert.Equal("session", exception.ParamName); } [Fact] public async Task GetSubjectAsync_ReturnsSubjectFromStore() { // Arrange var session = new CustomSession(); var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = new Mock>(); store.Setup(store => store.GetSubjectAsync(session, It.IsAny())) .ReturnsAsync("subject"); var manager = new OpenIddictSessionManager(cache, logger, options, store.Object); // Act var subject = await manager.GetSubjectAsync(session); // Assert Assert.Equal("subject", subject); store.Verify(store => store.GetSubjectAsync(session, It.IsAny()), Times.Once()); } [Fact] public async Task ListAsync_ReturnsAllSessions() { // Arrange var sessions = new[] { new CustomSession(), new CustomSession() }; var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>( mock => mock.CurrentValue == new OpenIddictCoreOptions()); var store = new Mock>(); store.Setup(store => store.ListAsync(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(sessions.ToAsyncEnumerable()); var manager = new OpenIddictSessionManager(cache, logger, options, store.Object); // Act var results = new List(); await foreach (var scp in manager.ListAsync()) { results.Add(scp); } // Assert Assert.Equal(2, results.Count); store.Verify(store => store.ListAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once()); } [Fact] public async Task PopulateAsync_ThrowsAnExceptionForNullSession() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = Mock.Of>(); var manager = new OpenIddictSessionManager(cache, logger, options, store); var descriptor = new OpenIddictSessionDescriptor(); // Act and assert var exception = await Assert.ThrowsAsync( () => manager.PopulateAsync(session: null!, descriptor).AsTask()); Assert.Equal("session", exception.ParamName); } [Fact] public async Task PopulateAsync_ThrowsAnExceptionForNullDescriptor() { // Arrange var session = new CustomSession(); var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = Mock.Of>(); var manager = new OpenIddictSessionManager(cache, logger, options, store); // Act and assert var exception = await Assert.ThrowsAsync( () => manager.PopulateAsync(session, descriptor: null!).AsTask()); Assert.Equal("descriptor", exception.ParamName); } [Fact] public async Task UpdateAsync_ThrowsAnExceptionForNullSession() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>( mock => mock.CurrentValue == new OpenIddictCoreOptions()); var store = Mock.Of>(); var manager = new OpenIddictSessionManager(cache, logger, options, store); // Act and assert var exception = await Assert.ThrowsAsync( () => manager.UpdateAsync(session: null!).AsTask()); Assert.Equal("session", exception.ParamName); } [Fact] public async Task UpdateAsync_WithDescriptor_ThrowsAnExceptionForNullSession() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>( mock => mock.CurrentValue == new OpenIddictCoreOptions()); var store = Mock.Of>(); var manager = new OpenIddictSessionManager(cache, logger, options, store); var descriptor = new OpenIddictSessionDescriptor(); // Act and assert var exception = await Assert.ThrowsAsync( () => manager.UpdateAsync(session: null!, descriptor).AsTask()); Assert.Equal("session", exception.ParamName); } [Fact] public void ValidateAsync_ThrowsAnExceptionForNullSession() { // Arrange var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>(); var store = Mock.Of>(); var manager = new OpenIddictSessionManager(cache, logger, options, store); // Act and assert var exception = Assert.Throws( () => manager.ValidateAsync(session: null!)); Assert.Equal("session", exception.ParamName); } [Fact] public async Task ValidateAsync_ReturnsErrorWhenStatusIsEmpty() { // Arrange var session = new CustomSession(); var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>( mock => mock.CurrentValue == new OpenIddictCoreOptions()); var store = new Mock>(); store.Setup(store => store.GetStatusAsync(session, It.IsAny())) .ReturnsAsync(string.Empty); var manager = new OpenIddictSessionManager(cache, logger, options, store.Object); // Act var results = await manager.ValidateAsync(session).ToListAsync(); // Assert Assert.Contains(results, result => string.Equals(result.ErrorMessage, SR.GetResourceString(SR.ID2038), StringComparison.Ordinal)); } [Fact] public async Task ValidateAsync_ReturnsErrorWhenLoginIdIsEmpty() { // Arrange var session = new CustomSession(); var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>( mock => mock.CurrentValue == new OpenIddictCoreOptions()); var store = new Mock>(); store.Setup(store => store.GetStatusAsync(session, It.IsAny())) .ReturnsAsync(Statuses.Valid); store.Setup(store => store.GetLoginIdAsync(session, It.IsAny())) .ReturnsAsync(string.Empty); var manager = new OpenIddictSessionManager(cache, logger, options, store.Object); // Act var results = await manager.ValidateAsync(session).ToListAsync(); // Assert Assert.Contains(results, result => string.Equals(result.ErrorMessage, SR.GetResourceString(SR.ID2209), StringComparison.Ordinal)); } [Fact] public async Task ValidateAsync_ReturnsNoErrorsForValidSession() { // Arrange var session = new CustomSession(); var cache = Mock.Of>(); var logger = Mock.Of>>(); var options = Mock.Of>( mock => mock.CurrentValue == new OpenIddictCoreOptions()); var store = new Mock>(); store.Setup(store => store.GetStatusAsync(session, It.IsAny())) .ReturnsAsync(Statuses.Valid); store.Setup(store => store.GetLoginIdAsync(session, It.IsAny())) .ReturnsAsync("login-id"); var manager = new OpenIddictSessionManager(cache, logger, options, store.Object); // Act var results = await manager.ValidateAsync(session).ToListAsync(); // Assert Assert.DoesNotContain(results, static result => result != ValidationResult.Success); } public class CustomSession; }